http://qs321.pair.com?node_id=247087
Category: Network Code (possibly Crypto)
Author/Contact Info /msg me if you need help with the script
Description:

I frequently move around (new IP addresses) and find myself needing to access machines behind a client's NetScreen VPN appliances. I got a little tired of constantly having to set up new config files that contained essentially the same information just to gain access from a new location.

So this script enables you to use a simple config file to set up all of your tunnels and connections based on your current IP address, and it will even restart racoon for you when it's done.

Could it be prettier? Yes. Could it be more clever? Yes. Does it work? Yes. And it even has comments if you want to extend it or make it better.

Note too, that although I've embedded most of the default values in the Templates.pm file, there's nothing stopping you from overriding them and adding a configurable version to the Profiles.pm file. Simply replace the parameter to be made configurable (e.g. proposal_check obey;) with the interpolated style (e.g proposal_check @@PROPOSAL@@) and then add PROPOSAL => 'foo'; to your profiles file.

HTH

Templates.pm

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;

Profiles.pm (you can call it whatever you want)

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

The script itself

#!/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] [pro
+file=/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 ($p
+rofile) to read: $!");
        my $slurp = <PROFILE>;
        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 (/sb
+in/ifconfig) to read: $!");
        while (<IP>) {
                $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 proper
+ly configured the interface\n";
                print STDOUT "It is higly unlikely that you really wan
+t 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 yo
+ur 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->{NAM
+E} . "\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: " . $netw
+ork . "\n";
                }
        }

        $lookup{tunnels} = $tunnels;

        open (OUT, ">$dir/interface.sh") or die ("Couldn't open $dir/i
+nterface.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->{NAM
+E} . "\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->{NAM
+E} . "\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/ra
+coon.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 ${di
+r}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 proces
+ses: $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;
}