ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.6
Committed: Sat Feb 3 11:39:55 2007 UTC (19 years, 8 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

# Content
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 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
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 =item disable_ssl => $bool
104
105 If C<$bool> is true no SSL will be used.
106
107 =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 sub debug_wrote_data {
223 my ($self, $data) = @_;
224 $self->event (debug_send => $data);
225 }
226
227 sub write_data {
228 my ($self, $data) = @_;
229 $self->SUPER::write_data ($data);
230 }
231
232 =head2 reg_cb ($eventname1, $cb1, [$eventname2, $cb2, ...])
233
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 my $handled;
260 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 $self->{features} = $node;
274
275 } 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 } 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 } elsif ($node->eq (client => 'message')) {
288 $self->event (message_xml => $node);
289 } elsif ($node->eq (client => 'presence')) {
290 $self->event (presence_xml => $node);
291 } 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 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
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 This method returns the newly generated id for this iq request.
326
327 =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 $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 Use C<$create_cb> to create the XML for the result.
368
369 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 about the meaning of C<%attrs>.
389
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 }
403
404 sub handle_iq {
405 my ($self, $node) = @_;
406
407 my $type = $node->attr ('type');
408
409 if ($type eq 'result') {
410 if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
411 $cb->($node);
412 }
413 } elsif ($type eq 'error') {
414 if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
415
416 my $error = $self->filter_error_stanza ($node);
417 $cb->(($error->[0] eq 'continue' ? $node : undef), $node, $error);
418 }
419
420 } else {
421 my $handled = 0;
422 $self->event ("iq_${type}_request_xml" => $node, \$handled);
423
424 my @from;
425 push @from, (to => $node->attr ('from')) if $node->attr ('from');
426
427 unless ($handled) {
428 $self->reply_iq_error ($node, undef, 'feature-not-implemented', @from);
429 }
430 }
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 my @tls = $node->find_all ([qw/tls starttls/]);
469
470 if (not ($self->{disable_ssl}) && not ($self->{ssl_enabled}) && @tls) {
471 $self->{writer}->send_starttls;
472
473 } elsif (not ($self->{authenticated}) and @mechs) {
474 $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 $self->event (stream_error => $error, (@txt ? $txt[0]->text : ''));
525 $self->event (stream_error_xml => $node);
526 $self->{writer}->send_end_of_stream;
527 }
528
529 =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 =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 $self->event (bind_error_xml => $err_iq);
602
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 =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 =head1 EVENTS
647
648 These events can be registered on with C<reg_cb>:
649
650 =over 4
651
652 =item stream_features_xml => $node
653
654 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
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 =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 =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 =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 =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 =item presence_xml => $node
722
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 =item message_xml => $node
727
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 =item iq_set_request_xml => $node, $handled_ref
732
733 =item iq_get_request_xml => $node, $handled_ref
734
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 =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 use Errno;
801 use Fcntl;
802 use Encode;
803
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 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 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 $self->set_noblock;
851
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 if ($l) {
859 $self->{read_buffer} .= $data;
860 $self->handle_data (\$self->{read_buffer});
861
862 } else {
863 return if $! == Errno::EAGAIN;
864 if (defined $l) {
865 $self->{disconnect_cb}->($self->{host}, $self->{port}, "EOF from server '$self->{host}:$self->{port}'");
866 $self->end_sockets;
867 return;
868
869 } else {
870 $self->{disconnect_cb}->($self->{host}, $self->{port}, "Error while reading from server '$self->{host}:$port': $!");
871 $self->end_sockets;
872 return;
873 }
874 }
875 });
876 return 1;
877 }
878
879 sub end_sockets {
880 my ($self) = @_;
881 delete $self->{r};
882 delete $self->{w};
883 delete $self->{socket};
884 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 }
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
923 unless ($self->{ssl_out_buffer}) { # refill buffer
924 $self->{ssl_out_buffer} = $self->{write_buffer};
925 $self->{write_buffer} = "";
926 }
927
928 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 if ($! != Errno::EAGAIN
953 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 } 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 }
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 if ($! != Errno::EAGAIN
993 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 } else {
1006 $self->{read_buffer} .= decode_utf8 ($self->{ssl_read_data});
1007 $self->handle_data (\$self->{read_buffer});
1008 $self->{ssl_read_data} = "";
1009 }
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 AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
1020 #d# warn "read cb [$poll]\n";
1021 $self->try_ssl_read;
1022 });
1023 }
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 #warn "write cb [$poll]\n";
1033 $self->try_ssl_write;
1034 1;
1035 });
1036 }
1037
1038 sub write_data {
1039 my ($self, $data) = @_;
1040 #return unless $self->{r};
1041
1042 my $cl = $self->{socket};
1043 $self->{write_buffer} .= $data;
1044
1045 unless ($self->{w}) {
1046 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 return if $! == Errno::EAGAIN;
1053 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 delete $self->{w};
1063 }
1064
1065 $self->debug_wrote_data (substr $self->{write_buffer}, 0, $len);
1066 $self->{write_buffer} = substr $self->{write_buffer}, $len;
1067 }
1068 });
1069
1070 } 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 }
1078 }
1079
1080 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 1; # End of Net::XMPP2