#!/usr/bin/perl -w
#
# xucpclient
#
# Hacked together from sms, rup2sms, and some sample XUCP code from M. Aarnio
# Ian Macdonald 26/12/98
#
# $Id: xucpclient,v 1.3 1999/09/27 17:08:35 ianmacd Exp $

use XUCP;
use Getopt::Std;

$vers = "0.10";
$systemrc = "/etc/xucpclientrc";
$userrc = $ENV{HOME} . "/.xucpclientrc";
$phonerc = "/etc/xucpphonebook";
$userphonerc = $ENV{HOME} . "/.xucpphonebook";
$maxlen = 150;
$server = "kaukohaku.mailnet.fi";
$port = 2000;

getopts("de:hl:np:r:s:tv", \%opt);
# remove password from process table if supplied on command line
# this is a race condtion and is NOT safe!
$0 = "xucpclient -p " . "X" x length($opt{"p"}) if $opt{"p"};
usage() if $opt{"h"} or not $opt{"r"};
# check for valid expiry date
if ($opt{"e"}) {
   if ($opt{"e"} =~ s/^\+(\d+)$/$1/) { # form is +minutes
      $dst = (localtime)[8];  # Daylight Savings Time (1 if in effect)
      ($min, $hour, $mday, $mon, $year)
         = map { sprintf("%02d", $_) } # add a leading zero if necessary
            # EET is UTC + 2 hours + 1 hour if DST. Add minutes to that
            (gmtime(time+2*3600+$dst*3600+$opt{"e"}*60))[1..5];
      ++$mon; # month returned as 0..11
      $year -= 100 if $year > 99; # year is years since 1900
      $opt{"e"} = join "", $mday, $mon, $year, $hour, $min;
   } elsif (not $opt{"e"} =~ s{^([0-3]\d)[-/]?([0-1]\d)[-/]?([09]\d)
                               \^?
                               ([0-2]\d):?([0-5]\d)$
                              }
                              {$1$2$3$4$5}x) {
      # form is not DDMMYYHHMM
      die "Expiry format error\n";
   }
}
# get system and user defaults
foreach ($systemrc, $userrc) {
   if (-r) {
      warn "Reading $_...\n" if $opt{"d"};
      require;
   } elsif ($opt{"d"}) {
      warn "$_ not " . ((-f) ? "readable" : "found") . "\n"
   }
}
# read system and user phone book
foreach ($phonerc, $userphonerc) {
   if (-r) {
      warn "Reading $_...\n" if $opt{"d"};
      open(PHONE, $_) or die "Couldn't open $_: $!\n";
      while($_ = <PHONE>) {
         chomp;
         ($person, $number) = (split /\s*=\s*/, $_);
         $phone{$person} = $number;
         print("Adding phone book entry for $person...\n") if $opt{"d"};
      }
      close(PHONE) or die "Couldn't close $_: $!\n";
   } elsif ($opt{"d"}) {
      warn "$_ not " . ((-f) ? "readable" : "found") . "\n"
   }
}

# check for overriding command line arguments
$login = $opt{"l"} if $opt{"l"};
$passwd = $opt{"p"} if $opt{"p"};
# check we have both a login & passwd
foreach ("login", "passwd") {
   die "$_ must be defined in $systemrc, $userrc\n"
       . "or on the command line\n" if ! defined $$_;
}

@rcpt = split / /, $opt{"r"};
foreach $rcpt (@rcpt) {
   # get number from phone book if available
   $rcpt = $phone{$rcpt} if $rcpt =~ /\D/ && exists $phone{$rcpt};
   substr($rcpt, 0, 1) = "00" if substr($rcpt, 0, 1) eq "+";
   die "recipient number contains non-digits and isn't "
       . "an entry in the phone book.\n" if $rcpt =~ /\D/;
   warn "Recipient number doesn't appear to be in international format.\n"
      if substr($rcpt, 0, 2) ne "00" and $opt{"d"};
}
substr($opt{"s"}, 0, 1) = "00" if $opt{"s"} and substr($opt{"s"}, 0, 1) eq "+";
warn "Non-numeric characters stripped from sender's number.\n"
   if $opt{"s"} and $opt{"s"} =~ s/\D+//g;

# get message from command line or stdin
$message = "@ARGV" || join "", <STDIN>;
# remove newlines
$message =~ s/\n/ /g;
# check length of message and truncate if appropriate
if (($len = length($message)) > $maxlen) {
   if ($opt{"t"}) {
      $message = substr($message, 0, $maxlen);
      warn "Truncating message by ", $len-$maxlen, " characters.\n"
	 if $opt{"d"};
   } else {
      die "Message must be <= $maxlen characters.\n";
   }
}

