ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.9
Committed: Sat Mar 17 12:27:57 2007 UTC (19 years, 7 months ago) by elmex
Branch: MAIN
Changes since 1.8: +26 -363 lines
Log Message:
some minor cleanups.

File Contents

# Content
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::Event;
9 use Net::XMPP2::SimpleConnection;
10 use Net::XMPP2::Namespaces qw/xmpp_ns/;
11 use Net::DNS;
12
13 our @ISA = qw/Net::XMPP2::SimpleConnection Net::XMPP2::Event/;
14
15 =head1 NAME
16
17 Net::XMPP2::Connection - A XML stream that implements the XMPP RFC 3920.
18
19 =head1 SYNOPSIS
20
21 use Net::XMPP2::Connection;
22
23 my $con =
24 Net::XMPP2::Connection->new (
25 username => "abc",
26 domain => "jabber.org",
27 resource => "Net::XMPP2"
28 );
29
30 $con->connect or die "Couldn't connect to jabber.org: $!";
31 $con->init;
32 $con->reg_cb (stream_ready => sub { print "XMPP stream ready!\n" });
33
34 =head1 DESCRIPTION
35
36 This module represents a XMPP stream as described in RFC 3920. You can issue the basic
37 XMPP XML stanzas with methods like C<send_iq>, C<send_message> and C<send_presence>.
38
39 And receive events with the C<reg_cb> event framework from the connection.
40
41 If you need instant messaging stuff please take a look at C<Net::XMPP2::IM::Connection>.
42
43 =head1 METHODS
44
45 =head2 new (%args)
46
47 Following arguments can be passed in C<%args>:
48
49 =over 4
50
51 =item language => $tag
52
53 This should be the language of the human readable contents that
54 will be transmitted over the stream. The default will be 'en'.
55
56 Please look in RFC 3066 how C<$tag> should look like.
57
58 =item resource => $resource
59
60 If this argument is given C<$resource> will be passed as desired
61 resource on resource binding.
62
63 Note: You have to take care that the stringprep profile for
64 resources can be applied at: C<$resource>. Otherwise the server
65 might signal an error. See L<Net::XMPP2::Util> for utility functions
66 to check this.
67
68 =item domain => $domain
69
70 This is the destination host we are going to connect to.
71 As the connection won't be automatically connected use C<connect>
72 to initiate the connect.
73
74 Note: A SRV RR lookup will be performed to discover the real hostname
75 and port to connect to. See also C<connect>.
76
77 =item override_host => $host
78 =item override_port => $port
79
80 This will be used as override to connect to.
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 =item disable_ssl => $bool
103
104 If C<$bool> is true no SSL will be used.
105
106 =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 delete $self->{authenticated};
130 delete $self->{ssl_enabled};
131 $self->event (disconnect => $host, $port, $message);
132 };
133
134 for (qw/username password domain/) {
135 die "No '$_' argument given to new, but '$_' is required\n"
136 unless $self->{$_};
137 }
138
139 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 if ($self->{override_host}) {
172 ($host, $port) = ($self->{override_host}, $self->{override_port} || 5222);
173
174 } else {
175 unless ($no_srv_rr) {
176 my $res = Net::DNS::Resolver->new;
177 my $p = $res->query ('_xmpp-client._tcp.'.$host, 'SRV');
178 if ($p) {
179 my @srvs = grep { $_->type eq 'SRV' } $p->answer;
180 if (@srvs) {
181 @srvs = sort { $a->priority <=> $b->priority } @srvs;
182 @srvs = sort { $b->weight <=> $a->weight } @srvs; # TODO
183 $port = $srvs[0]->port;
184 $host = $srvs[0]->target;
185 }
186 }
187 }
188 }
189
190 if ($self->SUPER::connect ($host, $port)) {
191 $self->event (connect => $host, $port);
192 return 1;
193 } else {
194 return undef;
195 }
196 }
197
198 =head2 may_try_connect
199
200 Returns the number of left alternatives of hosts to connect to for the
201 domain passed to C<new>.
202
203 An internal list of tried hosts will be managed by C<connect> and those
204 hosts will be ignored by a SRV RR lookup (which will be done if you
205 call this function).
206
207 Use C<reset_connect_tries> to reset the internal list of tried hosts.
208
209 =cut
210
211 sub may_try_connect {
212 # TODO
213 }
214
215 =head2 reset_connect_tries
216
217 This function resets the internal list of tried hosts for C<connect>.
218 See also C<connect>.
219
220 =cut
221
222 sub reset_connect_tries {
223 # TODO
224 }
225
226 sub handle_data {
227 my ($self, $buf) = @_;
228 $self->event (debug_recv => $$buf);
229 $self->{parser}->feed (substr $$buf, 0, (length $$buf), '');
230 }
231
232 sub debug_wrote_data {
233 my ($self, $data) = @_;
234 $self->event (debug_send => $data);
235 }
236
237 sub write_data {
238 my ($self, $data) = @_;
239 $self->SUPER::write_data ($data);
240 }
241
242 sub handle_stanza {
243 my ($self, $p, $node) = @_;
244
245 if ($node->eq (stream => 'features')) {
246 $self->event (stream_features => $node);
247 $self->handle_stream_features ($node);
248 $self->{features} = $node;
249
250 } elsif ($node->eq (tls => 'proceed')) {
251 $self->enable_ssl;
252 $self->{parser}->init;
253 $self->{writer}->init;
254 $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
255
256 } elsif ($node->eq (sasl => 'challenge')) {
257 $self->handle_sasl_challenge ($node);
258 } elsif ($node->eq (sasl => 'success')) {
259 $self->handle_sasl_success ($node);
260 } elsif ($node->eq (client => 'iq')) {
261 $self->handle_iq ($node);
262 } elsif ($node->eq (client => 'message')) {
263 $self->event (message_xml => $node);
264 } elsif ($node->eq (client => 'presence')) {
265 $self->event (presence_xml => $node);
266 } elsif ($node->eq (stream => 'error')) {
267 $self->handle_error ($node);
268 } else {
269 warn "Didn't understood stanza: '" . $node->name . "'";
270 }
271 }
272
273 =head2 init ($domain)
274
275 Initiate the XML stream.
276
277 =cut
278
279 sub init {
280 my ($self) = @_;
281 $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
282 }
283
284 =head2 send_iq ($type, $create_cb, $result_cb, %attrs)
285
286 This method sends an IQ XMPP request.
287
288 Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
289 about the meaning of C<$type>, C<$create_cb> and C<%attrs>.
290
291 C<$result_cb> will be called when a result was received. The first argument to
292 C<$result_cb> will be a Net::XMPP2::Node instance containing the IQ result
293 stanza contents.
294
295 If the IQ resulted in a stanza error the second argument to C<$result_cb> will
296 be C<undef> (if the error type was not 'continue') and the third argument will
297 be a Net::XMPP2::Node containg the IQ error stanza. And the fourth argument
298 will be a array reference with following contents:
299
300 This method returns the newly generated id for this iq request.
301
302 =over 4
303
304 =item index 0: error type
305
306 This will be one of: 'cancel', 'continue', 'modify', 'auth' and 'wait'.
307
308 =item index 1: error condition element
309
310 This might be undefined if other XMPP speakers don't play nice i guess.
311
312 =item index 2: error text
313
314 This will be the human readable form of the error which is maybe undef if
315 not supplied.
316
317 =item index 3: error code
318
319 If the error element had an 'code' attribute it will be put here,
320 the RFC says that this is for backward compatibility :)
321
322 =back
323
324 =cut
325
326 sub send_iq {
327 my ($self, $type, $create_cb, $result_cb, %attrs) = @_;
328 my $id = $self->{iq_id}++;
329 $self->{iqs}->{$id} = $result_cb;
330 $self->{writer}->send_iq ($id, $type, $create_cb, %attrs);
331 $id
332 }
333
334 =head2 reply_iq_result ($req_iq_node, $create_cb, %attrs)
335
336 This method will generate a result reply to the iq request C<Net::XMPP2::Node>
337 in C<$req_iq_node>.
338
339 Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
340 about the meaning C<$create_cb> and C<%attrs>.
341
342 Use C<$create_cb> to create the XML for the result.
343
344 The type for this iq reply is 'result'.
345
346 =cut
347
348 sub reply_iq_result {
349 my ($self, $iqnode, $create_cb, %attrs) = @_;
350 $self->{writer}->send_iq ($iqnode->attr ('id'), 'result', $create_cb, %attrs);
351 }
352
353 =head2 reply_iq_error ($req_iq_node, $error_type, $error, %attrs)
354
355 This method will generate an error reply to the iq request C<Net::XMPP2::Node>
356 in C<$req_iq_node>.
357
358 C<$error_type> is one of 'cancel', 'continue', 'modify', 'auth' and 'wait'.
359 C<$error> is one of the defined error conditions described in
360 L<Net::XMPP2::Writer::write_error_tag>.
361
362 Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
363 about the meaning of C<%attrs>.
364
365 The type for this iq reply is 'error'.
366
367 =cut
368
369 sub reply_iq_error {
370 my ($self, $iqnode, $errtype, $error, %attrs) = @_;
371
372 $self->{writer}->send_iq (
373 $iqnode->attr ('id'), 'error',
374 sub { $self->{writer}->write_error_tag ($iqnode, $errtype, $error) },
375 %attrs
376 );
377 }
378
379 sub handle_iq {
380 my ($self, $node) = @_;
381
382 my $type = $node->attr ('type');
383
384 if ($type eq 'result') {
385 if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
386 $cb->($node);
387 }
388
389 } elsif ($type eq 'error') {
390 if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
391
392 my $error = $self->filter_error_stanza ($node);
393 $cb->(($error->[0] eq 'continue' ? $node : undef), $node, $error);
394 }
395
396 } else {
397 my $handled = 0;
398 $self->event ("iq_${type}_request_xml" => $node, \$handled);
399
400 my @from;
401 push @from, (to => $node->attr ('from')) if $node->attr ('from');
402
403 unless ($handled) {
404 $self->reply_iq_error ($node, undef, 'feature-not-implemented', @from);
405 }
406 }
407 }
408
409 sub filter_error_stanza {
410 my ($self, $node) = @_;
411
412 my @error;
413 my ($err) = $node->find_all ([qw/client error/]);
414 $error[0] = $err->attr ('type');
415 $error[3] = $err->attr ('code');
416
417 if ($err) {
418 if (my ($txt) = $err->find_all ([qw/stanzas text/])) {
419 $error[2] = $txt->text;
420 }
421 for my $er (
422 qw/bad-request conflict feature-not-implemented forbidden
423 gone internal-server-error item-not-found jid-malformed
424 not-acceptable not-allowed not-authorized payment-required
425 recipient-unavailable redirect registration-required
426 remote-server-not-found remote-server-timeout resource-constraint
427 service-unavailable subscription-required undefined-condition
428 unexpected-request/)
429 {
430 if (my ($el) = $err->find_all ([stanzas => $er])) {
431 $error[1] = $el;
432 last;
433 }
434 }
435 } else {
436 warn "no error element found in error stanza!";
437 }
438
439 return \@error
440 }
441
442 sub handle_stream_features {
443 my ($self, $node) = @_;
444 my @mechs = $node->find_all ([qw/sasl mechanisms/], [qw/sasl mechanism/]);
445 my @bind = $node->find_all ([qw/bind bind/]);
446 my @tls = $node->find_all ([qw/tls starttls/]);
447
448 if (not ($self->{disable_ssl}) && not ($self->{ssl_enabled}) && @tls) {
449 $self->{writer}->send_starttls;
450
451 } elsif (not ($self->{authenticated}) and @mechs) {
452 $self->{writer}->send_sasl_auth (
453 (join ' ', map { $_->text } @mechs),
454 $self->{username}, $self->{domain}, $self->{password}
455 );
456
457 } elsif (@bind) {
458 $self->do_rebind ($self->{resource});
459 }
460 }
461
462 sub handle_sasl_challenge {
463 my ($self, $node) = @_;
464 $self->{writer}->send_sasl_response ($node->text);
465 }
466
467 sub handle_sasl_success {
468 my ($self, $node) = @_;
469 $self->{authenticated} = 1;
470 $self->{parser}->init;
471 $self->{writer}->init;
472 $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
473 }
474
475 sub handle_error {
476 my ($self, $node) = @_;
477 my @txt = $node->find_all ([qw/stream text/]);
478 my $error;
479 for my $er (
480 qw/bad-format bad-namespace-prefix conflict connection-timeout host-gone
481 host-unknown improper-addressing internal-server-error invalid-from
482 invalid-id invalid-namespace invalid-xml not-authorized policy-violation
483 remote-connection-failed resource-constraint restricted-xml
484 see-other-host system-shutdown undefined-condition unsupported-stanza-type
485 unsupported-version xml-not-well-formed/)
486 {
487 for ($node->nodes) {
488 if ($node->eq (streams => $er)) {
489 $error = $_->name;
490 last
491 }
492 }
493 }
494 unless ($error) {
495 warn "got undefined error stanza, trying to find any undefined error...";
496 for ($node->nodes) {
497 if ($node->eq_ns ('streams')) {
498 $error = $node->name;
499 }
500 }
501 }
502 $self->event (stream_error => $error, (@txt ? $txt[0]->text : ''));
503 $self->event (stream_error_xml => $node);
504 $self->{writer}->send_end_of_stream;
505 }
506
507 =head2 send_presence ($type, $create_cb, %attrs)
508
509 This method sends a presence stanza, for the meanings
510 of C<$type>, C<$create_cb> and C<%attrs> please take a look
511 at the documentation for L<Net::XMPP2::Writer::send_presence>.
512
513 This methods does attach an id attribute to the message stanza and
514 will return the id that was used (so you can react on possible replies).
515
516 =cut
517
518 sub send_presence {
519 my ($self, $type, $create_cb, %attrs) = @_;
520 my $id = $self->{iq_id}++;
521 $self->{writer}->send_presence ($id, $type, $create_cb, %attrs);
522 $id
523 }
524
525 =head2 send_message ($to, $type, $create_cb, %attrs)
526
527 This method sends a presence stanza, for the meanings
528 of C<$to>, C<$type>, C<$create_cb> and C<%attrs> please take a look
529 at the documentation for L<Net::XMPP2::Writer::send_message>.
530
531 This methods does attach an id attribute to the message stanza and
532 will return the id that was used (so you can react on possible replies).
533
534 =cut
535
536 sub send_message {
537 my ($self, $to, $type, $create_cb, %attrs) = @_;
538 my $id = $self->{iq_id}++;
539 $self->{writer}->send_message ($id, $to, $type, $create_cb, %attrs);
540 $id
541 }
542
543 =head2 do_rebind ($resource)
544
545 In case you got a C<bind_error> event and want to retry
546 binding you can call this function to set a new C<$resource>
547 and retry binding.
548
549 If it fails again you can call this again. Becareful not to
550 end up in a loop!
551
552 If binding was successful the C<stream_ready> event will be generated.
553
554 =cut
555
556 sub do_rebind {
557 my ($self, $resource) = @_;
558 $self->{resource} = $resource;
559 $self->send_iq (
560 set =>
561 sub {
562 my ($w) = @_;
563 if ($self->{resource}) {
564 $w->startTag ([xmpp_ns ('bind'), 'bind']);
565 $w->startTag ([xmpp_ns ('bind'), 'resource']);
566 $w->characters ($self->{resource});
567 $w->endTag;
568 $w->endTag;
569 } else {
570 $w->emptyTag ([xmpp_ns ('bind'), 'bind'])
571 }
572 },
573 sub {
574 my ($ret_iq, $err_iq, $err) = @_;
575
576 if ($err) {
577 my ($res) = $err_iq->find_all ([qw/bind bind/], [qw/bind resource/]);
578 $self->event (bind_error => $err->[0], ($res ? $res : $self->{resource}));
579 $self->event (bind_error_xml => $err_iq);
580
581 } else {
582 my @jid = $ret_iq->find_all ([qw/bind bind/], [qw/bind jid/]);
583 my $jid = $jid[0]->text;
584 unless ($jid) { die "Got empty JID tag from server!\n" }
585 $self->{jid} = $jid;
586
587 $self->event (stream_ready => $jid);
588 }
589 }
590 );
591 }
592
593 =head2 jid
594
595 After the stream has been bound to a resource the JID can be retrieved via this
596 method.
597
598 =cut
599
600 sub jid { $_[0]->{jid} }
601
602 =head2 features
603
604 Returns the last received <features> tag in form of an L<Net::XMPP2::Node> object.
605
606 =cut
607
608 sub features { $_[0]->{features} }
609
610 #sub enable_extension {
611 # my ($self, @exts) = @_;
612 # for (@exts) {
613 # if (/^xep-(\d+)$/i) {
614 # $self->{ext}->{''.(1*$1)} = 1;
615 # }
616 # }
617 #}
618 #
619 #sub check_extension {
620 # my ($self, $extnum) = @_;
621 # return $self->{ext}->{"$extnum"} || $Net::XMPP2::EXTENSION_ENABLED{"$extnum"};
622 #}
623
624 =head1 EVENTS
625
626 These events can be registered on with C<reg_cb>:
627
628 =over 4
629
630 =item stream_features_xml => $node
631
632 This event is sent when a stream feature (<features>) tag is received. C<$node> is the
633 L<Net::XMPP2::Node> object that represents the <features> tag.
634
635 =item stream_ready => $jid
636
637 This event is sent if the XML stream has been established (and
638 resources have been bound) and is ready for transmitting regular stanzas.
639
640 C<$jid> is the bound jabber id.
641
642 =item stream_error => $error, $text
643
644 This event is sent if a XML stream error occured. C<$error>
645 will be the machine readable error string, which is one of:
646
647 bad-format bad-namespace-prefix conflict connection-timeout host-gone
648 host-unknown improper-addressing internal-server-error invalid-from
649 invalid-id invalid-namespace invalid-xml not-authorized policy-violation
650 remote-connection-failed resource-constraint restricted-xml
651 see-other-host system-shutdown undefined-condition unsupported-stanza-type
652 unsupported-version xml-not-well-formed
653
654 And C<$text> is an optional human readable text.
655
656 =item stream_error_xml => $node
657
658 This is sent when a XML stream error occurs. C<$node>
659 is the XML node of the 'error' stanza and will be a L<Net::XMPP2::Node>
660 object.
661
662 =item bind_error => $error_name, $resource
663
664 This event is generated when the stream was unable to bind to
665 any or the in C<new> specified resource. C<$error_name>
666 may be 'bad-request', 'not-allowed' or 'conflict'.
667
668 Node: this is untested, i couldn't get the server to send a bind error
669 to test this.
670
671 =item bind_error_xml => $iq_error_node
672
673 This event is generated when the stream was unable to bind to
674 any or the in C<new> specified resource. C<$iq_error_node> contains
675 the IQ error L<Net::XMPP2::Node>.
676
677 =item connect => $host, $port
678
679 This event is generated when a successful connect was performed to
680 the domain passed to C<new>.
681
682 Note: C<$host> and C<$port> might be different from the domain you passed to
683 C<new> if C<connect> performed a SRV RR lookup.
684
685 If this connection is lost a C<disconnect> will be generated with the same
686 C<$host> and C<$port>.
687
688 =item disconnect => $host, $port, $message
689
690 This event is generated when the connection was lost or another error
691 occured while writing or reading from it.
692
693 C<$message> is a humand readable error message for the failure.
694 C<$host> and C<$port> were the host and port we were connected to.
695
696 Note: C<$host> and C<$port> might be different from the domain you passed to
697 C<new> if C<connect> performed a SRV RR lookup.
698
699 =item presence_xml => $node
700
701 This event is sent when a presence stanza is received. C<$node> is the
702 L<Net::XMPP2::Node> object that represents the <presence> tag.
703
704 =item message_xml => $node
705
706 This event is sent when a message stanza is received. C<$node> is the
707 L<Net::XMPP2::Node> object that represents the <message> tag.
708
709 =item iq_set_request_xml => $node, $handled_ref
710
711 =item iq_get_request_xml => $node, $handled_ref
712
713 These events are sent when an iq request stanza of type 'get' or 'set' is received.
714 C<$type> will either be 'get' or 'set' and C<$node> will be the L<Net::XMPP2::Node>
715 object of the iq tag.
716
717 If C<$$handled_ref> is true an event handler should not handle this message anymore.
718
719 If one of the event handlers handled this message the scalar pointed at by
720 the reference in C<$handled_ref> should be set to 1 true value. If C<$$handled_ref>
721 is still false after all event handlers were executed an error iq will be generated.
722
723 =back
724
725 =head1 AUTHOR
726
727 Robin Redeker, C<< <elmex at ta-sa.org> >>
728
729 =head1 COPYRIGHT & LICENSE
730
731 Copyright 2007 Robin Redeker, all rights reserved.
732
733 This program is free software; you can redistribute it and/or modify it
734 under the same terms as Perl itself.
735
736 =cut
737
738 1; # End of Net::XMPP2