sap-vpn.pl
changeset 11 16e6b1683c63
parent 10 13b84a92a65a
child 12 8cd92042fb8b
child 14 5682bf09533c
equal deleted inserted replaced
10:13b84a92a65a 11:16e6b1683c63
     9 use warnings;
     9 use warnings;
    10 use Getopt::Long qw(:config no_ignore_case bundling);
    10 use Getopt::Long qw(:config no_ignore_case bundling);
    11 use Pod::Usage;
    11 use Pod::Usage;
    12 use File::Basename;
    12 use File::Basename;
    13 use English qw(-no_match_vars);
    13 use English qw(-no_match_vars);
       
    14 use lib ".";
       
    15 
       
    16 my $use_lwp = -f "lwp";
    14 
    17 
    15 ($EUID, $UID) = ($UID, $EUID);    # release ROOT, doesn't harm, if not suid
    18 ($EUID, $UID) = ($UID, $EUID);    # release ROOT, doesn't harm, if not suid
    16 ($0) = ($0 =~ /([.\/\w-]+)/);     # untaint $0
    19 ($0) = ($0 =~ /([.\/\w-]+)/);     # untaint $0
    17 
    20 
    18 use constant ME => basename $0;
    21 use constant ME => basename $0;
    19 
       
    20 
    22 
    21 delete @ENV{ grep /PATH/, keys %ENV };
    23 delete @ENV{ grep /PATH/, keys %ENV };
    22 $ENV{PATH} = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin";
    24 $ENV{PATH} = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin";
    23 
    25 
    24 ###
    26 ###
    61     return $$ref = (defined $x and $$ref eq $x) ? $x : undef;
    63     return $$ref = (defined $x and $$ref eq $x) ? $x : undef;
    62 }
    64 }
    63 
    65 
    64 sub verbose(@) { print STDERR @_ if $opt_verbose }
    66 sub verbose(@) { print STDERR @_ if $opt_verbose }
    65 sub debug(@)   { warn @_         if $opt_debug }
    67 sub debug(@)   { warn @_         if $opt_debug }
       
    68 sub ohshit(@) { die ME . ": ", @_ }
    66 sub do_kill($);
    69 sub do_kill($);
    67 
    70 
    68 MAIN: {
    71 MAIN: {
    69 
    72 
    70     GetOptions(
    73     GetOptions(
    89       if not defined $opt_user
    92       if not defined $opt_user
    90           and not defined $opt_kill;
    93           and not defined $opt_kill;
    91 
    94 
    92     $opt_verbose += $opt_debug;
    95     $opt_verbose += $opt_debug;
    93 
    96 
    94     untaint($opt_linkname) or die ME . ": linkname didn't pass verification\n";
    97     untaint($opt_linkname) or ohshit "linkname didn't pass verification\n";
    95     untaint($opt_script)
    98     untaint($opt_script)
    96       or die ME . ": script name didn't pass verification\n"
    99       or ohshit "script name didn't pass verification\n"
    97       if $opt_script;
   100       if $opt_script;
    98     map { untaint } @ppp_opts;
   101     map { untaint } @ppp_opts;
    99 
   102 
   100     die ME . ": need to run with root permissions!\n"
   103     ohshit "need to run with root permissions!\n"
   101       if not $EUID == 0 || $UID == 0;
   104       if not $EUID == 0 || $UID == 0;
   102 
   105 
   103     exit do_kill($opt_linkname) if $opt_kill;
   106     exit do_kill($opt_linkname) if $opt_kill;
   104 
   107 
   105     untaint($opt_user)
   108     untaint($opt_user)
   106       or die ME . ": username didn't pass verification\n";
   109       or ohshit "username didn't pass verification\n";
   107 
   110 
   108     if (not defined $opt_passcode) {
   111     if (not defined $opt_passcode) {
   109         chomp(my $settings = qx{stty "-g"});
   112         chomp(my $settings = qx{stty "-g"});
   110         open(IN, "/dev/tty") or die ME . ": Can't open /dev/tty: $!\n";
   113         open(IN, "/dev/tty") or ohshit "Can't open /dev/tty: $!\n";
   111         print "Passcode for $opt_user: ";
   114         print "Passcode for $opt_user: ";
   112         system stty => "-echo";
   115         system stty => "-echo";
   113         chomp($opt_passcode = <IN>);
   116         chomp($opt_passcode = <IN>);
   114         system stty => $settings;
   117         system stty => $settings;
   115         print "\n";
   118         print "\n";
   116     }
   119     }
   117     untaint($opt_passcode) or die ME . ": passcode didn't pass verification\n";
   120     untaint($opt_passcode) or ohshit "passcode didn't pass verification\n";
   118     pod2usage(-verbose => 0, -exitval => 0) if not defined $opt_passcode;
   121     pod2usage(-verbose => 0, -exitval => 0) if not defined $opt_passcode;
   119 
   122 
   120     -x $opt_script
   123     -x $opt_script
   121       or die ME . ": Script $opt_script is not executable: $!\n"
   124       or ohshit "Script $opt_script is not executable: $!\n"
   122       if defined $opt_script;
   125       if defined $opt_script;
   123 
   126 
   124     push @ppp_opts, $opt_debug ? qw(nodetach debug dump) : qw(nolog updetach);
   127     push @ppp_opts, $opt_debug ? qw(nodetach debug dump) : qw(nolog updetach);
   125     push @ppp_opts, "nodefaultroute" if not grep /^defaultroute$/, @ppp_opts;
   128     push @ppp_opts, "nodefaultroute" if not grep /^defaultroute$/, @ppp_opts;
   126     push @ppp_opts, "logfd", 2 if $opt_verbose;
   129     push @ppp_opts, "logfd", 2 if $opt_verbose;
   136       . (-d "/etc/ssl/certs" ? " -CApath /etc/ssl/certs" : "")
   139       . (-d "/etc/ssl/certs" ? " -CApath /etc/ssl/certs" : "")
   137       . " -ign_eof -quiet -connect ${opt_host}:443";
   140       . " -ign_eof -quiet -connect ${opt_host}:443";
   138 
   141 
   139     # Make initial request to get client_data
   142     # Make initial request to get client_data
   140     verbose "% initial request\n";
   143     verbose "% initial request\n";
   141     $request =
   144 
   142         "GET /my.logon.php3?check=1 HTTP/1.0\r\n"
   145     my $client_data;
   143       . "Content-Type: application/x-www-form-urlencoded\r\n"
   146     my $ua;
   144       . "Connection: close\r\n" . "\r\n";
   147     my $cookies;
   145 
   148 
   146     $response = qx(echo "${request}" | ${openssl} 2>/dev/null)
   149     if (!$use_lwp) {
   147       or die ME . ": Invalid Host specified or not reachable: $opt_host\n";
   150         $request =
       
   151             "GET /my.logon.php3?check=1 HTTP/1.0\r\n"
       
   152           . "Content-Type: application/x-www-form-urlencoded\r\n"
       
   153           . "Connection: close\r\n" . "\r\n";
       
   154 
       
   155         $response = qx(echo "${request}" | ${openssl} 2>/dev/null)
       
   156           or ohshit "Invalid Host specified or not reachable: $opt_host\n";
       
   157 
       
   158     }
       
   159     else {
       
   160         use LWP::UserAgent;
       
   161         use HTTP::Request::Common;
       
   162         use HTTP::Status;
       
   163 	use HTTP::Cookies;
       
   164 	#use LWP::Debug qw(+);
       
   165 	$cookies = new HTTP::Cookies;
       
   166         $ua = new LWP::UserAgent;
       
   167         $ua->agent("");
       
   168         $ua->env_proxy;
       
   169 	$ua->cookie_jar($cookies);
       
   170         $response = $ua->get("https://$opt_host:443/my.logon.php3?check=1");
       
   171 
       
   172         ohshit "initial failed with http code @{[$response->message]}\n"
       
   173           unless $response->is_success;
       
   174 
       
   175         $response = $response->as_string;
       
   176     }
   148 
   177 
   149     $response =~ s/<INPUT type="hidden" name="client_data" value="(.*)">/$1/;
   178     $response =~ s/<INPUT type="hidden" name="client_data" value="(.*)">/$1/;
   150     my $client_data = $1;
   179     $client_data = $1;
   151 
   180 
   152 ###
   181 ###
   153 ### STEP 2 :: Log in to FirePass.
   182 ### STEP 2 :: Log in to FirePass.
   154 ###
   183 ###
   155 
   184 
   157 
   186 
   158   # This is the bare minimum required in order to successfully log in.  A normal
   187   # This is the bare minimum required in order to successfully log in.  A normal
   159   # browser will make many more requests than this to complete the log in
   188   # browser will make many more requests than this to complete the log in
   160   # sequence, but all that is required is this POST with our credentails.  This
   189   # sequence, but all that is required is this POST with our credentails.  This
   161   # may fail if the FirePass has End-Point Security Policies configured.
   190   # may fail if the FirePass has End-Point Security Policies configured.
   162     $request =
   191 
       
   192     if (!$use_lwp) {
       
   193         $request =
   163 "check=1&username=${opt_user}&password=${opt_passcode}&mrhlogonform=1&client_data=${client_data}";
   194 "check=1&username=${opt_user}&password=${opt_passcode}&mrhlogonform=1&client_data=${client_data}";
   164 
   195 
   165     $request =
   196         $request =
   166         "POST /my.activation.php3 HTTP/1.0\r\n"
   197             "POST /my.activation.php3 HTTP/1.0\r\n"
   167       . "Host: ${opt_host}\r\n"
   198           . "Host: ${opt_host}\r\n"
   168       . "Content-Type: application/x-www-form-urlencoded\r\n"
   199           . "Content-Type: application/x-www-form-urlencoded\r\n"
   169       . "Content-Length: "
   200           . "Content-Length: "
   170       . length($request) . "\r\n"
   201           . length($request) . "\r\n"
   171       . "Connection: close\r\n" . "\r\n"
   202           . "Connection: close\r\n" . "\r\n"
   172       . "${request}\r\n";
   203           . "${request}\r\n";
   173 
   204 
   174     $response = qx(echo "${request}" | ${openssl} 2>/dev/null);
   205         $response = qx(echo "${request}" | ${openssl} 2>/dev/null);
       
   206     }
       
   207     else {
       
   208         $response = $ua->post(
       
   209             "https://$opt_host:443/my.activation.php3",
       
   210             check        => 1,
       
   211             username     => $opt_user,
       
   212             password     => $opt_passcode,
       
   213             mrhlogonform => 1,
       
   214             client_data  => $client_data
       
   215         );
       
   216 
       
   217         ohshit "login failed with http code @{[$response->message]}\n"
       
   218           unless $response->is_success;
       
   219 
       
   220         $response = $response->as_string;
       
   221     }
   175 
   222 
   176   # We can then parse the response for the MRHSession Cookie, which contains our
   223   # We can then parse the response for the MRHSession Cookie, which contains our
   177   # SessionID.  In this example, we print out the SessionID in order to verify
   224   # SessionID.  In this example, we print out the SessionID in order to verify
   178   # that our log in attempt worked.
   225   # that our log in attempt worked.
       
   226 
   179     $response =~ /MRHSession=(\w+);/;
   227     $response =~ /MRHSession=(\w+);/;
   180     $sessionid = $1;
   228     $sessionid = $1;
   181     verbose "% session id ${sessionid}\n";
   229     verbose "% session id ${sessionid}\n";
   182 
   230 
       
   231     $cookies->set_cookie(0, MRHSession => $sessionid, "/", $opt_host, 443, 1, 0, 10, 0, {});
       
   232     #warn $cookies->as_string;
       
   233 
   183 ###
   234 ###
   184 ### STEP 3 :: Create the SSL VPN tunnel.
   235 ### STEP 3 :: Create the SSL VPN tunnel.
   185 ###
   236 ###
   186 
   237 
   187     verbose "% creating the ssl tunnel\n";
   238     verbose "% creating the ssl tunnel\n";
   188 
   239 
   189    # Now that we are authenticated and have a valid SessionID, we must request
   240    # Now that we are authenticated and have a valid SessionID, we must request
   190    # specific pages/objects in order to initiate a SSL VPN tunnel.  Before we do
   241    # specific pages/objects in order to initiate a SSL VPN tunnel.  Before we do
   191    # this, let's determine the resource locator for our Network Access favorite.
   242    # this, let's determine the resource locator for our Network Access favorite.
   192     $request = "GET /vdesk/vpn/index.php3?outform=xml HTTP/1.0\r\n"
   243     #if (!$use_lwp) {
   193       . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
   244     if (1) {
   194     $response = qx(echo "${request}" | ${openssl} 2>/dev/null);
   245         $request = "GET /vdesk/vpn/index.php3?outform=xml HTTP/1.0\r\n"
       
   246           . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
       
   247         $response = qx(echo "${request}" | ${openssl} 2>/dev/null);
       
   248     }
       
   249     else {
       
   250         $response = $ua->get("https://$opt_host:443/vdesk/vpn/index.php3?outform=xml");
       
   251 
       
   252         ohshit "creating tunnel failed with http code @{[$response->message]}\n"
       
   253           unless $response->is_success;
       
   254 
       
   255 	$response = $response->as_string;
       
   256     }
   195 
   257 
   196  # The response is XML, so we can safely grab what we are looking for using some
   258  # The response is XML, so we can safely grab what we are looking for using some
   197  # regular expression magic.  Same with the SessionID, we're printing out the
   259  # regular expression magic.  Same with the SessionID, we're printing out the
   198  # final value to make sure we're on the right track.
   260  # final value to make sure we're on the right track.
       
   261  open(X, ">response.out") or die;
       
   262  print X $response;
       
   263 
   199     $response =~ /${opt_name}[^\n]+\n[^Z]+Z=\d+,(\d+)/;
   264     $response =~ /${opt_name}[^\n]+\n[^Z]+Z=\d+,(\d+)/;
   200     $favorite = $1;
   265     $favorite = $1;
   201     verbose "% favorite ${favorite}\n";
   266     verbose "% favorite ${favorite}\n";
       
   267 
       
   268     exit;
   202 
   269 
   203     # We're all set!  Let's visit the necessary pages/objects to notify FirePass
   270     # We're all set!  Let's visit the necessary pages/objects to notify FirePass
   204     # that we wish to open a SSL VPN tunnel.
   271     # that we wish to open a SSL VPN tunnel.
   205     foreach my $uri ("/vdesk/", "/vdesk/vpn/connect.php3?Z=0,${favorite}",) {
   272     foreach my $uri ("/vdesk/", "/vdesk/vpn/connect.php3?Z=0,${favorite}",) {
   206         $request = "GET ${uri} HTTP/1.0\r\n"
   273         $request = "GET ${uri} HTTP/1.0\r\n"
   215       . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
   282       . "Cookie: MRHSession=${sessionid}\r\n" . "\r\n";
   216     $request = "chat -v '' '${request}'";
   283     $request = "chat -v '' '${request}'";
   217 
   284 
   218     {
   285     {
   219         my $pid = fork();    # this way we've better control
   286         my $pid = fork();    # this way we've better control
   220         die ME . ": can't fork: $!\n" if not defined $pid;
   287         ohshit "can't fork: $!\n" if not defined $pid;
   221 
   288 
   222         if ($pid == 0) {
   289         if ($pid == 0) {
   223             $EUID = $UID;    # get ROOT, the pppd want's to see
   290             $EUID = $UID;    # get ROOT, the pppd want's to see
   224                              # the UID and the EUID to be ROOT
   291                              # the UID and the EUID to be ROOT
   225             exec(
   292             exec(
   226                 pppd     => @ppp_opts,
   293                 pppd     => @ppp_opts,
   227                 pty      => ${openssl},
   294                 pty      => ${openssl},
   228                 connect  => ${request},
   295                 connect  => ${request},
   229                 linkname => $opt_linkname,
   296                 linkname => $opt_linkname,
   230                 qw(noauth crtscts passive noproxyarp local)
   297                 qw(noauth crtscts passive noproxyarp local)
   231             ) or die ME . ": Can't exec: $!\n";
   298             ) or ohshit "Can't exec: $!\n";
   232 
   299 
   233         }
   300         }
   234         wait;
   301         wait;
   235         die ME . ": pppd didn't return sucessfully (rc: @{[$? >> 8]})\n" if $?;
   302         ohshit "pppd didn't return sucessfully (rc: @{[$? >> 8]})\n" if $?;
   236     }
   303     }
   237 
   304 
   238     # now executing the script
   305     # now executing the script
   239     # if there is some script name supplied
   306     # if there is some script name supplied
   240 
   307 
   241     if (defined $opt_script and not $opt_debug) {
   308     if (defined $opt_script and not $opt_debug) {
   242 
   309 
   243         my $pid = fork();
   310         my $pid = fork();
   244         die ME . ": Can't fork: $!\n" if not defined $pid;
   311         ohshit "Can't fork: $!\n" if not defined $pid;
   245 
   312 
   246         if ($pid == 0) {    # child
   313         if ($pid == 0) {    # child
   247 
   314 
   248             if ($UID != $EUID) {
   315             if ($UID != $EUID) {
   249                 my ($owner, $group) = (stat $opt_script)[ 4, 5 ];
   316                 my ($owner, $group) = (stat $opt_script)[ 4, 5 ];
   250                 die ME . ": can't stat $opt_script\n" if not defined $owner;
   317                 ohshit "can't stat $opt_script\n" if not defined $owner;
   251 
   318 
   252                 $UID = $EUID = $owner;
   319                 $UID = $EUID = $owner;
   253                 $GID = $EGID = $group;
   320                 $GID = $EGID = $group;
   254             }
   321             }
   255 
   322 
   256             verbose
   323             verbose
   257               "% executing script $opt_script with uid $UID and gid $GID\n";
   324               "% executing script $opt_script with uid $UID and gid $GID\n";
   258 
   325 
   259             exec $opt_script
   326             exec $opt_script
   260               or die ME . ": Can't exec $opt_script: $!\n";
   327               or ohshit "Can't exec $opt_script: $!\n";
   261         }
   328         }
   262         $pid = wait;
   329         $pid = wait;
   263         verbose "% script returned $?\n";
   330         verbose "% script returned $?\n";
   264         exit;
   331         exit;
   265     }
   332     }
   273 }
   340 }
   274 
   341 
   275 sub do_kill($) {
   342 sub do_kill($) {
   276     my $linkname = shift;
   343     my $linkname = shift;
   277     open((my $fh), $_ = "/var/run/ppp-$linkname.pid")
   344     open((my $fh), $_ = "/var/run/ppp-$linkname.pid")
   278       or die ME . ": Can't open the link file \"$_\": $!\n";
   345       or ohshit "Can't open the link file \"$_\": $!\n";
   279 
   346 
   280     my $pid   = <$fh>;
   347     my $pid   = <$fh>;
   281     my $iface = <$fh>;
   348     my $iface = <$fh>;
   282 
   349 
   283     map { chomp; untaint } $pid, $iface;
   350     map { chomp; untaint } $pid, $iface;
   284 
   351 
   285     # become root doesn't seem to be necessary, as long as our
   352     # become root doesn't seem to be necessary, as long as our
   286     # real uid is 0
   353     # real uid is 0
   287     kill 15, $pid or die ME . ": Can't kill pid $pid: $!\n";
   354     kill 15, $pid or ohshit "Can't kill pid $pid: $!\n";
   288 
   355 
   289     waitpid $pid, 0 != -1
   356     waitpid $pid, 0 != -1
   290       or die ME . ": was not able to wait for the ppp process: $!\n";
   357       or ohshit "was not able to wait for the ppp process: $!\n";
   291 
   358 
   292     verbose "% process killed, rc: "
   359     verbose "% process killed, rc: "
   293       . ($? >> 8)
   360       . ($? >> 8)
   294       . ", sig: "
   361       . ", sig: "
   295       . ($? & 0xff) . "\n";
   362       . ($? & 0xff) . "\n";
   306      [-s|--script <script>]
   373      [-s|--script <script>]
   307      -u|--user <user> -p|--passcode <passcode>
   374      -u|--user <user> -p|--passcode <passcode>
   308 
   375 
   309  sap-vpn [-l|--linkname <link name>] -k|--kill
   376  sap-vpn [-l|--linkname <link name>] -k|--kill
   310 
   377 
   311  sap-vpn [-h|--help]
   378  sap-vpn -h|--help
   312  sap-vpn [-m|--man]
   379  sap-vpn -m|--man
   313 
   380 
   314 =head1 DESCRIPTION
   381 =head1 DESCRIPTION
   315 
   382 
   316 Ths script establishes a VPN connecttion to a FirePass server.
   383 Ths script establishes a VPN connecttion to a FirePass server.
   317 B<Note:> This script requires B<root> privileges and should be run via sudo
   384 B<Note:> This script requires B<root> privileges and should be run via sudo