ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.7
Committed: Tue Feb 6 22:52:45 2007 UTC (19 years, 8 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

# 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::Namespaces qw/xmpp_ns/;
9 use Net::DNS;
10 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
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 =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
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 sub debug_wrote_data {
229 my ($self, $data) = @_;
230 $self->event (debug_send => $data);
231 }
232
233 sub write_data {
234 my ($self, $data) = @_;
235 $self->SUPER::write_data ($data);
236 }
237
238 =head2 reg_cb ($eventname1, $cb1, [$eventname2, $cb2, ...])
239
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 my $handled;
266 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 $self->{features} = $node;
280
281 } 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 } 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 } elsif ($node->eq (client => 'message')) {
294 $self->event (message_xml => $node);
295 } elsif ($node->eq (client => 'presence')) {
296 $self->event (presence_xml => $node);
297 } 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 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
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 This method returns the newly generated id for this iq request.
332
333 =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 $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 Use C<$create_cb> to create the XML for the result.
374
375 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 about the meaning of C<%attrs>.
395
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 }
409
410 sub handle_iq {
411 my ($self, $node) = @_;
412
413 my $type = $node->attr ('type');
414
415 if ($type eq 'result') {
416 if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
417 $cb->($node);
418 }
419 } elsif ($type eq 'error') {
420 if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
421
422 my $error = $self->filter_error_stanza ($node);
423 $cb->(($error->[0] eq 'continue' ? $node : undef), $node, $error);
424 }
425
426 } else {
427 my $handled = 0;
428 $self->event ("iq_${type}_request_xml" => $node, \$handled);
429
430 my @from;
431 push @from, (to => $node->attr ('from')) if $node->attr ('from');
432
433 unless ($handled) {
434 $self->reply_iq_error ($node, undef, 'feature-not-implemented', @from);
435 }
436 }
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 my @tls = $node->find_all ([qw/tls starttls/]);
475
476 if (not ($self->{disable_ssl}) && not ($self->{ssl_enabled}) && @tls) {
477 $self->{writer}->send_starttls;
478
479 } elsif (not ($self->{authenticated}) and @mechs) {
480 $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 $self->event (stream_error => $error, (@txt ? $txt[0]->text : ''));
531 $self->event (stream_error_xml => $node);
532 $self->{writer}->send_end_of_stream;
533 }
534
535 =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 =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 $self->event (bind_error_xml => $err_iq);
608
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 =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 =head1 EVENTS
653
654 These events can be registered on with C<reg_cb>:
655
656 =over 4
657
658 =item stream_features_xml => $node
659
660 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
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 =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 =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 =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 =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 =item presence_xml => $node
728
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 =item message_xml => $node
733
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 =item iq_set_request_xml => $node, $handled_ref
738
739 =item iq_get_request_xml => $node, $handled_ref
740
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 =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 use Errno;
807 use Fcntl;
808 use Encode;
809
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 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 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 $self->set_noblock;
857
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 if ($l) {
865 $self->{read_buffer} .= $data;
866 $self->handle_data (\$self->{read_buffer});
867
868 } else {
869 return if $! == Errno::EAGAIN;
870 if (defined $l) {
871 $self->{disconnect_cb}->($self->{host}, $self->{port}, "EOF from server '$self->{host}:$self->{port}'");
872 $self->end_sockets;
873 return;
874
875 } else {
876 $self->{disconnect_cb}->($self->{host}, $self->{port}, "Error while reading from server '$self->{host}:$port': $!");
877 $self->end_sockets;
878 return;
879 }
880 }
881 });
882 return 1;
883 }
884
885 sub end_sockets {
886 my ($self) = @_;
887 delete $self->{r};
888 delete $self->{w};
889 delete $self->{socket};
890 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 }
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
929 unless ($self->{ssl_out_buffer}) { # refill buffer
930 $self->{ssl_out_buffer} = $self->{write_buffer};
931 $self->{write_buffer} = "";
932 }
933
934 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 if ($! != Errno::EAGAIN
959 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 } 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 }
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 if ($! != Errno::EAGAIN
999 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 } else {
1012 $self->{read_buffer} .= decode_utf8 ($self->{ssl_read_data});
1013 $self->handle_data (\$self->{read_buffer});
1014 $self->{ssl_read_data} = "";
1015 }
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 AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
1026 #d# warn "read cb [$poll]\n";
1027 $self->try_ssl_read;
1028 });
1029 }
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 #warn "write cb [$poll]\n";
1039 $self->try_ssl_write;
1040 1;
1041 });
1042 }
1043
1044 sub write_data {
1045 my ($self, $data) = @_;
1046 #return unless $self->{r};
1047
1048 my $cl = $self->{socket};
1049 $self->{write_buffer} .= $data;
1050
1051 unless ($self->{w}) {
1052 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 return if $! == Errno::EAGAIN;
1059 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 delete $self->{w};
1069 }
1070
1071 $self->debug_wrote_data (substr $self->{write_buffer}, 0, $len);
1072 $self->{write_buffer} = substr $self->{write_buffer}, $len;
1073 }
1074 });
1075
1076 } 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 }
1084 }
1085
1086 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 1; # End of Net::XMPP2