use constant RACOON_TMPL => <<'EOF'; log debug2; path pre_shared_key "@@KEY@@"; padding { randomize off; maximum_length 20; exclusive_tail off; strict_check off; } timer { counter 5; interval 20 seconds; persend 1; phase1 30 seconds; phase2 15 seconds; } @@remote@@ @@sainfo@@ listen { isakmp @@client_ip@@; } EOF use constant SAINFO_TMPL => <<'EOF'; sainfo address @@client_ip@@/32 any address @@network@@ any { pfs_group @@PFS_GROUP@@; lifetime time 1 hours; encryption_algorithm @@ENCRYPT@@; authentication_algorithm @@IDENT@@; compression_algorithm deflate; } EOF use constant REMOTE_SERVER_TMPL => <<'EOF'; remote @@SERVER_IP@@ { exchange_mode aggressive, main; initial_contact on; proposal_check obey; support_mip6 on; generate_policy off; nonce_size 16; doi ipsec_doi; situation identity_only; passive off; my_identifier user_fqdn "@@USER_FQDN@@"; proposal { encryption_algorithm @@ENCRYPT@@; hash_algorithm @@HASH@@; authentication_method pre_shared_key; dh_group @@DH_GROUP@@; lifetime time 8 minutes; } } EOF use constant SCRIPT_TMPL => <<'EOS'; #!/bin/sh setkey -FP setkey -F setkey -c << EOF @@tunnels@@ EOF EOS use constant TUNNEL_TMPL => <<'EOF'; spdadd @@client_ip@@/32 @@network@@ any -P out ipsec esp/tunnel/@@client_ip@@-@@server_ip@@/require; spdadd @@network@@ @@client_ip@@/32 any -P in ipsec esp/tunnel/@@server_ip@@-@@client_ip@@/require; EOF 1; #### # The interface on which to # run racoon. Typically, en0 # is the Ethernet NIC and # en1 is for WiFi/Airport INTERFACE => 'en0'; # One anonymous hash for each # VPN appliance to which to connect. # Note the notation for the networks # behind the VPN -- you cannot connect # to the same subnet on two different # VPNs. PROFILES => [ { NAME => 'Foo', SERVER_IP => '1.2.3.4', USER_FQDN => 'user@domain.com', NETWORKS => ['192.168.1.0/24', '192.168.0.0/24'], }, { NAME => 'Bar', SERVER_IP => '5.6.7.8', USER_FQDN => 'user2@domain2.com', NETWORKS => ['192.168.50.0/24'], }, ]; # Set to your IP address if # the script is having trouble # determining it via /sbin/ifconfig CLIENT_IP => '169.254.0.1'; # Where to find the Private Shared # Key file. There is one already in # set up for you in /etc/racoon, but # you'll need to set up the keys for # each VPN appliance before connecting KEY => '/etc/racoon/psk.txt'; # These are connection parameters. # You should work these out with # your sysadmin. The defaults below # work with a properly-configured # NetScreen VPN appliance. PFS_GROUP => 'modp1024'; DH_GROUP => 'modp1024'; ENCRYPT => '3des'; HASH => 'sha1'; IDENT => 'hmac_sha1'; #### #!/usr/bin/perl ##################################### # This script is distributed on the understanding # that if you make improvements you will return # them to the community of Perl and Mac users. This # is pint-ware -- if you find it particularly useful # a thank you pint would be appreciated but is not # required. # # For more information visit http://www.reades.com/ # # Please note that I *cannot* offer support on VPN # configuration from the systems side -- not only # is each and every VPN different but, frankly, I do # not know enough about how they work to be of any # use. # # Enjoy, # jon reades #################################### BEGIN { # Normally, you will want to # keep your script and files # here. push @INC, "/etc/racoon"; } use Cwd; use strict; use Templates; # The default profile my $profile = "./Profiles.pm"; # Assume that you should use # the current working directory # unless one is specified in the # command-line args my $dir = getcwd; my $to_do; unless (@ARGV) { print STDOUT "Usage:\n"; print STDOUT "\tperl setup.pl [dir=/path/to/output/files] [profile=/path/to/profile.txt] config|restart|all\n"; print STDOUT "\tDefault profile is in ./Profile.pm\n"; print STDOUT "\tDefault output directory is cwd ($dir)\n"; exit 0; } foreach(@ARGV) { my ($key, $val) = split /=/; # Override the profile $profile = $val if ($key eq 'profile'); # Override the output directory $dir = $val if ($key eq 'dir'); # What to do? $to_do = "configure" if ($key eq 'config'); $to_do = "restart" if ($key eq 'restart'); $to_do = "all" if ($key eq 'all'); } # Are we generating a config file? if ($to_do eq 'all' || $to_do eq 'configure') { print STDOUT "\nFiles will be output to $dir\n"; generateConfig($profile, $dir); } # Are we restarting racoon and setting the tunnels? if ($to_do eq 'all' || $to_do eq 'restart') { print STDOUT "\nLooking for configuration files in $dir\n"; restartRacoon($dir); } exit 0; # Reads in the profile file and splits # it based on a key => val syntax sub parseProfile { my $profile = shift; my %lookup; # We slurp it in so that we can # properly handle arrays and hashes # spread across multiple lines of # the profile file my $delimiter = $/; $/ = undef; open (PROFILE, "<$profile") or die ("Couldn't open profile ($profile) to read: $!"); my $slurp = ; close PROFILE; my (@params) = split /\;/, $slurp; foreach my $param (@params) { # Remove any comments $param =~ s|^\#.*?$||gm; # Parse out the parameter my ($key, $val) = parseParam($param); # And assign it to the lookup # hash for interpolation $lookup{$key} = $val if ($key); } $/ = $delimiter; return \%lookup; } sub parseParam { my $param = shift; # We're assuming that everything # after the '=>' is part of the value my ($key, $val) = $param =~ m/(\S+)\s+=>\s+(.*)$/s; # This 'vivifies' the # hash or array my $ref = eval ($val); return $key, $ref; } sub interpolate { my $string = shift; my $lookup = shift; $string =~ s/@@([^@]+)@@/$lookup->{$1}/g; return $string; } sub generateConfig { my $profile = shift; my $dir = shift; my %lookup = %{parseProfile($profile)}; # Look for a recognisable IP address # on the interface specified in # the profile file my $match; open (IP, "/sbin/ifconfig |") or die ("Couldn't open pipe (/sbin/ifconfig) to read: $!"); while () { $match = 'found' if ($_ =~ m|$lookup{INTERFACE}|); next if ($match ne 'found'); if ($_ =~ m|inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|) { $lookup{client_ip} = $1; last; } } $lookup{client_ip} = $lookup{CLIENT_IP} unless ($lookup{client_ip}); close IP; print STDOUT "\nSetting up racoon on " . $lookup{INTERFACE} . " : " . $lookup{client_ip} . "\n\n"; if ($lookup{client_ip} eq '169.254.0.1') { print STDOUT "Warning: you don't appear to have properly configured the interface\n"; print STDOUT "It is higly unlikely that you really want to run racoon on " . $lookup{client_ip} . "\n"; print STDOUT "Try checking the INTERFACE parameter in $profile\n"; print STDOUT "or setting the CLIENT_IP parameter to your machine's IP.\n"; exit 0; } ############################ # Set up the tunnels to be # run via a shell script ############################ my $tunnels = ""; print STDOUT "Setting up tunnels on " . $lookup{INTERFACE} . "\n"; foreach my $profile (@{$lookup{PROFILES}}) { print STDOUT "\tSetting up profile: " . $profile->{NAME} . "\n"; $lookup{server_ip} = $profile->{SERVER_IP}; foreach my $network (@{$profile->{NETWORKS}}) { $lookup{network} = $network; $tunnels .= interpolate(TUNNEL_TMPL, \%lookup); print STDOUT "\t\tSetting up subnet: " . $network . "\n"; } } $lookup{tunnels} = $tunnels; open (OUT, ">$dir/interface.sh") or die ("Couldn't open $dir/interface.sh to write: $!"); print OUT interpolate(SCRIPT_TMPL, \%lookup); close OUT; print STDOUT "\nTunnel script (interface.sh) complete.\n"; print STDOUT "Next time you can just run 'sudo ${dir}interface.sh'.\n\n"; ############################ # Set up the SAInfo section # of the racoon.conf file ############################ my $sainfo = ""; print STDOUT "Setting up SAInfo section for racoon.conf\n"; foreach my $profile (@{$lookup{PROFILES}}) { print STDOUT "\tSetting up profile: " . $profile->{NAME} . "\n"; foreach my $network (@{$profile->{NETWORKS}}) { $lookup{network} = $network; $sainfo .= interpolate(SAINFO_TMPL, \%lookup); } } $lookup{sainfo} = $sainfo; ############################ # Set up the remote servers section # of the racoon.conf file ############################ my $remote = ""; print STDOUT "Setting up Remote Server section for racoon.conf\n"; foreach my $profile (@{$lookup{PROFILES}}) { print STDOUT "\tSetting up profile: " . $profile->{NAME} . "\n"; foreach (keys %$profile) { $lookup{$_} = $profile->{$_}; } $remote .= interpolate(REMOTE_SERVER_TMPL, \%lookup); } $lookup{remote} = $remote; ############################ # Set up the racoon.conf file ############################ print STDOUT "Setting up racoon.conf\n"; open (OUT, ">$dir/racoon.conf") or die ("Couldn't open $dir/racoon.conf to write: $!"); print OUT interpolate(RACOON_TMPL, \%lookup); close OUT; print STDOUT "\nRacoon setup (racoon.conf) complete.\n"; print STDOUT "Next time you can just run: 'sudo racoon -f ${dir}racoon.conf'.\n\n"; print STDOUT "Setup complete\n"; return; } sub restartRacoon { my $dir = shift; print STDOUT "\nRestarting\n"; my $retval1 = system('sudo', 'killall', 'racoon'); if ($retval1) { print STDOUT "Couldn't kill any running racoon processes: $retval1\n"; } my $retval2 = system ('sudo', '/bin/sh', $dir . '/interface.sh'); if ($retval2) { print STDOUT "Couldn't run interface.sh: $retval2\n"; } my $retval3 = system ('sudo', '/usr/sbin/racoon', '-f', $dir . '/racoon.conf'); if ($retval3) { print "Couldn't start racoon: $retval3\n"; } if (!($retval1 || $retval2 || $retval3)) { print STDOUT "Restart complete\n"; } else { print STDOUT "May not have restarted cleanly\n"; } return; }