#!/usr/bin/env perl # ASPSMS -- Send SMS through the aspsms.com short message service gateway # http://www.roe.ch/ASPSMS # # Copyright (C) 2005-2010, Daniel Roethlisberger # # Redistribution and use, with or without modification, are permitted # provided that the following conditions are met: # 1. Redistributions must retain the above copyright notice, this list of # conditions and the following disclaimer. # 2. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # $Id: aspsms.pl 30 2010-01-12 10:38:15Z roe $ # $RCS: aspsms.pl,v 1.3 2005/10/08 18:38:53 roe Exp $ # Conforming with the ASPSMS XML Interface Specifications 1.7, 2005-05-18. # * Pure plain text SMS only # * Support for setting sender/origin by message # * No delivery status notifications (yet?) # * Conforms to ASPSMS failure safety recommendations # * Fails gracefully, depending on type of failure # * Userkey, password and default sender read from config file # # Configuration is read from ~/.aspsms or /etc/aspsms: # # ASPSMS configuration # userkey XYZXYZXYZXYZ # password y0UrPasSw0rD # sender +41XXXXXXXXX # Make sure to set sensible permissions (0600 is a good start). use LWP::UserAgent; use HTTP::Request::Common; use Getopt::Std; use strict; use vars qw{ $opt_f $opt_s $opt_t $opt_v $opt_h }; getopts("f:s:tvh"); sub usage { die "$0 [OPTIONS...] RCPTS... Send SMS through the aspsms.com short message service gateway. -f FILE Read message text from FILE instead of stdin. -s SENDER Set sender/origin to SENDER (max 11 characters). -t Truncate message to 160 characters (default: split) -v Be verbose. Recipient cellphone numbers RCPTS are in international format, eg. +41XXXXXXXXX for Switzerland or +44XXXXXXXXX for the UK. "; } usage() if($opt_h); ############################################################################## # configuration and parameters my @urls = ( 'http://xml1.aspsms.com:5061/xmlsvr.asp', 'http://xml2.aspsms.com:5098/xmlsvr.asp', 'http://xml1.aspsms.com:5098/xmlsvr.asp', 'http://xml2.aspsms.com:5061/xmlsvr.asp', ); my %conf = (); my $cfg = glob('~/.aspsms'); $cfg = '/etc/aspsms' if(! -r $cfg); die "Can read neither ~/.aspsms nor /etc/aspsms" if(! -r $cfg); open(CFG, "<$cfg") || die "Cannot open $cfg"; while() { next if(/^#/); if(m/^(\S+)\s+(.*)$/) { $conf{$1} = $2; } } close(CFG); my $userkey = $conf{'userkey'} || die "No userkey in $cfg"; my $password = $conf{'password'} || die "No password in $cfg"; my $src = $opt_s || $conf{'sender'} || die "No sender in $cfg and -s not given"; ############################################################################## # destination numbers my $dst = ''; while(my $d = shift()) { die "Illegal number: $d" if($d !~ /^\+[0-9]{7,15}$/); $dst .= $d . ","; } chop($dst); usage() unless($dst); my @dsts = split(/,/, $dst); ############################################################################## # message text my $in_file = $opt_f || '-'; if($in_file eq '-') { *INFILE = \*STDIN; } else { open(INFILE, "<$in_file") or die "Cannot open $in_file for reading"; } my $msg = ''; while() { chomp; $msg .= $_ . ' '; } $msg =~ s/\s+/ /g; $msg =~ s/^\s+//g; $msg =~ s/\s+$//g; if($in_file ne '-') { close(INFILE); } usage() unless($msg); my @msgs = (); my $limit = 160; if($opt_t) { push(@msgs, substr($msg, 0, $limit)); } else { my $m = $msg; while(length($m) > $limit) { push(@msgs, substr($m, 0, $limit)); $m = substr($m, $limit, length($m) - $limit); } push(@msgs, $m); } ############################################################################## # main sending loop my $log = ''; my $status = 4; my $errors = 0; # for each server for(my $i_s = 0; $i_s <= $#urls && $status == 4; $i_s++) { $status = 1; $log .= " $urls[$i_s]\n"; # for each msgs for(my $i_m = 0; $i_m <= $#msgs && $status == 1; $i_m++) { # for each dst for(my $i_d = 0; $i_d <= $#dsts && $status == 1; $i_d++) { my %res = sms_submit( userkey => $userkey, password => $password, src => $src, dst => $dsts[$i_d], msg => $msgs[$i_m], url => $urls[$i_s], ); $log .= " $dsts[$i_d] - $res{reason}\n"; $status = $res{code}; $errors++ if($status != 1); } } } chomp $log; if($errors > 0) { print STDERR < 'your ASPSMS userkey', # password => 'your ASPSMS password', # src => 'sender/origin to use', # dst => 'recipient cellphone number in +XXXXX format', # msg => 'message body', # url => 'URL of ASPSMS gateway to use' # Returns hash: # code => N, # N = 1: ok, 4: try other, 5: do not try other # reason => 'reason text' sub sms_submit { # return (code => 5, reason => "fake!"); my %args = @_; my $xml = < $args{userkey} $args{password} $args{src} $args{dst} $args{msg} SendTextSMS EOF my $ua = LWP::UserAgent->new(agent => 'aspsms.pl/20100112'); $ua->timeout(60); $ua->env_proxy(); my $res = $ua->request(POST $args{url}, Content_Type => 'text/xml', Content => $xml); if(!$res->is_success) { return (code => 4, reason => $res->status_line); } # #1Ok # #3Authorization failed $res->as_string =~ /([0-9]+)<\/ErrorCode>/i; my $ec = $1; $res->as_string =~ /(.*)<\/ErrorDescription>/i; my $ed = $1; unless($ec == 1) { return (code => 5, reason => "$ed ($ec)"); } else { return (code => 1, reason => "$ed ($ec)"); } }