#!/usr/bin/perl -w
#
# sms - send SMS messages to GSM telephones via WWW based carriers
#
# (c) 1998 Ian Macdonald <ian@caliban.org>
#
# $Id: sms,v 1.13 1998/12/28 00:11:07 ianmacd Exp ianmacd $

use Getopt::Std;
use LWP::UserAgent;    # performs the POSTing

$vers = "0.61";
%network = (
            QUIOS => {
                    referer => "http://www.quios.com/",
                    post    => "http://www.quios.com/",
                    defpre  => "+31651",
                    maxlen  => 128
                   }
	   );
# define a hash of named recipients here, if you like
# %gsm = (tom => "470123", dick => "470456", harry => "470789");
getopts("a:c:hn:o:p:qr:t:u:", \%opt);

usage() if $opt{"h"} or not $opt{"c"};
# Check -c for valid (sub)string of supported network
foreach (keys %network) {
  if (lc($opt{"c"}) eq lc(substr($_, 0, length($opt{"c"})))) {
    $carrier = $_;
    last;
  }
}
no_carrier($opt{"c"}) unless $carrier;
$opt{"s"} && (list_networks($carrier) && exit
  || die "Couldn't retrieve list for $carrier.\n");
usage() unless $opt{"n"};

# if no command line message, get one from standard input
$message = "@ARGV" || join "", <STDIN>;
die "Message must be <= $network{$carrier}->{'maxlen'} characters.\n"
  if $network{$carrier}->{"maxlen"} - length($message) < 0;
if ($opt{"n"} =~ /\D/) {
  # non-digit in number - check for named recipient
  die "Unknown subscriber: $opt{'n'}\n" unless exists $gsm{$opt{"n"}};
  $opt{"n"} = $gsm{$opt{"n"}};
  $opt{"p"} = $network{$carrier}->{"defpre"};
}
#$carrier eq "MTN" and substr($opt{"p"}, 0, 2) eq "00"
#  and substr($opt{"p"}, 0, 2) = "+";
$tel = ($intlPrefix = $opt{"p"}
	|| $network{$carrier}->{"defpre"}) . $opt{"n"};
# encode ambiguous characters
encode($message, $intlPrefix, $tel);
# get the entire submission into urlencoded form
if ($carrier eq "QUIOS") {
   $urlenc = "carrier=10:3"
      . "&service_type=Stocks"
         . "&address=$tel"
            . "&message=$message";
}
send_request($opt{"r"} || $network{$carrier}->{"referer"},
	     $opt{"o"} || $network{$carrier}->{"post"},
	     $urlenc);

sub send_request {
  my($referer, $post, $urlenc) = @_;
  # create a new user agent object and HTTP request object
  $ua = new LWP::UserAgent;
  # if you want or need to use a proxy server, add that here
  #$ua->proxy(['http','ftp'] => 'http://myproxy.proxy.com:3128');
  $req = new HTTP::Request "POST", $post;
  # the least known browser in the world: PerlScape
  $req->header("user-agent" => $opt{"u"} || ("PerlScape" . "/$vers"));
  # fib about where we're coming from - essential
  $req->header("referer" => $referer);
  $req->header("content-length" => length($urlenc));
  $req->header("content-type" => "application/x-www-form-urlencoded");
  $req->content($urlenc);
  $ua->timeout($opt{"t"}) if $opt{"t"};
  $try = 1; $maxtries = $opt{"a"} || 1;
  until ($try > $maxtries) {
    print "Sending HTTP request:\n\n", $req->as_string()
      unless $opt{"q"};
    $resp = $ua->request($req); # There it goes...
    print "\nReceived HTTP response:\n\n", $resp->as_string()
      unless $opt{"q"};
    return 0 if $resp->is_success();
    die "SMS message not sent: " . $resp->message() . "\n"
      if $try == $maxtries;
    warn "SMS message not sent: " . $resp->message() . "\n"
      . "Retrying (". ++$try .")...\n\n";
  }
}

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

 Usage: sms [ -q ] [ -a attempts ] [ -t time-out ] [ -u user agent ]
            -c carrier [ -e e-mail address ] [ -r referer URL ] [ -o post URL ]
            [ -p GSM network prefix ] -n GSM number [ message ]

        sms -h

  -q quiet: only error messages will be printed.
  -h help:  print this usage message.

end_usage
}

sub no_carrier {
  my($nc, @networks) = (@_, keys %network);
  $" = "\n";
  die "Network $nc is not supported. "
    . "Currently supported networks are:\n\n"
      . "@networks\n";
}

