File Coverage

blib/lib/Jabber/Connection.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Jabber::Connection;
2              
3             # $Id: Connection.pm,v 1.7 2002/05/06 16:53:11 dj Exp $
4              
5             =head1 NAME
6              
7             Connection - Simple connectivity functions for Jabber
8              
9             =head1 SYNOPSIS
10              
11             # client connection:
12             my $c = new Jabber::Connection(
13             server => 'jabber.org',
14             log => 1,
15             );
16            
17             # component connection:
18             # my $c = new Jabber::Connection(
19             # server => 'localhost:5700',
20             # localname => 'comp.localhost',
21             # ns => 'jabber:component:accept',
22             # log => 1,
23             # debug => 1,
24             # );
25            
26             die "oops: ".$c->lastError unless $c->connect();
27            
28             $c->register_beat(10, \&every_10_seconds);
29            
30             $c->register_handler('presence',\&presence);
31             $c->register_handler('iq',\&handle_iq_conference);
32             $c->register_handler('iq',\&handle_iq_browse);
33            
34             $c->auth('qmacro','password','myresource'); # client auth
35             # $c->auth('secret'); # component auth
36              
37             $c->send('<presence/>');
38              
39             $c->start;
40            
41             =head1 DESCRIPTION
42              
43             The Jabber::Connection package provides basic functions
44             for connecting clients and components to a Jabber server.
45              
46             =cut
47              
48 1     1   2266 use strict;
  1         3  
  1         2605  
49 1     1   1842 use XML::Parser;
  0            
  0            
