#!/usr/bin/perl
#
#    VodaSMS - perl script to send SMS via Vodafone.it website
#    Based on GotMail perl script:
#
#    -----------------------------------------------------------------------
#    GotMail - perl script to get mail from hotmail mailboxes.
#    Copyright (C) 2000-2003 Peter Hawkins <peterhawkins@ozemail.com.au>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    -----------------------------------------------------------------------
#    Currently maintained by John Fruetel <jfruetel@hotmail.com> 
#    Peter Hawkins is no longer  involved with the development
#    of this software.
#

require 5.004;

# Uncomment this if you have SpamAssassin installed
use English;
use URI::Escape;
use POSIX qw(tmpnam);
use FileHandle;
use strict;

# Signal handlers:
$SIG{INT} = $SIG{TERM} =
	sub {
		my($text) = @_;
		print STDERR "vodasms died with message: $text\n";
		print STDERR "Exiting abnormally, cleaning temp files.\n";
		doCleanTempFiles();
		exit(1);
	};

# Hide command line in a "ps" listing;
$0 = '[ vodasms sending new messages ]';
# This is not great security. The command line can still be found by other
# users. I recommend using a ~/.vodasmsrc file, so that curl will be passed
# the username and password via private temporary files.

# Don`t allow others to read our temp files
my($oldumask) = umask(077);

# Constants...
# FIXME: This opens the possibility of race conditions.
my($tmp_headers) = tmpnam()."vodasms_headers";
my($tmp_cookies) = tmpnam()."vodasms_cookies";
my($tmp_formdata) = tmpnam()."vodasms_form";

my($log) = "/tmp/vodasms_log";
my($vodasms_version) = "1.1.1";
my($vodasms_date) = "2004-11-25";

# Some option dependent variables
my($conf_proxy)="";
my($conf_proxy_auth)="";
my($login) = "";
my($password) = "";
my($conf_file) = "";
my($conf_curl) = 'curl';
my($conf_speed_limit) = 0;
my($conf_retry_limit) = 2;
my($conf_verbosity) = 0;  # -1 = silent; 0 = normal; 1 = verbose; 2 = debug spew
my($conf_message) = "";
my($conf_reciever) = "";
my($conf_signature) = "";
my($input) = "";

# Global variables...
my($host) = ""; # The name of the hotmail server we are talking to...
my($gotconfig) = 0; # Have we found a config file?
my(@cookies) = ();

# Display some text to the screen, and log it, if we are in debug mode.
sub dispText($)
{
	my($text) = @_;

	if ($conf_verbosity >= 0) {
		print $text;
	}

	if ($conf_verbosity > 1) {
		my($out) = new FileHandle ">> $log" || return;
		print $out $text;
		close $out;
	}
}

# Various utility functions
sub dispIntroText()
{
	if ($conf_verbosity >= 0)
	{
		print "\nVodaSMS v".$vodasms_version."     Copyright (C) 2004 Emanuele Vecchio.\n";
		print "Based on the excellent work of Peter Hawkins and John Fruetel Gotmail.\n\n";

		if ($conf_verbosity > 1) {
			my($out) = new FileHandle ">> $log" || return;
			print $out "Vodasms v".$vodasms_version." logfile.\n";
			close $out;
		}
	}
}

sub dispVersionText()
{
	print "Version information: Vodasms v".$vodasms_version."   Date: ".$vodasms_date."\n";
}

sub dispUsageAndExit()
{
	# We are about to quit, so we want to show the user everything.
	$conf_verbosity = 0;
	dispIntroText();

	print "Usage:\nvodasms [OPTIONS...] message\n";
   print "cat <message_file.txt> | vodasms [OPTIONS...] -\n";

	print "\nOptions:\n";
	print "  -?, --help, --usage        Display this screen\n";
	print "  --version                  Display version information\n";
	print "  -c, --config-file <file>   Specify config file (default ~/.vodasmsrc)\n";
	print "  -u, --username <name>      Specify your 190.it username (REQUIRED)\n";
	print "  -p, --password <pass>      Specify your 190.it password (REQUIRED)\n";
	print "  -s, --signature <text>     Specify the signature to append to the message\n";
	print "  -n, --number <tel number>  Specify the reciever telephone number (REQUIRED)\n";
	print "  --proxy <host:port>        Specify an HTTP proxy to use. Format is\n" .
	      "                               host:port - eg: localhost:3128\n";
	print "  --proxy-auth <user:pass>   Specify authentification details for the\n" .
	      "                               HTTP proxy.\n";
	print "  --curl-bin <path>          Specify the path to the cURL program if it's\n" .
	      "                               not in your path.\n";
	print "  --silent                   Do not print messages\n";
	print "  -v, --verbose              Verbosely print messages\n";
	print "  --debug                    Print debug output\n";
	exit();
}