sub encode {
  foreach (@_) {
    # encode ambiguous characters
    # must escape $ to avoid interpolation of $%
    s/([\n !"#\$%&'()+,\/:;<=>?@[\]\\^`{|}~])/"%" . unpack("H2", $1)/eg;
  }
}

__END__;

=head1 NAME

sms - send SMS messages via WWW based carriers

=head1 SYNOPSIS

B<sms> [ B<-q> ] [ B<-a> attempts ] [ B<-t> time-out ]
    [ B<-u> user agent ] B<-c> carrier [ B<-r> referer URL ]
    [ B<-o> post URL ] [ B<-p> GSM network prefix ]
    B<-n> GSM subscriber number [ message ]

B<sms> B<-h>

If you don't provide a message on the command line, standard input
will be used instead.

=head1 DESCRIPTION

I<sms> is a tool for sending SMS messages via WWW based carrier
services. Currently, only the QUIOS network is supported.

It can be used for many purposes, for example in combination with
I<rup>(1) in I<cron>(8) scripts for producing cheap and cheerful
server watchdog systems, and with I<fortune>(6) for sending random
quotations to your friends.

You should B<not> rely solely on I<sms> for mission critical
applications. See L<"BUGS">.

The recipient of your message will have no indication that it
originated from you, so you should include the appropriate information
if this is required.

=head1 OPTIONS

=over 4

=item B<-a> attempts

The maximum number of attempts that I<sms> should make at sending the
message. A failure is defined as the return of an HTTP error code, so
time-outs count as a failure, but incorrect GSM numbers don't, since
the server will correctly process the request by rejecting it (and
there would be no point in retrying anyway).

=item B<-c> carrier

The preferred carrier for your message. The only currently valid value
is I<QUIOS>, although case is unimportant and abbreviation is allowed.

=item B<-h>

Print a short usage message.

=item B<-n> GSM subscriber number

The telephone number of the subscriber to whom you wish to send a
message. This parameter is mandatory.

=item B<-o> POST URL

If a particular carrier changes his SMS submission procedure in such a
way that I<sms> can no longer send messages, it could simply be that
the URL of the page used for submitting requests has changed. If you
can discern what the new URL is, this option will allow you to specify
it and continue to use I<sms> until a new version comes out. See also
B<-r>.

=item B<-p> GSM network prefix

The network prefix of the GSM telephone to which you wish to send a
message. If you don't provide a prefix, the default defined in $defpre
will be used. You can use prefixes of the form +31651 or 0031651.

=item B<-q>

Quiet mode: only error messages will be printed.

=item B<-r> referer page URL

If a particular carrier changes the SMS submission procedure in such a
way that I<sms> can no longer send messages, it could simply be that
the URL of the page used for referring requests has changed. If you
can discern what the new URL is, this option will allow you to specify
it and continue to use I<sms> until a new version comes out. See also
B<-o>.

=item B<-s>

Show list of supported GSM networks.

=item B<-t> time-out

The time-out after which sms will abandon trying to contact the server
of the chosen carrier. If you don't provide a timeout, the default of
180 seconds will be used.

=item B<-u> user agent

The user agent passed to the carrier server. You can use this to
pretend to be using a certain kind of browser to send your
message. The default is PerlScape.

=back

=head1 EXAMPLES

=over 4

=item S<sms -c quios -p 0031622 -n 123456 Stop SMSing me. I hate it.>

Sends a message to 0031622 123456.

=item S</usr/games/fortune -n 128 -s | sms -a 5 -c QUIOS -p +31655 -n 123456>

Makes 5 attempts at sending a random pithy quotation to +31655 123456
using the default time-out period.

=item sms -c QUIOS -n 456789 < /proc/version

Using the default network prefix, makes 456789's day with information
about your operating system (Linux only).

=item Try this in your F<.procmailrc> (UNIX only):

:0 c
* ^From.*company.com
S<| formail -X "From:" -X "Subject:" | \>
  /usr/bin/sms -c QUIOS -p +31655 -n 123456

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

=back

=head1 BUGS

I<sms> doesn't check message for non-printing characters (I really
should fish out the SMS standard some time).

A simple change in the configuration of one of the supported servers
can render I<sms> inoperable in combination with that server. Such an
occurrence seems likely, if for no other reason than to attempt to
stop third party programs like this from functioning.

I<sms> is subject to the same caveats as messages sent directly from
the server in question, e.g. messages should consist of no more than
128 characters when using the QUIOS server.

=head1 TO DO

=over 4

Volunteers are needed for this project. Please get in touch.

=item Add external configuration file for defaults

=item Add DBM database for storing names & numbers

=item Add Tk based GUI

=item Add support for more carriers

=back

=head1 AUTHOR

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

=head1 LICENCE

Copyright (c) 1998-99 Ian Macdonald. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.

No support is available for this software and no warranty whatsoever
is either given or implied. In particular, do not contact me about
getting this software to work on Win32 platforms (unless you have done
so yourself and wish to contribute to the documentation).

Improvements to the code and new features, however, are most
welcome. Mail me your diffs.

=head1 ACKNOWLEDGEMENTS

My thanks go to QUIOS for the free SMS service they provide.

=head1 SEE ALSO

I<grep>(1), I<LWP>(3), I<fortune>(6), B<http://www.quios.com/>

I<sms> depends on the LWP modules, which in turn have their own set of
dependencies. See B<http://www.linpro.no/lwp/> for more information.

=cut
