sap-vpn.pl
changeset 3 01314d620fe0
parent 2 5f08067bc677
child 5 9e64828e7d70
equal deleted inserted replaced
2:5f08067bc677 3:01314d620fe0
       
     1 #! /usr/bin/perl -T
       
     2 # $Id$
       
     3 # $URL$
       
     4 # based on a version Uwe Werler downloaded from f5networks developer
       
     5 # area http://devcentral.f5.com/SDK/sslvpn.public.pl.txt
       
     6 
       
     7 use 5.8.8;
       
     8 use strict;
       
     9 use warnings;
       
    10 use Getopt::Long qw(:config no_ignore_case bundling);
       
    11 use Pod::Usage;
       
    12 use File::Basename;
       
    13 use English qw(-no_match_vars);
       
    14 
       
    15 ($EUID, $UID) = ($UID, $EUID);    # release ROOT, doesn't harm, if not suid
       
    16 ($0) = ($0 =~ /([\w-]+)/);        # untaint $0
       
    17 
       
    18 use constant ME => basename $0;
       
    19 
       
    20 delete @ENV{ grep /PATH/, keys %ENV };
       
    21 $ENV{PATH} = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin";
       
    22 
       
    23 ###
       
    24 ### STEP 0 :: Verify that we have the necessary requirements.
       
    25 ###
       
    26 ### This script requires recent versions of Perl, OpenSSL and PPPD (including
       
    27 ### the 'chat' program), and that they all be in our PATH.  This script was
       
    28 ### written and tested with the following versions:
       
    29 ###	FirePass:	5.5 and 6.0
       
    30 ###	Perl:		5.8.8
       
    31 ###	OpenSSL:	0.9.8b
       
    32 ###	PPPD:		2.4.4
       
    33 
       
    34 ###
       
    35 ### STEP 1 :: Set up variables with the proper information to log in.
       
    36 ###
       
    37 
       
    38 # Default values for the FQDN or IP of the FirePass we wish to connect to, the
       
    39 # name of our Network Access favorite, and our username/pasword.  All of these
       
    40 # can be passed as arguments, if desired.
       
    41 
       
    42 my $opt_user;
       
    43 my $opt_passcode;
       
    44 my $opt_host     = 'connectwdf.sap.com';
       
    45 my $opt_name     = 'SAP Network Access';
       
    46 my $opt_linkname = basename $0;
       
    47 my $opt_help     = 0;
       
    48 my $opt_man      = 0;
       
    49 my @ppp_opts     = ();
       
    50 my $opt_debug    = 0;
       
    51 my $opt_verbose  = 0;
       
    52 my $opt_script;
       
    53 my $opt_ppp = 1;
       
    54 
       
    55 sub untaint($) {
       
    56     my ($x) = ($_[0] =~ /([\/\w.-]+)/i);
       
    57     return $_[0] = $_[0] eq $x ? $x : undef;
       
    58 }
       
    59 
       
    60 sub verbose(@) { print STDERR @_ if $opt_verbose }
       
    61 sub debug(@)   { warn @_         if $opt_debug }
       
    62 
       
    63 MAIN: {
       
    64 
       
    65     GetOptions(
       
    66         "u|user=s"     => \$opt_user,
       
    67         "p|passcode=s" => \$opt_passcode,
       
    68         "H|host=s"     => \$opt_host,
       
    69         "n|favorite=s" => \$opt_name,
       
    70         "d|debug"      => \$opt_debug,
       
    71         "o|opts=s"     => \@ppp_opts,
       
    72         "h|help"       => \$opt_help,
       
    73         "m|man"        => \$opt_man,
       
    74         "s|script=s"   => \$opt_script,
       
    75         "l|linkname"   => \$opt_linkname,
       
    76         "ppp!"         => \$opt_ppp,
       
    77         "v|verbose"    => \$opt_verbose,
       
    78     ) or pod2usage();
       
    79 
       
    80     pod2usage(-verbose => 1, -exitval => 0) if $opt_help;
       
    81     pod2usage(-verbose => 2, -exitval => 0) if $opt_man;
       
    82     pod2usage(-verbose => 0, -exitval => 0) if not defined $opt_user;
       
    83 
       
    84     $opt_verbose += $opt_debug;
       
    85 
       
    86     untaint($opt_user)
       
    87       or die ME . ": username \"$opt_user\" didn't pass verification\n";
       
    88 
       
    89     die ME . ": need to run with root permissions!\n"
       
    90       if not $EUID == 0 || $UID == 0;
       
    91 
       
    92     if (not defined $opt_passcode) {
       
    93         chomp(my $settings = qx{stty "-g"});
       
    94         open(IN, "/dev/tty") or die ME . ": Can't open /dev/tty: $!\n";
       
    95         print "Passcode for $opt_user: ";
       
    96         system stty => "-echo";
       
    97         chomp($opt_passcode = <IN>);
       
    98         system stty => $settings;
       
    99         print "\n";
       
   100     }
       
   101 
       
   102     pod2usage(-verbose => 0, -exitval => 0) if not defined $opt_passcode;
       
   103 
       
   104     untaint($opt_passcode) or die ME . ": passcode didn't pass verification\n";
       
   105     untaint($opt_linkname) or die ME . ": linkname didn't pass verification\n";
       
   106     untaint($opt_script) or die ME . ": script name didn't pass verification\n";
       
   107 
       
   108     -x $opt_script
       
   109       or die ME . ": Script $opt_script is not executable: $!\n"
       
   110       if defined $opt_script;
       
   111 
       
   112     push @ppp_opts, $opt_debug ? qw(nodetach debug dump) : qw(nolog updetach);
       
   113     push @ppp_opts, "nodefaultroute" if not grep /^defaultroute$/, @ppp_opts;
       
   114     push @ppp_opts, "logfd", 2 if $opt_verbose;
       
   115     push @ppp_opts, "dryrun" if not $opt_ppp;
       
   116     verbose "% PPP options: @ppp_opts\n";
       
   117 
       
   118     # Declare variables used throughout the rest of the script.
       
   119     my ($request, $response, $sessionid, $favorite);
       
   120 
       
   121     # Store the OpenSSL command in a variable for convienence.
       
   122     my $openssl =
       
   123         "openssl s_client"
       
   124       . (-d "/etc/ssl/certs" ? " -CApath /etc/ssl/certs" : "")
       
   125       . " -ign_eof -quiet -connect ${opt_host}:443";
       
   126 
       
   127     # Make initial request to get client_data
       
   128     verbose "% initial request\n";
       
   129     $request =
       
   130         "GET /my.logon.php3?check=1 HTTP/1.0\r\n"
       
   131       . "Content-Type: application/x-www-form-urlencoded\r\n"
       
   132       . "Connection: close\r\n" . "\r\n";
       
   133 
       
   134     $response = qx(echo "${request}" | ${openssl} 2>/dev/null)
       
   135       or die ME . ": Invalid Host specified or not reachable: $opt_host\n";
       
   136 
       
   137     $response =~ s/<INPUT type="hidden" name="client_data" value="(.*)">/$1/;
       
   138     my $client_data = $1;
       
   139 
       
   140 ###
       
   141 ### STEP 2 :: Log in to FirePass.
       
   142 ###
       
   143 
       
   144     verbose "% login into firepass\n";
       
   145 
       
   146   # This is the bare minimum required in order to successfully log in.  A normal
       
   147   # browser will make many more requests than this to complete the log in
       
   148   # sequence, but all that is required is this POST with our credentails.  This
       
   149   # may fail if the FirePass has End-Point Security Policies configured.
       
   150     $request =
       
   151 "check=1&username=${opt_user}&password=${opt_passcode}&mrhlogonform=1&client_data=${client_data}";
       
   152 
       
   153     $request =
       
   154         "POST /my.activation.php3 HTTP/1.0\r\n"
       
   155       . "Host: ${opt_host}\r\n"
       
   156       . "Content-Type: application/x-www-form-urlencoded\r\n"
       
   157       . "Content-Length: "
       
   158       . length($request) . "\r\n"
       
   159       . "Connection: close\r\n" . "\r\n"
       
   160       . "${request}\r\n";
       
   161 
       
   162     $response = qx(echo "${request}" | ${openssl} 2>/dev/null);
       
   163 
       
   164   # We can then parse the response for the MRHSession Cookie, which contains our
       
   165   # SessionID.  In this example, we print out the SessionID in order to verify
       
   166   # that our log in attempt worked.
       
   167     $response =~ /MRHSession=(\w+);/;
       
   168     $sessionid = $1;
       
   169     verbose "% session id ${sessionid}\n";
       
   170 
       
   171 ###
       
   172 ### STEP 3 :: Create the SSL VPN tunnel.
       
   173 ###
       
   174 
       
   175     verbose "% creating the ssl tunnel\n";
       
   176 
       
   177    # Now that we are authenticated and have a valid SessionID, we must request
       
   178    # specific pages/objects in order to initiate a SSL VPN tunnel.  Before we do
       
   179    # this, let's determine the resource locator for our Network Access favorite.
       
   180     $request = "GET /vdesk/vpn/index.php3?outform=xml HTTP/1.0\r\n"
       
   181       . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
       
   182     $response = qx(echo "${request}" | ${openssl} 2>/dev/null);
       
   183 
       
   184  # The response is XML, so we can safely grab what we are looking for using some
       
   185  # regular expression magic.  Same with the SessionID, we're printing out the
       
   186  # final value to make sure we're on the right track.
       
   187     $response =~ /${opt_name}[^\n]+\n[^Z]+Z=\d+,(\d+)/;
       
   188     $favorite = $1;
       
   189     verbose "% favorite ${favorite}\n";
       
   190 
       
   191     # We're all set!  Let's visit the necessary pages/objects to notify FirePass
       
   192     # that we wish to open a SSL VPN tunnel.
       
   193     foreach my $uri ("/vdesk/", "/vdesk/vpn/connect.php3?Z=0,${favorite}",) {
       
   194         $request = "GET ${uri} HTTP/1.0\r\n"
       
   195           . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
       
   196         system("echo \"${request}\" | ${openssl} >/dev/null 2>&1");
       
   197     }
       
   198 
       
   199 # We are now authenticated, and have requested the necessary pre-tunnel
       
   200 # pages/objects.  It's time to start our SSL VPN connection.  To do this, we are
       
   201 # simply calling PPPD, and having it use OpenSSL as a psuedo terminal device.
       
   202     $request = "GET /myvpn?sess=${sessionid} HTTP/1.0\r\n"
       
   203       . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
       
   204     $request = "chat -v '' '${request}'";
       
   205 
       
   206     {
       
   207         my $pid = fork();    # this way we've better control
       
   208         die ME . ": can't fork: $!\n" if not defined $pid;
       
   209 
       
   210         if ($pid == 0) {
       
   211             $EUID = $UID;    # get ROOT, the pppd want's to see
       
   212                              # the UID and the EUID to be ROOT
       
   213             exec(
       
   214                 pppd     => @ppp_opts,
       
   215                 pty      => ${openssl},
       
   216                 connect  => ${request},
       
   217                 linkname => $opt_linkname,
       
   218                 qw(noauth crtscts passive noproxyarp local)
       
   219             ) or die ME . ": Can't exec: $!\n";
       
   220 
       
   221         }
       
   222         wait;
       
   223 	die ME . ": pppd didn't return sucessfully (rc: @{[$? >> 8]})\n" if $?;
       
   224     }
       
   225 
       
   226     # now executing the script
       
   227     # if there is some script name supplied
       
   228 
       
   229     if (defined $opt_script and not $opt_debug) {
       
   230 
       
   231         my $pid = fork();
       
   232         die ME . ": Can't fork: $!\n" if not defined $pid;
       
   233 
       
   234         if ($pid == 0) {    # child
       
   235 
       
   236             if ($UID != $EUID) {
       
   237                 my ($owner, $group) = (stat $opt_script)[ 4, 5 ];
       
   238                 die ME . ": can't stat $opt_script\n" if not defined $owner;
       
   239 
       
   240                 $UID = $EUID = $owner;
       
   241                 $GID = $EGID = $group;
       
   242             }
       
   243 
       
   244             verbose "% executing script $opt_script with uid $UID and gid $GID\n";
       
   245 
       
   246             exec $opt_script
       
   247               or die ME . ": Can't exec $opt_script: $!\n";
       
   248         }
       
   249         $pid = wait;
       
   250         verbose "% script returned $?\n";
       
   251         exit;
       
   252     }
       
   253 
       
   254     # Voila!  We should now have a PPP connection running over SSL.  We can exit
       
   255     # from this script cleanly, and move on to setting up routes to the remote
       
   256     # network using our favorite networking tools.  Happy hacking!
       
   257 
       
   258     exit(0);
       
   259 
       
   260 }
       
   261 
       
   262 __END__
       
   263 
       
   264 =head1 SYNOPSIS
       
   265 
       
   266  sap-vpn [-d|--debug] [-v|--verbose] [--[no]ppp] 
       
   267      [-o|--opt <ppp option>]
       
   268      [-u|--user <user>] [-p|--passcode <passcode>]
       
   269      [-H|--host <host>]
       
   270      [-s|--script <script>]
       
   271 
       
   272  sap-vpn [-h|--help]
       
   273  sap-vpn [-m|--man]
       
   274 
       
   275 =head1 DESCRIPTION
       
   276 
       
   277 Ths script establishes a VPN connecttion to a FirePass server.
       
   278 B<Note:> This script requires B<root> privileges and should be run via sudo
       
   279 or some other approbiate mechanism. (You may install it SUID root. This
       
   280 is the safest way, since the script rises its privileges only when
       
   281 necessary, instead of operating all the time as root.)
       
   282 
       
   283 =head1 OPTIONS
       
   284 
       
   285 =over
       
   286 
       
   287 =item B<-d>
       
   288 
       
   289 Print debug output from pppd. In this mode the script (and the pppd)
       
   290 will not detach from your terminal and the script code will not be
       
   291 executed.  (default: 0)
       
   292 
       
   293 =item B<-h>|B<--help>
       
   294 
       
   295 A short help. (defualt: off)
       
   296 
       
   297 =item B<-H>|B<--host> I<host>
       
   298 
       
   299 Host to connect to. (defaults to C<connectwdf.sap.corp>)
       
   300 
       
   301 =item B<-l>|B<--linkname> I<linkname>
       
   302 
       
   303 The name for the PPP link. Only word characters and "-" (minus sign) are
       
   304 allowed. (default: base name of this script)
       
   305 
       
   306 =item B<-m>|B<--man>
       
   307 
       
   308 This man page. (default: off)
       
   309 
       
   310 =item B<-n>|B<--name> I<name>
       
   311 
       
   312 Name of the service(?). (default: to 'SAP Network Access')
       
   313 
       
   314 =item B<-o>|B<--opts> I<ppp options>
       
   315 
       
   316 Additional options for ppp. Can be specified multiple times. (default: nodetach)
       
   317 Good choices are C<detach>, or C<updetach>.
       
   318 B<Note:> The C<nodefaultroute> is set automatically.
       
   319 
       
   320 =item B<-p>|B<--passcode> I<passcode>
       
   321 
       
   322 Your passcode, if not supplied, F</dev/tty> is opened for reading
       
   323 the passcode. (no default)
       
   324 
       
   325 =item B<--[no]ppp>
       
   326 
       
   327 Start (don't start) the PPP daemon. This option is for debugging purpose
       
   328 only. (default: on)
       
   329 
       
   330 =item B<-s>|B<--script> I<script>
       
   331 
       
   332 Script to execute after establishing the IP link.  If we're running as
       
   333 SUID-root, this script is executed under the effective UID and GID of
       
   334 its owner, otherwise just with the permissions we have. (See the B<-d>
       
   335 option above!). (no default)
       
   336 
       
   337     #! /bin/bash
       
   338     # How to know the interface name and the local/remove
       
   339     # ip addresses?
       
   340     # Please se below.
       
   341 
       
   342 B<Note:> You should consider using the F</etc/ppp/ip-up.d/> scripts in
       
   343 favour of this option!  The of the "ip-up"-script is having access to
       
   344 all the environment variables set by the PPP daemon:
       
   345 
       
   346     #! /bin/bash
       
   347     # example /etc/ppp/ip-up.d/<SCRIPT>
       
   348     test "$LINKNAME" = "sap-vpn" || exit 0
       
   349     ip route add 10.21.0.0/16 dev $PPP_IFACE
       
   350     ip route add 10.17.0.0/16 dev $PPP_IFACE
       
   351 
       
   352 =item B<-u>|B<--user> I<user>
       
   353 
       
   354 Your D|C|I user, not really optional. (no default)
       
   355 
       
   356 =back
       
   357 
       
   358 =cut
       
   359 
       
   360 ###
       
   361 ### End of file.
       
   362 ###