|
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 ### |