50             use IO::Socket::INET;
51             use IO::Select;
52             use Digest::SHA1 qw(sha1_hex);
53             use Carp;
54              
55             use Jabber::NS qw(:all);
56             use Jabber::NodeFactory;
57              
58             use constant BEAT => 5;
59              
60             use vars qw($VERSION);
61              
62             $VERSION = '0.04';
63              
64             my $id = 1;
65              
66             =head1 METHODS
67              
68             =over 4
69              
70             =item new()
71              
72             The connection constructor. Returns a new Jabber::Connection object.
73             The parameters are specified in a
74              
75             param => value
76              
77             list.
78              
79             For a basic client connection, you can specify the minimum
80              
81             my $c = new Jabber::Connection(server => 'jabber.org');
82              
83             If no port is specified, the default 5222 will be used.
84             There are other parameters that can be passed:
85              
86             =over 4
87              
88             =item ns
89              
90             the namespace that qualifies the connection stream. If
91             left unspecified, this will default to 'jabber:client'.
92             For a TCP socket-based component, specify
93             'jabber:component:accept'. [ *** These are the only two
94             stream namespaces supported now *** ]
95              
96             =item localname
97              
98             the name of the component in a component connection.
99              
100             =item ssl
101              
102             whether the connection should use SSL
103             [ *** not supported yet! *** ]
104              
105             =back
106              
107             See the SYNOPSIS for examples of new().
108              
109             =cut
110              
111             sub new {
112              
113             my ($class, %args) = @_;
114             my $self = {};
115              
116             # poss. keys in %args: server, namespace, localname, ssl (?)
117              
118             croak "No host specified" unless $args{server};
119             ($self->{host}, $self->{port}) = split(":", $args{server});
120             $self->{port} ||= 5222;
121              
122             $self->{ns} = $args{ns} || NS_CLIENT;
123             $self->{localname} = $args{localname};
124             $self->{debug} = $args{debug};
125             $self->{log} = $args{log};
126            
127             $self->{parser} = new XML::Parser
128             (
129             Handlers => {
130             Start => sub { $self->_startTag(@_) },
131             End => sub { $self->_endTag(@_) },
132             Char => sub { $self->_charData(@_) },
133             }
134             )->parse_start();
135            
136             $self->{nf} = Jabber::NodeFactory->new();
137             $self->{beatcount} = 0;
138             $self->{connected} = 0;
139             $self->{streamerror} = 0;
140             return bless $self, $class;
141              
142             }
143              
144              
145             =item connect()
146              
147             Use this to establish the stream to the Jabber server. There
148             are no parameters required. If a problem occurs, the function
149             returns 0, and the error reason is available by calling
150             C<lastError()>.
151              
152             Example:
153              
154             $c->connect();
155              
156             =cut
157              
158             sub connect {
159              
160             my $self = shift;
161             $self->{socket} = new IO::Socket::INET
162             (
163             PeerAddr => $self->{host},
164             PeerPort => $self->{port},
165             Proto => 'tcp',
166             );
167            
168             unless ($self->{socket}) {
169             $self->{errortext} = "Can't establish socket connection";
170             return 0;
171             }
172              
173             $self->{select} = new IO::Select($self->{socket});
174              
175             $self->_write($self->_stream_header());
176             $self->_read();
177             if ($self->{streamerror}) {
178             return 0;
179             }
180             else {
181             return $self->{connected} = 1;
182             }
183              
184             }
185              
186              
187             =item disconnect()
188              
189             Use this to terminate the stream and end the connection.
190              
191             Example:
192              
193             $c->disconnect();
194              
195             =cut
196              
197             sub disconnect {
198              
199             my $self = shift;
200              
201             # send unavailable presence?
202             $self->_write("</stream:stream>");
203              
204             }
205              
206              
207             =item process()
208              
209             Call this function to look for incoming fragments on the stream.
210             You can specify an optional argument which is the number of seconds
211             to wait while looking. If no argument is given, a value of 0 is
212             assumed.
213              
214             An incoming fragment is parsed and assembled into a Node object which
215             is dispatched to any handlers that have been registered for the Node
216             object's tag name.
217              
218             Examples:
219              
220             $c->process(); # look for any fragments but don't
221             # wait around if there aren't any
222              
223             $c->process(5); # wait for up to 5 seconds for fragments
224             # to come in on the stream
225              
226              
227             =cut
228              
229             sub process {
230              
231             my $self = shift;
232             my $timeout = shift || 0;
233              
234             if ($self->{select}->can_read($timeout)) {
235             return $self->_read();
236             }
237              
238             return 1;
239              
240              
241             }
242              
243             sub _getID {
244              
245             $id++;
246              
247             }
248              
249              
250             =item auth()
251              
252             Perform authorization. This function takes either one or three
253             arguments, depending on what type of connection has been made.
254             If you have made a I<component> connection, the secret must be
255             specified here as the single argument. If you have made a
256             I<client> connection, the username, password and resource must
257             be specified.
258              
259             Example:
260              
261             $c->auth('secret'); # component auth
262             $c->auth('user','password','resource'); # client auth
263              
264             For a component authorization, the <handshake/> based process
265             is used. For a client authorization, the JSM is queried for the
266             supported authentication methods, and then one is picked,
267             degrading gracefully through zero-k, digest and plaintext
268             methods.
269              
270             =cut
271              
272             sub auth {
273              
274             my $self = shift;
275             my ($user, $pass, $resource, %args, $secret);
276              
277             $self->_checkConnected;
278              
279             if ($self->{ns} eq NS_CLIENT) {
280              
281             ($user, $pass, $resource, %args) = @_;
282             croak "Supply user/pass/resource" unless $user and $pass and $resource;
283              
284             my $auth_node = $self->{nf}->newNode('iq');
285             $auth_node->attr('type', IQ_GET);
286             my $query = $auth_node->insertTag('query');
287             $query->attr('xmlns', NS_AUTH);
288             $query->insertTag('username')->data($user);
289            
290             my $get_result = $self->ask($auth_node);
291              
292             # Assume we can authenticate and prepare a set
293             $auth_node->attr('type', IQ_SET);
294             $auth_node->attr('id', $self->_getID());
295              
296             # Zero-k?
297             if ($get_result->getTag('query')->getTag('token')) {
298             $self->_debug("auth: zerok supported");
299             my $hash = sha1_hex($pass);
300             my $seq = $get_result->getTag('query')->getTag('sequence')->data;
301             $hash = sha1_hex($hash.$get_result->getTag('query')->getTag('token')->data);
302             $hash = sha1_hex($hash) while $seq--;
303             $query->insertTag('hash')->data($hash);
304             }
305            
306             # digest?
307             elsif ($get_result->getTag('query')->getTag('digest')) {
308             $self->_debug("auth: digest supported");
309             $query->insertTag('digest')->data(sha1_hex($self->{streamid}.$pass));
310             }
311            
312             # plaintext?
313             elsif ($get_result->getTag('query')->getTag('password')) {
314             $self->_debug("auth: plaintext supported");
315             $query->insertTag('password')->data($pass);
316             }
317            
318             else {
319            
320             croak "No authentication methods available";
321            
322             }
323            
324             # abort to do
325            
326             # Add resource (common to all auth methods)
327             $query->insertTag('resource')->data($resource);
328            
329             # Auth attempt
330             my $set_result = $self->ask($auth_node);
331            
332             # XXX todo: perhaps return undef/0 instead of croaking?
333              
334             unless ($set_result->attr('type') eq IQ_RESULT) {
335             croak "auth failed"; }
336             }
337              
338              
339             elsif ($self->{ns} eq NS_ACCEPT) {
340             ($secret) = @_;
341              
342             my $handshake = $self->{nf}->newNode('handshake');
343             $handshake->data(sha1_hex($self->{streamid}.$secret));
344              
345             my $result = $self->ask($handshake);
346              
347             # XXX todo: will barf with stream:error if bad secret.
348             # XXX need to catch.
349              
350             }
351              
352             }
353              
354              
355             sub _stream_header {
356              
357             my $self = shift;
358             my $to = defined($self->{localname}) ? $self->{localname} : $self->{host};
359             my $hdr = qq[<?xml version='1.0'?><stream:stream xmlns='$self->{ns}' xmlns:stream='http://etherx.jabber.org/streams' to='$to'];
360             # $hdr .= qq[ from='$self->{localname}'] if $self->{ns} eq NS_ACCEPT;
361             $hdr .= qq[>];
362             return $hdr;
363             }
364              
365              
366             =item send()
367              
368             Send data across the stream with this function. You can send either
369             XML in string form, or send a Node object.
370              
371             Examples:
372              
373             $c->send('<presence/>');
374              
375             my $msg = $nf->newNode('message')->insertTag('body')->data('hello');
376             $msg->attr('to','qmacro@jabber.org');
377             $c->send($msg);
378              
379             =cut
380              
381             sub send {
382              
383             my $self = shift;
384              
385             $self->_checkConnected;
386              
387             my $what = shift;
388             if (ref($what) eq 'Jabber::NodeFactory::Node') {
389             $what = $what->toStr();
390             }
391             $self->_write($what);
392              
393             }
394              
395              
396             sub _write {
397              
398             my $self = shift;
399             my $data = shift;
400             $self->_log("SEND: ".$data);
401              
402             $self->{socket}->send($data);
403            
404             }
405              
406             sub _read {
407              
408             my $self = shift;
409             my $data;
410             my $received;
411              
412             while (defined $self->{socket}->recv($data, 1024)) { # or POSIX::BUFSIZ?
413             $received .= $data;
414             last if length($data) != 1024;
415             }
416             $self->_log("RECV: ".$received);
417             $self->{parser}->parse_more($received);
418              
419             return $received;
420             }
421              
422              
423             sub _log {
424              
425             my $self = shift;
426             my $string = shift;
427              
428             if ($self->{log}) {
429             print STDERR $string, "\n";
430             }
431              
432             }
433              
434              
435              
436             sub _debug {
437              
438             my $self = shift;
439             my $string = shift;
440              
441             if ($self->{debug}) {
442             print STDERR $string, "\n";
443             }
444              
445             }
446              
447            
448             sub _startTag {
449              
450             my ($self, $expat, $tag, %attr) = @_;
451             if ($tag eq "stream:stream") {
452             $self->{confirmedhost} = $attr{from};
453             $self->{streamid} = $attr{id};
454             }
455             else {
456             $self->{depth} += 1;
457              
458             # Top level fragment
459             if ($self->{depth} == 1) {
460              
461             # Check it's not an error
462             if ($tag eq 'stream:error') {
463             $self->{streamerror} = 1;
464             }
465             # Not an error - create Node
466             else {
467             $self->_debug("startTag: creating new node for $tag");
468             $self->{node} = Jabber::NodeFactory::Node->new($tag);
469             $self->{node}->attr($_, $attr{$_}) foreach keys %attr;
470             $self->{currnode} = $self->{node};
471             }
472             }
473              
474             # Some node within a fragment
475             else {
476             my $kid = $self->{currnode}->insertTag($tag);
477             $kid->attr($_, $attr{$_}) foreach keys %attr;
478             $self->{currnode} = $kid;
479             }
480             }
481             }
482              
483             sub _endTag {
484              
485             my ($self, $expat, $tag) = @_;
486              
487             # Don't bother to do anything if there's an error
488             return if $self->{streamerror};
489              
490             if ($self->{depth} == 1) {
491             $self->_dispatch($self->{currnode});
492             }
493             else {
494             $self->{currnode} = $self->{currnode}->parent();
495             }
496              
497             $self->{depth} -= 1;
498              
499             }
500              
501             sub _charData {
502              
503             my ($self, $expat, $data) = @_;
504            
505             # Die if we get an error mid-stream
506             if ($self->{streamerror}) {
507             $self->{errortext} = $data;
508             croak "stream error: $data" if $self->{connected};
509             }
510              
511             # Otherwise append the data to the current node
512             else {
513             $self->{currnode}->data($self->{currnode}->data().$data);
514             }
515             }
516              
517              
518             =item lastError()
519              
520             Returns the last error that occured. This will usually be the
521             text from a stream error.
522              
523             =cut
524              
525             sub lastError {
526             my $self = shift;
527             $self->{errortext};
528             }
529              
530              
531             sub _dispatch {
532              
533             my ($self, $node) = @_;
534             $self->_debug("dispatching ".$node->name);
535              
536             # Expecting an answer?
537             if ($self->{askID}) {
538             $self->{askID} = undef;
539             $self->{answer} = $node;
540             return;
541             }
542              
543             # Otherwise call the handlers
544             my $parcel = undef;
545             foreach my $handler (@{$self->{handlers}->{$node->name}}) {
546             $parcel = $handler->($node, $parcel) || $parcel;
547             last if defined $parcel and $parcel eq r_HANDLED;
548             }
549              
550             }
551              
552              
553             =item ask()
554              
555             Send something and wait for a response relating to what was sent. This
556             relation is established using an id attribute in the top level tag of
557             the node being sent. If there is no id attribute, one is inserted with
558             a value automatically assigned.
559              
560             =cut
561              
562             sub ask {
563              
564             my ($self, $node) = @_;
565             $self->_debug("ask: ".$node->name);
566              
567             # Add id if needed and remember
568             unless ($self->{askID} = $node->attr('id')) {
569             $self->_debug("ask: no ID - getting one");
570             $self->{askID} = $node->attr('id',$self->_getID());
571             }
572             $self->_debug("ask: id=".$self->{askID});
573              
574             # Send
575             $self->_write($node->toStr());
576              
577             # Wait for response
578             while (not defined $self->{answer}) {
579             $self->_debug("ask: waiting on answer");
580             $self->process(1);
581             }
582              
583             my $answer = $self->{answer};
584             $self->{answer} = undef;
585              
586             $self->_debug("ask: got answer: ".$answer->toStr());
587              
588             return $answer;
589             }
590              
591              
592             =item register_handler()
593              
594             When a fragment is received and turned into a Node object, a dispatching
595             process is started which will call handlers (callbacks) that you can set
596             using this function.
597              
598             The function takes two arguments. The first is used to identify the node
599             type (the element) - e.g. 'message', 'presence' or 'iq'. The second is
600             a reference to a subroutine.
601              
602             You can register as many handlers as you wish. Each of the handlers
603             registered for a specific node type will be called in turn (in the
604             order that they were registered). Each of the handlers are passed two
605             things - the node being dispatched, and a 'parcel' which can be used to
606             share data between the handlers being called. The parcel value passed
607             to the first handler in the call sequence is undef. Whatever value
608             is returned by a particular handler is then passed onto the next
609             handler.
610              
611             If a handler returns nothing (e.g. by simply the C<return> statement),
612             then the parcel data remains unaffected and is passed on intact to the
613             next handler.
614              
615             (You don't have to do anything with the parcel; it's there just in
616             case you want to pass something along the call sequence.)
617              
618             If a handler returns the special value represented by the constant
619             C<r_HANDLED>, the call sequence is ended - no more handlers in the
620             list are called in the dispatch for that node.
621              
622             Examples:
623              
624             $c->register_handler(
625             message => sub {
626             ...
627             }
628             );
629              
630             $c->register_handler('iq', \&handle_version);
631             $c->register_handler('iq', \&handle_time);
632             $c->register_handler('iq', \&handle_browse);
633              
634             =cut
635              
636             sub register_handler {
637              
638             my $self = shift;
639             my ($tag, $handler) = @_;
640             $self->_debug("registering handler $handler");
641             push @{$self->{handlers}->{$tag}}, $handler;
642            
643             }
644              
645              
646             =item register_beat()
647              
648             You can register subroutines to be called on a regular basis using
649             the C<heartbeat> feature. The first argument is the number of seconds
650             ('every N seconds'), the second is a subroutine reference.
651              
652             Example:
653              
654             $c->register_beat(1800, \&getRSS);
655              
656             This example registers a subroutine getRSS() to be called every
657             half an hour.
658              
659             Note: the heart doesn't start beating until the start() function
660             is called.
661              
662             =cut
663              
664             sub register_beat {
665              
666             my $self = shift;
667             my ($secs, $handler) = @_;
668             $self->_debug("registering beat $handler");
669             push @{$self->{heartbeats}->{$secs}}, $handler;
670              
671             }
672              
673              
674             =item start()
675              
676             Start a process loop. This has a similar effect to something
677             like
678              
679             while (1) { $c->process(1) }
680              
681             except that it also maintains a heartbeat (see
682             register_beat()).
683              
684             =cut
685              
686             sub start {
687              
688             my $self = shift;
689             $SIG{ALRM} = sub { $self->_heartbeat(); alarm BEAT; };
690             alarm BEAT ;
691             1 while $self->process(1);
692             }
693              
694              
695             sub connected {
696              
697             my $self = shift;
698             $self->{connected};
699              
700             }
701              
702              
703             sub _checkConnected {
704              
705             my $self = shift;
706             croak "No connection/stream established!" unless $self->connected;
707              
708             }
709              
710              
711             sub _heartbeat {
712              
713             my $self = shift;
714             $self->{beatcount} += BEAT;
715             foreach my $beat (keys %{$self->{heartbeats}}) {
716             if ($self->{beatcount} % $beat == 0) {
717             $_->() foreach @{$self->{heartbeats}->{$beat}};
718             }
719             }
720             }
721              
722             =back
723              
724             =head1 SEE ALSO
725              
726             Jabber::NodeFactory, Jabber::NS
727              
728             =head1 AUTHOR
729              
730             DJ Adams
731              
732             =head1 VERSION
733              
734             early
735              
736             =head1 COPYRIGHT
737              
738             This module is free software; you can redistribute it and/or modify
739             it under the same terms as Perl itself.
740              
741             =cut
742             1;