Import Debian changes 1.24-1
[cascardo/sendxmpp.git] / sendxmpp
1 #!/usr/bin/perl -w
2
3 eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
4 if 0; # not running under some shell
5
6 #
7 # script to send message using xmpp (aka jabber),
8 # somewhat resembling mail(1)
9 #
10 # Author:     Dirk-Jan C. Binnema <djcb AT djcbsoftware.nl>
11 # Maintainer: Lubomir Host <lubomir.host@gmail.com>
12 # Copyright (c) 2004 - 2005 Dirk-Jan C. Binnema
13 # Copyright (c) 2006 - 2014 Lubomir Host
14 #
15 # Homepage: http://sendxmpp.hostname.sk
16 #
17 # Released under the terms of the GNU General Public License v2
18 #
19
20 use Authen::SASL qw(Perl); # authentication broken if Authen::SASL::Cyrus module installed
21 use Net::XMPP;
22 use Net::Domain;
23 use Getopt::Long;
24
25 use strict;
26
27 use open ':utf8';
28 use open ':std';
29
30 # subroutines decls
31 sub xmpp_login($$$$$$$$$$$$);
32 sub xmpp_send ($$$$);
33 sub xmpp_send_raw_xml($$);
34 sub xmpp_send_message($$$$$$);
35 sub xmpp_send_chatroom_message($$$$$);
36 sub xmpp_logout($);
37 sub xmpp_check_result;
38 sub parse_cmdline();
39 sub error_exit;
40 sub debug_print;
41 sub read_config_file($);
42 sub push_hash($$);
43 sub terminate();
44 sub main();
45
46 my # MakeMaker
47 $VERSION        = 1.24;
48 my $RESOURCE = 'sendxmpp';
49 my $VERBOSE  = 0;
50 my $DEBUG    = 0;
51 # http://tools.ietf.org/html/rfc3921#section-2  section 2.1.1 - Types of Message
52 my @suppported_message_types    = qw( chat error groupchat headline );
53 my $message_type                                = 'chat'; # default message type
54
55 # start!
56 &main;
57
58 #
59 # main: main routine
60 #
61 sub main () { # {{{
62
63     my $cmdline = parse_cmdline();
64
65     $| = 1; # no output buffering
66
67     $DEBUG   = 1 if ($$cmdline{'debug'});
68     $VERBOSE = 1 if ($$cmdline{'verbose'});
69
70     my $config = read_config_file ($$cmdline{'file'})
71         unless ($$cmdline{'sso'} || ($$cmdline{'username'} && $$cmdline{'password'}));
72
73     # login to xmpp
74     my $cnx =  xmpp_login (
75                 $$cmdline{'jserver'}  || $$config{'jserver'},
76                 $$cmdline{'port'}     || $$config{'port'} || ($$cmdline{'ssl'} ? 5223 : 5222),
77                 $$cmdline{'username'} || $$config{'username'},
78                 $$cmdline{'password'} || $$config{'password'},
79                 $$cmdline{'component'}|| $$config{'component'},
80                 $$cmdline{'resource'},
81                 $$cmdline{'tls'} || $$config{'tls'} || 0,
82                 $$cmdline{'no-tls-verify'} || $$config{'no-tls-verify'},
83                 $$cmdline{'tls-ca-path'} || $$config{'tls-ca-path'} || '',
84                 $$cmdline{'ssl'},
85                 $$cmdline{'debug'},
86                 $$cmdline{'sso'}
87         ) or error_exit("cannot login: $!");
88
89
90     # read message from STDIN or from -m/--message parameter
91     if (!$$cmdline{interactive}) {
92                 # the non-interactive case
93                 my $txt;
94                 my $message = $$cmdline{'message'};
95                 if ($message) {
96                         open (MSG, "<$message")
97                                 or error_exit ("cannot open message file '$message': $!");
98                         while (<MSG>) { $txt .= $_ };
99                         close(MSG);
100                 }
101                 else {
102                         $txt .= $_ while (<STDIN>);
103                 }
104
105                 xmpp_send ($cnx,$cmdline,$config,$txt);
106
107     }
108         else {
109                 # the interactive case, read stdin line by line
110
111                 # deal with TERM
112                 $main::CNX = $cnx;
113                 $SIG{INT}=\&terminate;
114
115                 # line by line...
116                 while (<STDIN>) {
117                         chomp;
118                         xmpp_send ($cnx,$cmdline,$config,$_);
119                 }
120         }
121
122         xmpp_logout($cnx);
123         exit 0;
124 } # }}}
125
126 #
127 # read_config_file: read the configuration file
128 # input: filename
129 # output: hash with 'user', 'jserver' and 'password' keys
130 #
131 sub read_config_file ($) { # {{{
132
133     # check permissions
134     my $cfg_file = shift;
135     error_exit ("cannot read $cfg_file: $!")
136         unless (-r $cfg_file);
137     my $owner  = (stat _ )[4];
138     error_exit ("you must own $cfg_file")
139       unless ($owner == $>);
140     my $mode = (stat _ )[2] & 07777;
141     error_exit ("$cfg_file must not be accessible by others")
142       if ($mode & 0077);
143
144     open (CFG,"<$cfg_file")
145       or error_exit("cannot open $cfg_file for reading: $!");
146
147     my %config;
148     my $line = 0;
149         while (<CFG>) {
150
151                 ++$line;
152
153                 next if (/^\s*$/);     # ignore empty lines
154                 next if (/^\s*\#.*/);  # ignore comment lines
155
156                 #s/\#.*$//; # ignore comments in lines
157
158                 if (/^([a-z]+):\s*(.*)$/) {
159                         $config{$1} = $2;
160                 }
161                 # Hugo van der Kooij <hvdkooij AT vanderkooij.org> has account with '#' as username
162                 elsif (/([\.\w_#-]+)@([-\.\w:;]+)\s+(\S+)\s*(\S+)?$/) {
163                         %config = (
164                                 'username'      => $1,
165                                 'jserver'       => $2,
166                                 'port'          => 0,
167                                 'password'      => $3,
168                                 'component'     => $4,
169                         );
170
171                 }
172                 else {
173                         close CFG;
174                         error_exit ("syntax error in line $line of $cfg_file");
175                 }
176
177                 # account with weird port number
178                 if (defined($config{'jserver'}) and $config{'jserver'}  =~ /(.*):(\d+)/) {
179                         $config{'jserver'}      = $1;
180                         $config{'port'}         = $2;
181                 }
182
183                 # account with specific connection host
184                 if (defined($config{'jserver'}) and $config{'jserver'}  =~ /(.*);([-\.\w]+)/) {
185                         $config{'jserver'}      = $2;
186                         $config{'username'}     .= "\@$1" unless $config{'component'};
187                 }
188         }
189
190     close CFG;
191
192     error_exit ("no correct config found in $cfg_file")
193       unless (scalar(%config));
194
195           if ($DEBUG || $VERBOSE) {
196                   while (my ($key,$val) = each %config) {
197                           debug_print ("config: '$key' => '$val'");
198                   }
199           }
200
201     return \%config;
202 } # }}}
203
204 #
205 # parse_cmdline: parse commandline options
206 # output: hash with commandline options
207 #
208 sub parse_cmdline () { # {{{
209
210     usage() unless (scalar(@ARGV));
211
212         my ($subject, $file, $resource, $jserver, $port, $username, $password, $sso, $component, 
213                 $message, $chatroom, $headline, $debug, $tls, $ssl,
214                 $no_tls_verify, $tls_ca_path,
215                 $interactive, $help, $raw, $verbose
216         );
217     $debug = 0;
218     my $res = GetOptions (
219                 'subject|s=s'           => \$subject,
220                 'file|f=s'                      => \$file,
221                 'resource|r=s'          => \$resource,
222                 'jserver|j=s'           => \$jserver,
223                 'component|o=s'         => \$component,
224                 'username|u=s'          => \$username,
225                 'password|p=s'          => \$password,
226                 'sso'                           => \$sso,
227                 'message|m=s'           => \$message,
228                 'headline|l'            => \$headline,
229                 'message-type=s'        => \$message_type,
230                 'chatroom|c'            => \$chatroom,
231                 'tls|t'                         => \$tls,
232                 'no-tls-verify|n'       => \$no_tls_verify,
233                 'tls-ca-path|a=s'       => \$tls_ca_path,
234                 'ssl|e'                         => \$ssl,
235                 'interactive|i'         => \$interactive,
236                 'help|usage|h'          => \$help,
237                 'debug|d:i'                     => sub { $debug = $_[1] ? $_[1] : $debug + 1 },
238                 'raw|w'                         => \$raw,
239                 'verbose|v'                     => \$verbose
240         );
241
242         usage () if ($help);
243
244         my @rcpt = @ARGV;
245
246         if (defined($raw) && scalar(@rcpt) > 0) {
247                 error_exit("You must give a recipient or --raw (but not both)");
248         }
249         if ($raw && $subject) {
250                 error_exit("You cannot specify a subject in raw XML mode");
251         }
252         if ($raw && $chatroom) {
253                 error_exit("The chatroom option is pointless in raw XML mode");
254         }
255
256         if ($message && $interactive) {
257                 error_exit("Cannot have both -m (--message) and -i (--interactive)");
258         }
259
260         if (scalar(grep { $message_type eq $_ } @suppported_message_types) == 0) {
261                 error_exit("Unsupported message type '$message_type'");
262         }
263         
264         if ($ssl && $tls) {
265             error_exit("Connect securely wether using -e (--ssl) or -t (--tls)");
266         }
267
268         if ($sso && $username) {
269                 error_exit("When using --sso, user should not be specified");
270         }
271
272         if ($headline) {
273                 # --headline withouth --message-type
274                 if ($message_type eq 'message' or $message_type eq 'chat') {
275                         $message_type = 'headline'
276                 }
277                 else {
278                         error_exit("Options --headline and --message-type are mutually exclusive");
279                 }
280         }
281
282         if ($jserver && $jserver =~ /(.*):(\d+)/) {
283                 $jserver = $1;
284                 $port    = $2;
285         }
286
287         my %dict = (
288                 'subject'               => ($subject  or ''),
289                 'message'               => ($message or ''),
290                 'resource'              => ($resource or $RESOURCE),
291                 'jserver'               => ($jserver or ''),
292                 'component'             => ($component or ''),
293                 'port'                  => ($port or 0),
294                 'username'              => ($username or ''),
295                 'password'              => ($password or ''),
296                 'sso'                   => ($sso or 0),
297                 'chatroom'              => ($chatroom or 0),
298                 'message-type'  => $message_type,
299                 'interactive'   => ($interactive or 0),
300                 'tls'                   => ($tls or 0),
301                 'no-tls-verify' => ($no_tls_verify or 0),
302                 'tls-ca-path'   => ($tls_ca_path or ''),
303                 'ssl'                   => ($ssl or 0),
304                 'debug'                 => ($debug or 0),
305                 'verbose'               => ($verbose or 0),
306                 'raw'                   => ($raw or 0),
307                 'file'                  => ($file or ($ENV{'HOME'}.'/.sendxmpprc')),
308                 'recipient'             => \@rcpt
309         );
310
311    if ($DEBUG || $VERBOSE) {
312        while (my ($key,$val) = each %dict) {
313            debug_print ("cmdline: '$key' => '$val'");
314        }
315    }
316
317    return \%dict;
318 } # }}}
319
320 #
321 # xmpp_login: login to the xmpp (jabber) server
322 # input: hostname,port,username,password,resource,tls,ssl,debug
323 # output: an XMPP connection object
324 #
325 sub xmpp_login ($$$$$$$$$$$$) { # {{{
326
327     my ($host, $port, $user, $pw, $comp, $res, $tls, $no_tls_verify, $tls_ca_path, $ssl, $debug, $sso) = @_;
328     my $cnx = new Net::XMPP::Client(debuglevel=>$debug);
329     error_exit "could not create XMPP client object: $!"
330         unless ($cnx);
331
332         my $ssl_verify = 0x01;
333         if ($no_tls_verify) { $ssl_verify = 0x00; }
334         debug_print "ssl_verify: $ssl_verify";
335
336         debug_print "tls_ca_path: $tls_ca_path";
337
338     my @res;
339         my $arghash = {
340                 hostname                => $host,
341                 port            => $port,
342                 tls                             => $tls,
343                 ssl_verify              => $ssl_verify,
344                 ssl_ca_path             => $tls_ca_path,
345                 ssl             => $ssl,
346                 connectiontype  => 'tcpip',
347                 componentname   => $comp
348         };
349
350     if ($sso) {
351         $user = join('@', scalar getpwuid($<), Net::Domain::hostdomain());
352         debug_print "using SSO user $user";
353     }
354
355     # use the xmpp domain as the host and enable SRV lookups
356     if (!$host) {
357         if ($user =~ /@(.*)/) {
358             $arghash->{hostname} = $host = $1;
359             $arghash->{srv} = 1;
360             debug_print "enabling SRV lookups";
361             
362         } else {
363             error_exit "unable to determine a host to connect to (no cmdline, no config, no SRV possible)";
364         }           
365
366     }
367
368         delete $arghash->{port} unless $port; 
369         if ($arghash->{port}) {
370                 @res = $cnx->Connect(%$arghash);
371                 error_exit ("Could not connect to '$host' on port $port: ".($cnx->GetErrorCode()||$@)) unless @res;
372         } else {
373                 @res = $cnx->Connect(%$arghash);
374                 error_exit ("Could not connect to server '$host': ".($cnx->GetErrorCode()||$@)) unless @res;
375         }
376
377     xmpp_check_result("Connect",\@res,$cnx);
378
379         if ($comp) {
380                 my $sid = $cnx->{SESSION}->{id};
381                 $cnx->{STREAM}->{SIDS}->{$sid}->{hostname} = $comp
382         }
383
384     @res = $cnx->AuthSend(#'hostname' => $host,
385                           'username' => $user,
386                           'password' => $pw,
387                           'resource' => $res);
388     xmpp_check_result('AuthSend',\@res,$cnx);
389
390     return $cnx;
391 } # }}}
392
393 #
394 # xmmp_send: send the message, determine from cmdline
395 # whether it's to individual or chatroom
396 #
397 sub xmpp_send ($$$$) { # {{{
398
399         my ($cnx, $cmdline, $config, $txt) = @_;
400
401         unless ($$cmdline{'chatroom'}) {
402         unless ($$cmdline{'raw'}) {
403                         map {
404                                 xmpp_send_message ($cnx,
405                                         $_, #$$cmdline{'recipient'},
406                                         $$cmdline{'component'} || $$config{'component'},
407                                         $$cmdline{'subject'},
408                                         $$cmdline{'message-type'},
409                                         $txt)
410                         } @{$$cmdline{'recipient'}};
411         }
412                 else {
413                         xmpp_send_raw_xml ($cnx, $txt);
414         }
415         }
416         else {
417                 map {
418                         xmpp_send_chatroom_message ($cnx,
419                                 $$cmdline{'resource'},
420                                 $$cmdline{'subject'},
421                                 $_, # $$cmdline{'recipient'},
422                                 $txt)
423                 } @{$$cmdline{'recipient'}};
424         }
425 } # }}}
426
427 #
428 # xmpp_send_raw_xml: send a raw XML packet
429 # input: connection,packet
430 #
431 sub xmpp_send_raw_xml ($$) { # {{{
432
433     my ($cnx,$packet) = @_;
434
435     # for some reason, Send does not return anything
436     $cnx->Send($packet);
437     xmpp_check_result('Send',0,$cnx);
438 } # }}}
439
440 #
441 # xmpp_send_message: send a message to some xmpp user
442 # input: connection,recipient,subject,msg
443 #
444 sub xmpp_send_message ($$$$$$) { # {{{
445
446     my ($cnx, $rcpt, $comp, $subject, $message_type, $msg) = @_;
447
448     # for some reason, MessageSend does not return anything
449         # mimeit01@xmpp.hs-esslingen.de: if $comp IS set, AND the rcpt DOESN'T contain an @, then @comp is added
450     $cnx->MessageSend('to'      => $rcpt . ( ($comp && index($rcpt, "@") == -1) ? "\@$comp" : '' ),
451                 'type'          => $message_type,
452                 'subject'       => $subject,
453                 'body'          => $msg);
454
455     xmpp_check_result('MessageSend',0,$cnx);
456 } # }}}
457
458 #
459 # xmpp_send_chatroom_message: send a message to a chatroom
460 # input: connection,resource,subject,recipient,message
461 #
462 sub xmpp_send_chatroom_message ($$$$$) { # {{{
463
464     my ($cnx,$resource,$subject,$rcpt,$msg) =  @_;
465
466     # set the presence
467     my $pres = new Net::XMPP::Presence;
468     my $res = $pres->SetTo("$rcpt/$resource");
469
470     $cnx->Send($pres);
471
472     # create/send the message
473     my $groupmsg = new Net::XMPP::Message;
474     $groupmsg->SetMessage(to      => $rcpt,
475                           body    => $msg,
476                           type    => 'groupchat');
477
478     $res = $cnx->Send($groupmsg);
479     xmpp_check_result ('Send',$res,$cnx);
480
481     # leave the group
482     $pres->SetPresence (Type=>'unavailable',To=>$rcpt);
483 } # }}}
484
485 #
486 # xmpp_logout: log out from the xmpp server
487 # input: connection
488 #
489 sub xmpp_logout($) { # {{{
490
491     # HACK
492     # messages may not be received if we log out too quickly...
493     sleep 1;
494
495     my $cnx = shift;
496     $cnx->Disconnect();
497     xmpp_check_result ('Disconnect',0); # well, nothing to check, really
498 } # }}}
499
500 #
501 # xmpp_check_result: check the return value from some xmpp function execution
502 # input: text, result, [connection]
503 #
504 sub xmpp_check_result { # {{{
505     my ($txt, $res, $cnx)=@_;
506
507     error_exit ("Error '$txt': result undefined")
508         unless (defined $res);
509
510     # res may be 0
511         if ($res == 0) {
512                 debug_print "$txt";
513                 # result can be true or 'ok'
514         }
515         elsif ((@$res == 1 && $$res[0]) || $$res[0] eq 'ok') {
516                 debug_print "$txt: " .  $$res[0];
517                 # otherwise, there is some error
518         }
519         else {
520                 my $errmsg = $cnx->GetErrorCode() || '?';
521                 error_exit ("Error '$txt': " . join (': ',@$res) . "[$errmsg]", $cnx);
522         }
523 } # }}}
524
525 #
526 # terminate; exit the program upon TERM sig reception
527 #
528 sub terminate () { # {{{
529     debug_print "caught TERM";
530     xmpp_logout($main::CNX);
531     exit 0;
532 } # }}}
533
534 #
535 # debug_print: print the data if defined and DEBUG || VERBOSE is TRUE
536 # input: [array of strings]
537 #
538 sub debug_print { # {{{
539     print STDERR "sendxmpp: " . (join ' ', @_) . "\n"
540         if (@_ && ($DEBUG ||$VERBOSE));
541 } # }}}
542
543 #
544 # error_exit: print error message and exit the program
545 #             logs out if there is a connection
546 # input: error, [connection]
547 #
548 sub error_exit { # {{{
549
550     my ($err,$cnx) = @_;
551     print STDERR "$err\n";
552     xmpp_logout ($cnx)
553         if ($cnx);
554
555     exit 1;
556 } # }}}
557
558 #
559 # usage: print short usage message and exit
560 #
561 sub usage () { # {{{
562
563     print STDERR
564         "sendxmpp version $VERSION\n" .
565         "Copyright (c) 2004 - 2005 Dirk-Jan C. Binnema\n" .
566         "Copyright (c) 2006 - 2014 Lubomir Host\n" .
567         "usage: sendxmpp [options] <recipient1> [<recipient2> ...]\n" .
568         "or refer to the the sendxmpp manpage\n";
569
570     exit 0;
571 } # }}}
572
573 #
574 # the fine manual
575 #
576 =pod
577
578 =head1 NAME
579
580 sendxmpp - send xmpp messages from the commandline.
581
582 =head1 SYNOPSIS
583
584 sendxmpp [options] <recipient1> [<recipient2> ...]
585
586 sendxmpp --raw [options]
587
588 =head1 DESCRIPTION
589
590 sendxmpp is a program to send XMPP (Jabber) messages from the commandline, not
591 unlike L<mail(1)>. Messages can be sent both to individual recipients and chatrooms.
592
593 =head1 OPTIONS
594
595 =over
596
597 =item B<-f>,B<--file> I<file>
598
599 Use I<file> configuration file instead of F<~/.sendxmpprc>
600
601 =item B<-u>,B<--username> I<user>
602
603 Use I<user> instead of the one in the configuration file
604
605 =item B<-p>,B<--password> I<password>
606
607 Use I<password> instead of the one in the configuration file
608
609 =item B<--sso> 
610
611 Instead of specifying username or password, attempt to use system level SSO (e.g. kerberos) if supported.
612
613 =item B<-j>,B<--jserver> I<server>
614
615 Use jabber I<server> instead of the one in the configuration file.
616
617 =item B<-o>,B<--component> I<componentname>
618
619 Use componentname in connect call. Seems needed for Google talk.
620
621 =item B<-r>,B<--resource> I<res>
622
623 Use resource I<res> for the sender [default: 'sendxmpp']; when sending to a chatroom, this determines the 'alias'
624
625 =item B<-t>,B<--tls>
626
627 Connect securely, using TLS
628
629 =item B<-e>,B<--ssl>
630
631 Connect securely, using SSL
632
633 =item B<-n>,B<--no-tls-verify>
634
635 Deactivate the verification of SSL certificates. Better way is to use parameter B<--tls-ca-path> with the needed path to CA certificates.
636
637 =item B<-a>,B<--tls-ca-path>
638
639 Path to your custom CA certificates, so you can verificate SSL certificates during connecting.
640
641 =item B<-l>,B<--headline>
642
643 Backward compatibility option. You should use B<--message-type=headline> instead. Send a headline type message (not stored in offline messages)
644
645 =item B<--messages-type>
646
647 Set type of message. Supported types are: B<message chat headline>. Default message type is B<message>. Headline type message can be set also with B<--headline> option, see B<--headline>
648
649 =item B<-c>,B<--chatroom>
650
651 Send the message to a chatroom
652
653 =item B<-s>,B<--subject> I<subject>
654
655 Set the subject for the message to I<subject> [default: '']; when sending to a chatroom, this will set the subject for the chatroom
656
657 =item B<-m>,B<--message> I<message>
658
659 Read the message from I<message> (a file) instead of stdin
660
661 =item B<-i>,B<--interactive>
662
663 Work in interactive mode, reading lines from stdin and sending the one-at-time
664
665 =item B<-w>,B<--raw>
666
667 Send raw XML message to jabber server
668
669 =item B<-v>,B<--verbose>
670
671 Give verbose output about what is happening
672
673 =item B<-h>,B<--help>,B<--usage>
674
675 Show a 'Usage' message
676
677 =item B<-d>,B<--debug>
678
679 Show debugging info while running. B<WARNING>: This will include passwords etc. so be careful with the output! Specify multiple times to increase debug level.
680
681 =back
682
683 =head1 CONFIGURATION FILE
684
685 You may define a 'F<~/.sendxmpprc>' file with the necessary data for your
686 xmpp-account. Since version 1.24 the following format is supported:
687
688     username: I<your_username>
689     jserver: I<jabber_server>
690     port: I<jabber_port>
691     password: I<your_jabber_password>
692     component: I<optional_component_name>
693
694
695 Example for Google Talk servers:
696
697     username: I<lubomir.host>
698     jserver: I<talk.google.com>
699     password: I<my-secure-password>
700     component: I<gmail.com>
701
702 With version 1.23 and older only one-line format is supported:
703
704 =over
705
706 I<user>@I<server> I<password> I<componentname>
707
708 =back
709
710 e.g.:
711
712     # my account
713     alice@jabber.org  secret
714
715 ('#' and newlines are allowed like in shellscripts). You can add a I<host> (or IP address) if it is different from the I<server> part of your JID:
716
717     # account with specific connection host
718     alice@myjabberserver.com;foo.com secret
719
720 You can also add a I<port> if it is not the standard XMPP port:
721
722     # account with weird port number
723     alice@myjabberserver.com:1234 secret
724
725 Of course, you may also mix the two:
726
727     # account with a specific host and port
728     alice@myjabberserver.com;foo.com:1234 secret
729
730 B<NOTE>: for your security, sendxmpp demands that the configuration
731 file is owned by you and readable only to you (permissions 600).
732
733 =head1 EXAMPLE
734
735    $ echo "hello bob!" | sendxmpp -s hello someone@jabber.org
736
737      or to send to a chatroom:
738
739    $ echo "Dinner Time" | sendxmpp -r TheCook --chatroom test2@conference.jabber.org
740
741      or to send your system logs somewhere, as new lines appear:
742
743    $ tail -f /var/log/syslog | sendxmpp -i sysadmin@myjabberserver.com
744
745      NOTE: be careful not the overload public jabber services
746
747 =head1 SEE ALSO
748
749 Documentation for the L<Net::XMPP> module
750
751 The jabber homepage: L<http://www.jabber.org/>
752
753 The sendxmpp homepage: L<http://sendxmpp.hostname.sk>
754
755 =head1 AUTHOR
756
757 sendxmpp has been written by Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>, and uses
758 the L<Net::XMPP> modules written by Ryan Eatmon. Current maintainer is
759 Lubomir Host <lubomir.host@gmail.com>, L<http://blog.hostname.sk>
760
761 =cut
762 # vim: fdm=marker fdl=0 fdc=3