foreach $rcpt (@rcpt) {
   # connect to server
   $s = XUCP::new($server, $port);
   if (! defined $s) {
      printf("XUCP::new() yielded UNDEF.\n");
   } elsif ($opt{"d"}) {
      printf("XUCP::new() yielded connection, salt='%s'\n", $s->{salt});
   }
 
   # set debugging if required
   $s->setdebug(1) if $opt{"d"};
   # login
   $s->login($login, $passwd);
   printf("login response: '%s'\n",$s->{resp}) if $opt{"d"};
   print($message, "\n") if $opt{"v"};
   unless ($opt{"n"}) {
      $s->gsmexpiry($opt{"e"}) if $opt{"e"};
      $s->gsmfrom($opt{"s"} || $rcpt);
      $s->rcpt("gsm", $rcpt);
      $s->bidref("BillingID\tUserName\tBillType");
      $s->text($message);
      $s->bye();
   }
}

sub usage {
   die <<end_usage
   xucpclient, version $vers

   Usage: xucpclient [ -d ] [-e expiry ] [ -n ] [ -v ] [ -t ] [ -l login ]
                     [ -p passwd ] [ -s sender ] -r recipients [ message ]
          xucpclient -h

   -d debug:    prints debugging information and warnings to stdout.
   -h help:     print this usage message.
   -n test:     log in, but send no message.
   -t truncate: truncate message if longer than $maxlen characters
   -v verbose:  prints text of message to stdout.

end_usage
}
__END__

=head1 NAME

xucpclient - Sonera Short Message Service (SMS) client

=head1 SYNOPSIS

B<xucpclient [options] E<lt>messageE<gt>>

=head1 DESCRIPTION

B<xucpclient> is a Short Message Service (SMS) client that sends messages
via a Sonera XUCP server.

To operate B<xucpclient>, you will need an authentication file to be
present as either F</etc/xucpclientrc> or F<~/.xucpclientrc>. This file
should have the form:

$login = "xxxxx";

$passwd = "yyyyy";

where the details between the quotes are available only to authorised Sonera
personnel.

=head2 Options

=over 5

=item B<-d>

Turn on debugging messages.

=item B<-e> <DDMMYYHHMM|+mins>

Set an expiry date for the message, after which it will be discarded if it
is still pending. If provided in DDMMYYHHMM format, the time should be
given in Eastern European Time (EET), the time zone of Finland.

The days, months and years may be separated by either '/' or '-', the date
and time may be separated by a '^', and the hours and minutes may be
separated by a ':'. All of these separators are optional and are provided
purely to improve human legibililty.

More flexible is the +mins format, which will expire the message a given
number of minutes from the current time.

=item B<-l> E<lt>userE<gt>

Log into server as this user.

=item B<-n>

Log into server, but don't send the message.

=item B<-p> E<lt>passwordE<gt>

Log into server using this password.

=item B<-r> E<lt>recipientsE<gt>

List of recipients. The list should be supplied as a single parameter
(i.e. multiple entries should be single quoted), and may consist of
either telephone numbers or telephone book entries.

A telephone number should be supplied in its full international form, e.g.
+31651123456, although you may use 00 instead of the prefix '+'.

A telephone book is one or both of F</etc/xucpphonebook> and F<~/.xucpphonebook>.
These files should contain entries of the form B<user=number>. White space
on either side of the '=' is allowed.

=item B<-s> E<lt>senderE<gt>

This option may be used to supply a number that will appear to have
initiated the message. If this is not supplied, the recipient number will
be duplicated as the sender number.

=item B<-t>

Truncate message if more than 150 characters.

=item B<-v>

Run in verbose mode.

=head1 EXAMPLES

=over 5

=item xucpclient B<-de> 25/09/99^12:05 B<-s> +31651123456 B<-r> 0611 Hi

Send the message "Hi" to +31651123456, stating that it came from 0611.
Debugging messages will be displayed and, if the message cannot be sent by
12:05 on 25th September 1999, it will be discarded.

=item xucpclient B<-e> +120 B<-s> +31651123456 Got there in 2 hours

Send a message to expire 2 hours from now if undelivered.

=item /usr/games/fortune B<-sn> 150 | xucpclient B<-vr> 0031651123456

Send a suitably short fortune cookie to 0031651123456, sending the message to
stdout in the process. The sender's number will appear the same as
the recipient's.

=item Try this in your F<.procmailrc>:

:0 c

* ^From.*company.com

| formail B<-X> "From:" B<-X> "Subject:" | \ xucpclient B<-r> +31651123456

This will alert you whenever you receive an e-mail from somebody at
company.com.

=back

=head1 AUTHOR

B<Ian Macdonald E<lt>ian@caliban.orgE<gt>>

=head1 BUGS

Probably. Send me your diffs.

=head1 FILES

F</etc/xucpclientrc>, F<~/.xucpclientrc>, F</etc/xucpphonebook>,
F<~/./xucpphonebook>

=cut
