# HG changeset patch # User heiko # Date 1226610562 0 # Node ID 01314d620fe0399b5d99558071c573db3d5d7a1e # Parent 5f08067bc677a34e0ac66f57831b54db09fee18a - added verbose option - more secure about script execution - implemented script execution - using "updetach" option diff -r 5f08067bc677 -r 01314d620fe0 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Thu Nov 13 21:09:22 2008 +0000 @@ -0,0 +1,25 @@ +# $Id$ +# $URL$ + +prefix = /usr/local +bindir = ${prefix}/bin +DESTDIR = + + +SCRIPT = sap-vpn +CLEANFILES = $(SCRIPT) + +.PHONLY: clean install + +all: $(SCRIPT) + +clean: + rm -f $(CLEANFILES) + +install: all + install -m 0755 -d $(DESTDIR)${bindir}/ + install -m 04555 -o root $(SCRIPT) $(DESTDIR)${bindir}/ + +sap-vpn: sap-vpn.pl + install -m 0555 $< $@ + diff -r 5f08067bc677 -r 01314d620fe0 sap-vpn.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sap-vpn.pl Thu Nov 13 21:09:22 2008 +0000 @@ -0,0 +1,362 @@ +#! /usr/bin/perl -T +# $Id$ +# $URL$ +# based on a version Uwe Werler downloaded from f5networks developer +# area http://devcentral.f5.com/SDK/sslvpn.public.pl.txt + +use 5.8.8; +use strict; +use warnings; +use Getopt::Long qw(:config no_ignore_case bundling); +use Pod::Usage; +use File::Basename; +use English qw(-no_match_vars); + +($EUID, $UID) = ($UID, $EUID); # release ROOT, doesn't harm, if not suid +($0) = ($0 =~ /([\w-]+)/); # untaint $0 + +use constant ME => basename $0; + +delete @ENV{ grep /PATH/, keys %ENV }; +$ENV{PATH} = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin"; + +### +### STEP 0 :: Verify that we have the necessary requirements. +### +### This script requires recent versions of Perl, OpenSSL and PPPD (including +### the 'chat' program), and that they all be in our PATH. This script was +### written and tested with the following versions: +### FirePass: 5.5 and 6.0 +### Perl: 5.8.8 +### OpenSSL: 0.9.8b +### PPPD: 2.4.4 + +### +### STEP 1 :: Set up variables with the proper information to log in. +### + +# Default values for the FQDN or IP of the FirePass we wish to connect to, the +# name of our Network Access favorite, and our username/pasword. All of these +# can be passed as arguments, if desired. + +my $opt_user; +my $opt_passcode; +my $opt_host = 'connectwdf.sap.com'; +my $opt_name = 'SAP Network Access'; +my $opt_linkname = basename $0; +my $opt_help = 0; +my $opt_man = 0; +my @ppp_opts = (); +my $opt_debug = 0; +my $opt_verbose = 0; +my $opt_script; +my $opt_ppp = 1; + +sub untaint($) { + my ($x) = ($_[0] =~ /([\/\w.-]+)/i); + return $_[0] = $_[0] eq $x ? $x : undef; +} + +sub verbose(@) { print STDERR @_ if $opt_verbose } +sub debug(@) { warn @_ if $opt_debug } + +MAIN: { + + GetOptions( + "u|user=s" => \$opt_user, + "p|passcode=s" => \$opt_passcode, + "H|host=s" => \$opt_host, + "n|favorite=s" => \$opt_name, + "d|debug" => \$opt_debug, + "o|opts=s" => \@ppp_opts, + "h|help" => \$opt_help, + "m|man" => \$opt_man, + "s|script=s" => \$opt_script, + "l|linkname" => \$opt_linkname, + "ppp!" => \$opt_ppp, + "v|verbose" => \$opt_verbose, + ) or pod2usage(); + + pod2usage(-verbose => 1, -exitval => 0) if $opt_help; + pod2usage(-verbose => 2, -exitval => 0) if $opt_man; + pod2usage(-verbose => 0, -exitval => 0) if not defined $opt_user; + + $opt_verbose += $opt_debug; + + untaint($opt_user) + or die ME . ": username \"$opt_user\" didn't pass verification\n"; + + die ME . ": need to run with root permissions!\n" + if not $EUID == 0 || $UID == 0; + + if (not defined $opt_passcode) { + chomp(my $settings = qx{stty "-g"}); + open(IN, "/dev/tty") or die ME . ": Can't open /dev/tty: $!\n"; + print "Passcode for $opt_user: "; + system stty => "-echo"; + chomp($opt_passcode = ); + system stty => $settings; + print "\n"; + } + + pod2usage(-verbose => 0, -exitval => 0) if not defined $opt_passcode; + + untaint($opt_passcode) or die ME . ": passcode didn't pass verification\n"; + untaint($opt_linkname) or die ME . ": linkname didn't pass verification\n"; + untaint($opt_script) or die ME . ": script name didn't pass verification\n"; + + -x $opt_script + or die ME . ": Script $opt_script is not executable: $!\n" + if defined $opt_script; + + push @ppp_opts, $opt_debug ? qw(nodetach debug dump) : qw(nolog updetach); + push @ppp_opts, "nodefaultroute" if not grep /^defaultroute$/, @ppp_opts; + push @ppp_opts, "logfd", 2 if $opt_verbose; + push @ppp_opts, "dryrun" if not $opt_ppp; + verbose "% PPP options: @ppp_opts\n"; + + # Declare variables used throughout the rest of the script. + my ($request, $response, $sessionid, $favorite); + + # Store the OpenSSL command in a variable for convienence. + my $openssl = + "openssl s_client" + . (-d "/etc/ssl/certs" ? " -CApath /etc/ssl/certs" : "") + . " -ign_eof -quiet -connect ${opt_host}:443"; + + # Make initial request to get client_data + verbose "% initial request\n"; + $request = + "GET /my.logon.php3?check=1 HTTP/1.0\r\n" + . "Content-Type: application/x-www-form-urlencoded\r\n" + . "Connection: close\r\n" . "\r\n"; + + $response = qx(echo "${request}" | ${openssl} 2>/dev/null) + or die ME . ": Invalid Host specified or not reachable: $opt_host\n"; + + $response =~ s//$1/; + my $client_data = $1; + +### +### STEP 2 :: Log in to FirePass. +### + + verbose "% login into firepass\n"; + + # This is the bare minimum required in order to successfully log in. A normal + # browser will make many more requests than this to complete the log in + # sequence, but all that is required is this POST with our credentails. This + # may fail if the FirePass has End-Point Security Policies configured. + $request = +"check=1&username=${opt_user}&password=${opt_passcode}&mrhlogonform=1&client_data=${client_data}"; + + $request = + "POST /my.activation.php3 HTTP/1.0\r\n" + . "Host: ${opt_host}\r\n" + . "Content-Type: application/x-www-form-urlencoded\r\n" + . "Content-Length: " + . length($request) . "\r\n" + . "Connection: close\r\n" . "\r\n" + . "${request}\r\n"; + + $response = qx(echo "${request}" | ${openssl} 2>/dev/null); + + # We can then parse the response for the MRHSession Cookie, which contains our + # SessionID. In this example, we print out the SessionID in order to verify + # that our log in attempt worked. + $response =~ /MRHSession=(\w+);/; + $sessionid = $1; + verbose "% session id ${sessionid}\n"; + +### +### STEP 3 :: Create the SSL VPN tunnel. +### + + verbose "% creating the ssl tunnel\n"; + + # Now that we are authenticated and have a valid SessionID, we must request + # specific pages/objects in order to initiate a SSL VPN tunnel. Before we do + # this, let's determine the resource locator for our Network Access favorite. + $request = "GET /vdesk/vpn/index.php3?outform=xml HTTP/1.0\r\n" + . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n"; + $response = qx(echo "${request}" | ${openssl} 2>/dev/null); + + # The response is XML, so we can safely grab what we are looking for using some + # regular expression magic. Same with the SessionID, we're printing out the + # final value to make sure we're on the right track. + $response =~ /${opt_name}[^\n]+\n[^Z]+Z=\d+,(\d+)/; + $favorite = $1; + verbose "% favorite ${favorite}\n"; + + # We're all set! Let's visit the necessary pages/objects to notify FirePass + # that we wish to open a SSL VPN tunnel. + foreach my $uri ("/vdesk/", "/vdesk/vpn/connect.php3?Z=0,${favorite}",) { + $request = "GET ${uri} HTTP/1.0\r\n" + . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n"; + system("echo \"${request}\" | ${openssl} >/dev/null 2>&1"); + } + +# We are now authenticated, and have requested the necessary pre-tunnel +# pages/objects. It's time to start our SSL VPN connection. To do this, we are +# simply calling PPPD, and having it use OpenSSL as a psuedo terminal device. + $request = "GET /myvpn?sess=${sessionid} HTTP/1.0\r\n" + . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n"; + $request = "chat -v '' '${request}'"; + + { + my $pid = fork(); # this way we've better control + die ME . ": can't fork: $!\n" if not defined $pid; + + if ($pid == 0) { + $EUID = $UID; # get ROOT, the pppd want's to see + # the UID and the EUID to be ROOT + exec( + pppd => @ppp_opts, + pty => ${openssl}, + connect => ${request}, + linkname => $opt_linkname, + qw(noauth crtscts passive noproxyarp local) + ) or die ME . ": Can't exec: $!\n"; + + } + wait; + die ME . ": pppd didn't return sucessfully (rc: @{[$? >> 8]})\n" if $?; + } + + # now executing the script + # if there is some script name supplied + + if (defined $opt_script and not $opt_debug) { + + my $pid = fork(); + die ME . ": Can't fork: $!\n" if not defined $pid; + + if ($pid == 0) { # child + + if ($UID != $EUID) { + my ($owner, $group) = (stat $opt_script)[ 4, 5 ]; + die ME . ": can't stat $opt_script\n" if not defined $owner; + + $UID = $EUID = $owner; + $GID = $EGID = $group; + } + + verbose "% executing script $opt_script with uid $UID and gid $GID\n"; + + exec $opt_script + or die ME . ": Can't exec $opt_script: $!\n"; + } + $pid = wait; + verbose "% script returned $?\n"; + exit; + } + + # Voila! We should now have a PPP connection running over SSL. We can exit + # from this script cleanly, and move on to setting up routes to the remote + # network using our favorite networking tools. Happy hacking! + + exit(0); + +} + +__END__ + +=head1 SYNOPSIS + + sap-vpn [-d|--debug] [-v|--verbose] [--[no]ppp] + [-o|--opt ] + [-u|--user ] [-p|--passcode ] + [-H|--host ] + [-s|--script