Import Upstream version 0.0.5
[cascardo/sendxmpp.git] / sendxmpp
1 #!/usr/bin/perl -w
2 #-*-mode:perl-*-
3 #Time-stamp: <2004-12-01 14:52:47 (djcb)>
4
5 # script to send message using xmpp (aka jabber), 
6 #   somewhat resembling mail(1)
7
8 # author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
9 # copyright (c) 2004, Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
10 #
11 # released under the terms of the GNU General Public License v2
12
13 use Net::XMPP;
14 use Getopt::Long;
15 use strict;
16
17
18 # subroutines decls
19 sub xmpp_login($$$$$$);
20 sub xmpp_send_message($$$$);
21 sub xmpp_send_chatroom_message($$$$$);
22 sub xmpp_logout($);
23 sub xmpp_check_result;
24 sub parse_cmdline();
25 sub error_exit;
26 sub debug_print;
27 sub read_config_file($);
28 sub push_hash($$);
29 sub main ();
30
31
32 my # MakeMaker
33 $VERSION     = '0.0.5';
34 my $RESOURCE = 'sendxmpp';
35 my $VERBOSE  = 0;
36 my $DEBUG    = 0;
37
38              
39 # start!
40 &main;
41
42
43 #
44 # main: main routine
45 #
46 sub main () {
47
48     my $cmdline = parse_cmdline();
49     
50     $DEBUG   = 1 if ($$cmdline{'debug'});
51     $VERBOSE = 1 if ($$cmdline{'verbose'});
52
53     my $config = read_config_file ($$cmdline{'file'})
54         unless ($$cmdline{'jserver'} && $$cmdline{'username'} && $$cmdline{'password'});        
55     
56     # login to xmpp
57     my $cnx =  xmpp_login ($$cmdline{'jserver'}  || $$config{'jserver'},
58                            $$cmdline{'username'} || $$config{'username'},
59                            $$cmdline{'password'} || $$config{'password'},
60                            $$cmdline{'resource'},
61                            $$cmdline{'tls'},
62                            $$cmdline{'debug'})
63         or error_exit("cannot login: $!");
64     
65     
66     unless ($$cmdline{'chatroom'}) {
67         xmpp_send_message ($cnx,
68                            $$cmdline{'recipient'},
69                            $$cmdline{'subject'},
70                            $$cmdline{'message'});
71     } else {
72         xmpp_send_chatroom_message ($cnx,
73                                  $$cmdline{'resource'},
74                                  $$cmdline{'subject'},
75                                  $$cmdline{'recipient'},
76                                  $$cmdline{'message'});
77     }
78
79     xmpp_logout($cnx);
80     exit(0);
81 }
82
83
84 #
85 # read_config_file: read the configuration file
86 # input: filename
87 # output: hash with 'user', 'jserver' and 'password' keys
88 #
89 sub read_config_file ($) {
90    
91     # check permissions
92     my $cfg_file = shift;
93     error_exit ("cannot read $cfg_file: $!") 
94         unless (-r $cfg_file);    
95     my $owner  = (stat($cfg_file))[4];
96     error_exit ("you must own $cfg_file")
97         unless ($owner == $>); 
98     my $mode = (stat($cfg_file))[2] & 07777;
99     error_exit ("$cfg_file must have mode 0600")
100         unless ($mode == 0600);
101     
102     open (CFG,"<$cfg_file")
103         or error_exit("cannot open $cfg_file for reading: $!");
104
105     my %config;
106     my $line = 0;
107     while (<CFG>) {
108         
109         ++$line;
110         
111         next if (/^\s*$/);     # ignore empty lines
112         next if (/^\s*\#.*/);  # ignore comment lines
113         
114         s/\#.*$//; # ignore comments in lines
115         
116         if (/([-\.\w]+)@([-\.\w]+)\s+(\S+)\s*$/) {
117             %config = ('username'=>$1,
118                        'jserver'=>$2, 
119                        'password'=>$3);
120
121         } else {
122             close(CFG);
123             error_exit ("syntax error in line $line of $cfg_file");
124         }
125     }
126
127     close(CFG);
128     
129     error_exit ("no correct config found in $cfg_file") 
130         unless (scalar(%config));       
131
132     if ($DEBUG || $VERBOSE) {
133         while (my ($key,$val) = each %config) {
134             debug_print ("config: '$key' => '$val'");
135         }
136     }       
137
138     return \%config;               
139 }
140
141
142
143 #
144 # parse_cmdline: parse commandline options
145 # output: hash with commandline options
146 #
147 sub parse_cmdline () {
148     
149    usage() unless (scalar(@ARGV));
150             
151     my ($subject,$file,$resource,$jserver,$username,$password,
152         $message,$chatroom,$debug,$tls,$help,$verbose);
153     my $res = GetOptions ('subject|s=s'    => \$subject,
154                           'file|f=s'       => \$file,
155                           'resource|r=s'   => \$resource,
156                           'jserver|j=s'    => \$jserver,
157                           'username|u=s'   => \$username,
158                           'password|p=s'   => \$password,
159                           'message|m=s'    => \$message,
160                           'chatroom|c'     => \$chatroom,
161                           'tls|t'          => \$tls,
162                           'help|usage|h'   => \$help,
163                           'debug|d'        => \$debug,
164                           'verbose|v'      => \$verbose);
165     usage () 
166         if ($help);   
167    
168     my $rcpt = $ARGV[0]
169         or error_exit("no recipient specified");
170
171     # read message from STDIN or or from -m/--message parameter
172     my $txt;
173     if ($message) {
174         open (MSG, "<$message")
175             or error_exit ("cannot open message file '$message': $!");
176         while (<MSG>) {$txt.=$_};
177         close(MSG);
178     } else {
179         while (<STDIN>) {$txt.=$_};
180     }
181     
182     my %dict = ('subject'    => ($subject  or ''),
183                 'resource'   => ($resource or $RESOURCE),
184                 'jserver'    => ($jserver or ''),
185                 'username'   => ($username or ''),
186                 'password'   => ($password or ''),
187                 'chatroom'   => ($chatroom or 0),
188                 'tls'        => ($tls or 0),
189                 'debug'      => ($debug or 0),
190                 'message'    => ($txt or ''),
191                 'verbose'    => ($verbose or 0),
192                 'file'       => ($file or ($ENV{'HOME'}.'/.sendxmpprc')),
193                 'recipient'  => $rcpt);
194
195    if ($DEBUG || $VERBOSE) {
196        while (my ($key,$val) = each %dict) {
197            debug_print ("cmdline: '$key' => '$val'");
198        }
199    }        
200     
201    return \%dict;    
202 }
203
204
205
206 #
207 # xmpp_login: login to the xmmp (jabber) server
208 # input: hostname,username,password,resource,tls,debug
209 # output: an XMPP connection object
210 #
211 sub xmpp_login ($$$$$$) {
212
213     my ($host,$user,$pw,$res,$tls,$debug) = @_;
214     my $cnx = new Net::XMPP::Client(debuglevel=>($debug?2:0));
215     error_exit ("could not create XMPP client object: $!")
216         unless ($cnx);    
217
218     my @res = $cnx->Connect(hostname=>$host,tls=>$tls);
219     xmpp_check_result("Connect",\@res,$cnx);
220
221     
222     @res = $cnx->AuthSend('hostname'=>$host,
223                           'username'=>$user,
224                           'password'=>$pw,
225                           'resource'=>$res);
226     xmpp_check_result('AuthSend',\@res,$cnx);
227         
228     #@res = $cnx->PresenceSend(type=>'unavailable');
229     #mpp_check_result("PresenceSend",\@res,$cnx);
230     
231     return $cnx;    
232 }
233
234
235 #
236 # xmmp_send_message: send a message to some xmmp user
237 # input: connection,recipient,subject,msg
238 #
239 sub xmpp_send_message ($$$$) {
240     
241     my ($cnx,$rcpt,$subject,$msg) = @_;
242  
243     # for some reason, MessageSend does not return anything
244     $cnx->MessageSend('to'=>$rcpt,
245                       'subject'=>$subject,
246                       'body'=>$msg);
247     
248     xmpp_check_result('MessageSend',0,$cnx);
249 }
250     
251     
252 #
253 # xmpp_send_chatroom_message: send a message to a chatroom
254 # input: connection,resource,subject,recipient,message
255 #
256 sub xmpp_send_chatroom_message ($$$$$) {
257
258     my ($cnx,$resource,$subject,$rcpt,$msg) =  @_;
259     
260     # set the presence
261     my $pres = new Net::XMPP::Presence;
262     my $res = $pres->SetTo("$rcpt/$resource");
263
264     $cnx->Send($pres); 
265
266     # create/send the message
267     my $groupmsg = new Net::XMPP::Message;
268     $groupmsg->SetMessage(to=>$rcpt, 
269                           body=>$msg,
270                           subject=>$subject,
271                           type=>'groupchat');
272
273     $res = $cnx->Send($groupmsg);
274     xmpp_check_result ('Send',$res,$cnx); 
275     
276     # leave the group
277     $pres->SetPresence (Type=>'unavailable',To=>$rcpt);
278 }
279
280
281 #
282 # xmmp_logout: log out from the xmpp server
283 # input: connection
284 #
285 sub xmpp_logout($) {
286     
287     # HACK
288     # messages may not be received if we log out too quickly...
289     sleep 1; 
290     
291     my $cnx = shift;
292     $cnx->Disconnect();
293     xmpp_check_result ('Disconnect',0); # well, nothing to check, really
294 }
295
296
297
298 #
299 # xmpp_check_result: check the return value from some xmpp function execution
300 # input: text, result, [connection]                   
301 #
302 sub xmpp_check_result {
303
304     my ($txt,$res,$cnx)=@_;
305     
306     error_exit ("Error '$txt': result undefined")
307         unless (defined $res);  
308
309     # res may be 0
310     if ($res == 0) {
311         debug_print "$txt";
312     # result can be true or 'ok' 
313     } elsif ((@$res == 1 && $$res[0]) || $$res[0] eq 'ok') {    
314         debug_print "$txt: " .  $$res[0];
315     # otherwise, there is some error
316     } else {    
317         my $errmsg = $cnx->GetErrorCode() || '?';
318         error_exit ("Error '$txt': " . join (': ',@$res) . "[$errmsg]", $cnx);
319     }
320 }
321
322
323 #
324 # debug_print: print the data if defined and DEBUG || VERBOSE is TRUE
325 # input: [array of strings]
326 #
327 sub debug_print {
328     print STDERR "sendxmpp: " . (join ' ',@_) . "\n"
329         if (@_ && ($DEBUG ||$VERBOSE));
330 }
331
332
333 #
334 # error_exit: print error message and exit the program
335 #             logs out if there is a connection 
336 # input: error, [connection]
337 #
338 sub error_exit {
339     
340     my ($err,$cnx) = @_;
341     print STDERR "$err\n";   
342     xmpp_logout ($cnx) 
343         if ($cnx);
344  
345     exit 1;
346 }
347
348
349 #
350 # usage: print short usage message and exit
351 #
352 sub usage () {
353    
354     print 
355         "sendxmpp version $VERSION, (c) 2004 Dirk-Jan C. Binnema\n" .
356         "usage: sendxmpp [options] <recipient>\n" .
357         "or refer to the the sendxmpp manpage\n";
358     
359     exit 0;
360 }
361
362
363
364 #
365 # the fine manual
366 #
367 =head1 NAME
368
369 sendxmpp - send xmpp messages from the commandline.
370
371 =head1 SYNOPSIS
372
373 sendxmpp [options] <recipient>
374
375 =head1 DESCRIPTION
376
377 sendxmpp is a program to send XMPP (Jabber) messages from the commandline, not
378 unlike L<mail(1)>. Messages can be sent both to individual recipients and chatrooms.
379
380 the recipient is either another jabber-account or a jabber-chatroom (use '-c' to tell 
381 sendxmpp it's a chatroom)
382
383 =head1 OPTIONS
384
385 B<-f>,B<--file> <file>
386 use <file> configuration file instead of ~/.sendxmpprc
387
388 B<-u>,B<--username> <user>
389 use <user> instead of the one in the configuration file
390
391 B<-p>,B<--password> <password>
392 use <password> instead of the one in the configuration file
393
394 B<-j>,B<--jserver> <server>
395 use jabber server <server> instead of the one in the configuration file
396
397 B<-r>,B<--resource> <res>
398 use resource <res> for the sender [default: 'sendxmpp']; when sending to a chatroom, this determines the 'alias'
399
400 B<-t>,B<--tls>
401 connect securely, using TLS
402
403 B<-c>,B<--chatroom>
404 send the message to a chatroom 
405
406 B<-s>,B<--subject> <subject> 
407 set the subject for the message to <subject> [default: '']; when sending to a chatroom,
408 this will set the subject for the chatroom
409
410 B<-m>,B<--message> <message>
411 read the message from <message> (a file) instead of stdin
412
413 B<-v>,B<--verbose>
414 give verbose output about what is happening
415
416 B<-h>,B<--help>,B<--usage>
417 show a 'Usage' message
418
419 B<-d>,B<--debug>
420 show debugging info while running. B<WARNING>: This will include passwords etc. so be careful with the output!
421
422 =head1 CONFIGURATION FILE
423
424 You may define a '~/.sendxmpprc' file with the necessary data for your 
425 xmmp-account, with a line of the format:
426
427    <user>@<host> <password>
428
429 e.g.:
430
431     # my account
432     alice@jabber.org  secret
433
434 ('#' and newlines are allowed like in shellscripts)
435     
436 B<NOTE>: for your security, sendxmpp demands that the configuration
437 file is owned by you and has file permissions 600.
438
439 =head1 EXAMPLE
440
441    $ echo "hello bob!" | sendxmpp -s hello someone@jabber.org
442
443      or to send to a chatroom:
444
445    $ echo "Dinner Time" | sendxmpp -r TheCook --chatroom test2@conference.jabber.org    
446
447 =head1 SEE ALSO
448
449 Documentation for the L<Net::XMPP> module
450
451 The jabber homepage: http://www.jabber.org/
452
453 The sendxmpp homepage: http://www.djcbsoftware.nl/code/sendxmmp (the xmmp homepage)
454
455 =head1 AUTHOR
456
457 sendxmpp has been written by Dirk-Jan C. Binnema <dirk-jan@djcbsoftware.nl>, and uses
458 the L<Net::XMPP> modules written by Ryan Eatmon.
459
460 =cut
461
462
463
464