# Parse ~/.vodasmsrc
#
# Inserted code to parse ~/.vodasmsrc
# This *should* hopefully be a little secure than specifying your
# username and password on the command line.
# parseArgs() is called afterwards, so you can override any settings.
# Thanks to Patrick Froede
#    and also to Tim Dijkstra. -pik-

sub parseConfig {
	if ("@ARGV" =~ /(\s|^)(-c|--config-file)\ ([\w\.~\/\-]*)(\s|$)/i) {
		$conf_file = $3;
		if (! -r $conf_file) {
			die "Config file <$conf_file> is not readable!\n";
		}
	} elsif ($ENV{"HOME"}) {
		$conf_file = $ENV{"HOME"} . "/.vodasmsrc";
	} else {
		if (-e $ENV{"HOMEDRIVE"} . $ENV{"HOMEPATH"} . ".vodasmsrc") {
			# Using w2k environment variables.
			$conf_file = $ENV{"HOMEDRIVE"} . $ENV{"HOMEPATH"} . ".vodasmsrc";
		} elsif (-e "/.vodasmsrc") {
			# Try root directory
			$conf_file = "/.vodasmsrc";
		} else {
			# Or try current directory
			$conf_file = "./.vodasmsrc";
		}
	}

	# Open the config file, otherwise bail out of this subroutine
	open(RCFILE, $conf_file) || return;

	# I made these options identical to the ones in the arguments.
	# To avoid breaking compatibility, the old names can also be
	# used. -pik-

	# Parse the file
	while (<RCFILE>) {
		next if ($_ =~ /^#/);
		if ($_ =~ /^user(name)?=(.+)$/i) {
			$login = $2;
		} elsif ($_ =~ /^pass(word)?=(.+)$/i) {
			$password = $2;
		} elsif ($_ =~ /^num(ber)?=(.+)$/i) {
			$conf_reciever = $2;
		} elsif ($_ =~ /^sign(ature)?=(.+)$/i) {
			$conf_signature = $2;
		} elsif ($_ =~ /^proxy=(.+)$/i) {
			$conf_proxy = $1;
		} elsif ($_ =~ /^proxy_auth=(.+)$/i) {
			$conf_proxy_auth = $1;
		} elsif ($_ =~ /^retry-?limit=([0-9]+)$/i) {
			$conf_retry_limit=$1;
		} elsif ($_ =~ /^speed-?limit/i) {
			$conf_speed_limit = 1;
		} elsif ($_ =~ /^silent/i) {
			$conf_verbosity = -1;
		} elsif ($_ =~ /^curl-bin=(.+)$/i) {
			$conf_curl = $1;
		}
	}

	# Make a note that we obtained some configs from the options file
	$gotconfig = 1;
	close(RCFILE);
}

# Parse the command line
sub parseArgs($)
{
	# If we have a config file, we don`t care if there aren`t any arguments...
	if (!@ARGV && ($gotconfig == 0)) {
		dispUsageAndExit();
	}

   # loading STDIN into $stdin;
   my ($stdin) = @_;
   
	while(@ARGV) {
		my($element)=shift(@ARGV);
		if ($element =~ /^(-h|-\?|--help|--usage)$/i) {
			dispUsageAndExit();
		}
		elsif ($element =~ /^--version$/) {
			dispVersionText();
		}
		elsif ($element =~ /^(-c|--config-file)$/) {
			shift(@ARGV);
		}
		elsif ($element =~ /^(-u|--username)$/i) {
			if (@ARGV) {
				$login = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		elsif ($element =~ /^(-p|--password)$/i) {
			if (@ARGV) {
				$password = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		elsif ($element =~ /^--proxy$/i) {
			if(@ARGV) {
				$conf_proxy = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		elsif ($element =~ /^--proxy-auth$/i) {
			if(@ARGV) {
				$conf_proxy_auth = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		elsif ($element =~ /^--speed-limit$/i) {
			$conf_speed_limit = 1;
		}
		elsif ($element =~ /^--silent$/i) {
			$conf_verbosity = -1;
		}
		elsif ($element =~ /^--debug$/i) {
			$conf_verbosity = 2;
		}
		elsif ($element =~ /^(-v|--verbose)$/i) {
			$conf_verbosity = 1;
		}
		elsif ($element =~ /^--curl-bin$/i) {
			if (@ARGV) {
				$conf_curl = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		elsif ($element =~ /^(-s|--signature)$/i) {
			if(@ARGV) {
				$conf_signature = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		elsif ($element =~ /^(-n|--number)$/i) {
			if(@ARGV) {
				$conf_reciever = shift(@ARGV);
			} else {
				dispUsageAndExit();
			}
		}
		else {
			$conf_message = $element;
			#dispText("Unrecognized option $element\n");
			#dispUsageAndExit();
		}
	}
	
	if ($conf_message eq "" && $stdin ne "") {
      $conf_message = $stdin;
      $conf_message =~ s/\n//i;
   }
   
	if (($login eq "") || ($password eq "") || ($conf_message eq "") || ($conf_reciever eq "") )
	{
		print STDERR "A username, password, reciever tel. number and message are REQUIRED.\n";
		print STDERR "Try --help for usage info.\n";
		exit 1;
	}
}

# Clean up any temporary files
sub doCleanTempFiles()
{
	if (-e $tmp_headers) {
		unlink($tmp_headers)
		  or warn "Could not unlink tmp header: $!\n";
	}
	if (-e $tmp_cookies) {
		unlink($tmp_cookies)
		  or warn "Could not unlink tmp cookie: $!\n";
	}
	if (-e $tmp_formdata) {
		unlink($tmp_formdata)
		  or warn "Could not unlink tmp formdata: $!\n";
	}
}

sub doCleanOtherFiles()
{
	if (-e $log) { unlink($log); }
}

# Check all the required programs are installed.
sub doCheckPrograms()
{
	if ($conf_verbosity > 1) {
		dispText("System version is: ".$OSNAME."\n");

		# $PERL_VERSION and $^V seem to be broken
		dispText("Perl version is:   ".$]."\n");
	}

	if ($conf_verbosity > 1) {
		dispText("Curl version is:   ".`$conf_curl --version`."\n");
	}

}

# Grep any cookies from the header file into the cookies file.
sub parseHeaders()
{
	my $redirector = "";
	my($in) = new FileHandle "< $tmp_headers" || return;

	while (<$in>) {
		if (m/^Location: (\S+)\s/) {
			$redirector = $1;
		}
	}
	close($in);

	return $redirector;
}

# Fetch a given page using curl
#
# The parameters taken are the URL, any data to be posted in a POST,
# whether we are to follow HTTP redirects, whether we should send and
# receive cookies, and whether we should only get the headers for this
# page and not the body.
sub getPage($$$$$)
{
	my($url, $params, $follow_forward, $cookies, $headers_only) = @_;

	if ($url =~ m/http:\/\/(\S+?)\//i) {
		$host = $1;
	}

	if ($conf_verbosity > 0) {
		dispText "FETCH: $url\n";
	}

	# Set up the options string...
	my($options) = "";
	if ($conf_proxy) { $options .= "--proxy ". $conf_proxy . " "; }
	if ($conf_proxy_auth) { $options .= "--proxy-user ". $conf_proxy_auth . " "; }
	if ($cookies != 0) { $options .= "-b $tmp_cookies -c $tmp_cookies " }
	if ($params ne "") { $options .= "--data \"$params\" " }
	if ($headers_only) { $options .= "-I " }
	if ($conf_verbosity <= 0) { $options .= "-s -S " }
	if ($conf_verbosity >= 2) { $options .= "-v " }

	# Get rid of any trailing space on options.. Just for neatness.
	$options =~ s/ $//;

	my($cmdline) = "$conf_curl \"$url\" $options -i -m 600 -D $tmp_headers" .
	               " -A \"Mozilla/4.73 [en] (Win98; I)\"";

	# Copy output to logfile if necessary
	if ($conf_verbosity > 1) {
		$cmdline .= "| tee -a $log";
		dispText("command line: $cmdline\n");
	}

	my $tries = 1;
	my(@tmp_page) = `$cmdline`;

	# Retry at most $conf_retry_limit times if we fail.
	while (!@tmp_page && !$headers_only && $tries <= $conf_retry_limit) {
		dispText("Retrying [$tries/$conf_retry_limit]...\n");
		$tries++;
		@tmp_page = `$cmdline`;
	}
	if (!@tmp_page && !$headers_only && $tries >= $conf_retry_limit) {
		die("An error was encountered getting the page. Command was $cmdline");
	}

	my $redir = parseHeaders();

	# If we have been asked to follow Location: headers
	if ($follow_forward) {
		if ($redir ne "") {
      if ($redir !~ m/^http.*/i) {
        if ($url =~ m/(http?:\/\/[^\/]+)\//i) {
          $redir = $1 . $redir;
        }
      }
			if ($conf_verbosity > 1) {
				dispText("Following redirect to $redir\n");
			}
			return &getPage($redir, "", $follow_forward, $cookies, $headers_only);
		}
	}

	if ($conf_verbosity > 0) { dispText "\n"; }

	return @tmp_page;
}


#
# validate input text
#
sub doValidateText($) {
  my ($message) = @_;
  if (strlen($message) > 1200) { die ("Cannot send SMS longer than 1200 character"); }
  if (strlen($message) < 3) { die ("Cannot send SMS shorter than 3 character"); }
  return $message;
}


#
# validate input telephone number
#
sub doValidateTel ($) {
  my $m1 = "Attenzione, nel numero telefonico hai inserito caratteri non numerici.";
  my $m2 = "Attenzione il numero di cellulare non puo' iniziare con 0.";
  my $m4 = "Attenzione, il numero di cellulare deve essere di nove o dieci cifre.";
  my $m5 = "Attenzione, per proseguire devi inserire un numero di telefono cellulare.";
  my $m6 = "Attenzione, il numero inserito non e' un cellulare";

  my ($number) = @_;
  my $len = strlen($number);
  my $prefix = substr($number,0,3);
  my $pr2 = substr($number,0,2);
  my $mobilePref = "320,328,329,340,330,333,334,335,336,337,338,339,343,346,347,348,349,360,368,380,388,389,390,391,392,393";
  my $ipsePref = "37";
  
  if ($len > 0) {
    if ($number =~ m/[^0-9]/i) {
      die($m1);
    }
    if (substr($number,0,1) == 0) {
      die($m2);
    }
    if ($len < 9 || $len > 10) {
      die($m4);
    }
    if ( (index($mobilePref,$prefix) == -1) && index($ipsePref,$pr2) == -1) {
      die ($m6);
    }
  } else {
    die ($m5);
  }
  return $number;
}


#
# actually send the SMS
#
sub SendSMS($$$) {

   # load variables
   my($sms_message,$sms_reciever,$sms_signature) = @_;
   if ($sms_signature ne "") {
     $sms_message .= " --".$sms_signature;
   }
   
	dispText("Getting vodafone index page...\n");
	my(@index_page);     ## This will have the login page.
	my($form_label) = "";

	my(@index_page) = getPage("http://www.190.it/", "", 1, 1, 0);
	my($page) = join("", @index_page);

	# Find the first redirect...
	my($first_login_script) = "";
	my($login_script) = "";
	my($prefix) = "http://www.190.it";

	my $page = join "", @index_page;
	if ($page =~ m/CONTENT=\"0 ;URL=(\S+)\"/i) {
		$first_login_script = $prefix . $1;
	}
   
   my(@first_login_page) = getPage($first_login_script, "", 1, 1, 0);
	my $page = join "", @first_login_page;
   if ($page =~ m/<form.*${form_label}.*action=\"(\S+)\".*>/i) {
      $login_script = $prefix . $1;
   }

	if ($login_script eq "") {
		die "Page doesn't contain any form action field!\n";
	}

	# Emulate a POST action:
	my($FORMFILE) = new FileHandle "> $tmp_formdata" || die "Couldn't open formdata file: $!\n";
	print $FORMFILE ("username=" . uri_escape($login, "^A-Za-z") .
	                 "\&password=" . uri_escape($password, "^A-Za-z") );
	close $FORMFILE;
	my($params) = "\@$tmp_formdata";

	dispText("Logging in...\n");
	my(@login_page) = getPage($login_script, $params, 1, 1, 0);

   dispText("Getting SMS pepare form ...\n");
   my $sms_pre_link = "http://www.areaprivati.190.it/190/trilogy/jsp/programView.do?pageTypeId=9604&programId=9361&channelId=-8663&ty_nocache=true&tk=9604,t&ty_op=-3,-1,-1,9604,1,-1,-1";
   my(@sms_pre_page) = getPage($sms_pre_link,"",1,1,0);
   
   # splash page
	 my $smsPreLink = "http://www.190.it/190/trilogy/jsp/dispatcher.do?ty_key=fsms_hp&ipage=next";
	 my(@sms_pre_page2) = getPage($smsPreLink,"",1,1,0);
	 
   $page = join "", @sms_pre_page2;
	 $form_label = "fsmsMessageForm";
   my $sms_script;
   my $sms_hidden_1;
   my $sms_hidden_2;
   my $sms_hidden_3;
   my $sms_hidden_4;
   my $sms_hidden_5;
   if ($page =~ m/<form.*name=\"${form_label}\".*action=\"(\S+)\".*>/i) {
      dispText("Finding form action: found to be $1\n");
      $sms_script = $prefix . $1;
   }
   if ($page =~ m/<input.*name=\"BV_SessionID\".*value=\"(\S+)\">/i) {
      #dispText("Reading session parameters 1 ...\n");
      $sms_hidden_1 = $1;
   }
   if ($page =~ m/<input.*name=\"BV_EngineID\".*value=\"(\S+)\">/i) {
      #dispText("Reading session parameters 2 ...\n");
      $sms_hidden_2 = $1;
   }
   
   # new stuff on 190.it ...
   if ($page =~ m/<input name=\"pageTypeId\" type=\"hidden\" value=\"(\S+)\">/i) {
      #dispText("Reading session parameters 3 ...\n");
      $sms_hidden_3 = $1;
   }
   if ($page =~ m/<input name=\"programId\" type=\"hidden\" value=\"(\S+)\">/i) {
      #dispText("Reading session parameters 4 ...\n");
      $sms_hidden_4 = $1;
   }
   if ($page =~ m/<input name=\"channelId\" type=\"hidden\" value=\"(\S+)\">/i) {
      #dispText("Reading session parameters 5 ...\n");
      $sms_hidden_5 = $1;
   }
   
   
   # Emulate the second POST action:
	my($FORMFILE_2) = new FileHandle "> $tmp_formdata" || die "Couldn't open formdata file: $!\n";
	print $FORMFILE_2 ("BV_SessionID=" . $sms_hidden_1 .
	                 "\&BV_EngineID=" . $sms_hidden_2 .
	                 "\&pageTypeId=" . $sms_hidden_3 .
	                 "\&programId=" . $sms_hidden_4 .
	                 "\&channelId=" . $sms_hidden_5 .
	                 "\&message=" . $sms_message .
	                 "\&receiverNumber=" . $sms_reciever );
	close $FORMFILE_2;
	my($params_2) = "\@$tmp_formdata";

	dispText("Preparing SMS ...\n");
  my(@sms_page) = getPage($sms_script, $params_2, 1, 1, 0);
   
   # Last step
   $page = join "", @sms_page;
   my $sms_send_script;
   my $second_prefix = "/190/fsms/send.do";
   if ($page =~ m/<a href=\"send\.do?(\S+)\">/i) {
      dispText("Finding last URL...\n");
      $sms_send_script = $prefix . $second_prefix . $1;
   } else {
	    dispText("ERROR: No URL found, quitting!");
			return 1;
	 }
 
   dispText("Sending SMS ...\n");
   my(@sms_result_page) = getPage($sms_send_script, "", 1, 1, 0);
   
	dispText("Logging out ...\n");
	my $logoutUrl = "http://www.areaprivati.190.it/190/trilogy/jsp/logout.do?tk=9604,t";
	my(@logoutPage) = getPage($logoutUrl, "", 1, 1, 0);
  $page = join "", @sms_result_page;
#   print $page;

   return 1;
}

# just for debug:
#print <STDIN>;

parseConfig();
parseArgs(<STDIN>);
dispIntroText();
doCheckPrograms();
#doCleanOtherFiles();
#dispText("Reciever: ".$conf_reciever."\n\nYour message:\n".$conf_message."\n");
SendSMS($conf_message,$conf_reciever,$conf_signature);
dispText("\nAll done!\n");
doCleanTempFiles();

exit;

# vim:noet:sw=2:ts=2:filetype=perl
