From: Martin Willi Date: Tue, 3 Jun 2014 07:22:35 +0000 (+0200) Subject: swanctl: Add a perl script to convert ipsec.conf to swanctl.conf X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1dc314e84493c322b4112fb7bb78b472025b5cfe;p=thirdparty%2Fstrongswan.git swanctl: Add a perl script to convert ipsec.conf to swanctl.conf --- diff --git a/src/swanctl/Makefile.am b/src/swanctl/Makefile.am index a262a2fa7b..3d86fb7123 100644 --- a/src/swanctl/Makefile.am +++ b/src/swanctl/Makefile.am @@ -34,7 +34,7 @@ man_MANS = \ swanctl.conf.5 BUILT_SOURCES = swanctl.conf swanctl.conf.5.main -EXTRA_DIST = swanctl.opt swanctl.conf swanctl.conf.5.main +EXTRA_DIST = swanctl.opt swanctl.conf swanctl.conf.5.main ipsec2swanctl.pl CLEANFILES = $(man_MANS) .opt.conf: diff --git a/src/swanctl/ipsec2swanctl.pl b/src/swanctl/ipsec2swanctl.pl new file mode 100644 index 0000000000..b9b9672da3 --- /dev/null +++ b/src/swanctl/ipsec2swanctl.pl @@ -0,0 +1,849 @@ +#!/usr/bin/perl -w + +my $etc = "/etc"; +my $debug = 3; +my $tab = "\t"; +my $min_connection_name = 4; + +my %debug_level = +( + 'MIN' => 0, + 'ERROR' => 1, + 'WARN' => 2, + 'INFO' => 3, + 'DEBUG' => 4, +); + +my %any_ike_sa = +( + 'keyexchange' => 'version', + 'left' => 'local_addrs', + 'right' => 'remote_addrs', + 'leftikeport' => 'local_port', + 'rightikeport' => 'remote_port', + 'ike' => 'proposals', + 'leftsourceip' => 'vips', + 'aggressive' => 'aggressive', + 'modeconfig' => 'pull', + 'forceencaps' => 'encap', + 'mobike' => 'mobike', + 'dpddelay' => 'dpd_delay', + 'dpdtimeout' => 'dpd_timeout', + 'fragmentation' => 'fragmentation', + 'rightsendcert' => 'send_certreq', + 'leftsendcert' => 'send_cert', + 'keyingtries' => 'keyingtries', + 'uniqueids' => 'unique', + 'ikelifetime' => 'reauth_time', +# 'ikelifetime' => 'rekey_time', + 'rekeymargin' => 'over_time', + 'rekeyfuzz' => 'rand_time', +); + +my %child_sa = +( + 'ah' => 'ah_proposals', + 'esp' => 'esp_proposals', + 'leftsubnet' => 'local_ts', + 'rightsubnet' => 'remote_ts', + 'leftprotoport' => 'local_ts', + 'rightprotoport'=> 'remote_ts', + 'leftupdown' => 'update', + 'lefthostaccess'=> 'hostaccess', + 'type' => 'mode', + 'dpdaction' => 'dpd_action', + 'compress' => 'ipcomp', + 'inactivity' => 'inactivity', + 'reqid' => 'reqid', + 'mark' => 'mark_in', + 'mark_in' => 'mark_in', + 'mark_out' => 'mark_out', + 'tfc' => 'tfc_padding', + 'margintime' => 'rekey_time', + 'lifetime' => 'life_time', + 'rekeyfuzz' => 'rand_time', + 'marginpackets' => 'rekey_packets', + 'lifepackets' => 'life_packets', + 'rekeyfuzz' => 'rand_packets', + 'marginbytes' => 'rekey_bytes', + 'lifebytes' => 'life_bytes', + 'auto' => 'start_action', +); + +my %local = +( + 'leftcert' => 'certs', + 'leftca' => 'cacerts', + 'leftcacert' => 'cacerts', + 'leftauth' => 'auth', + 'leftid' => 'id', + 'eap_identity' => 'eap_id', + 'aaa_identity' => 'aaa_id', + 'xauth_identity'=> 'xauth_id', + 'rightgroups' => 'groups', + 'strictcrlpolicy'=> 'revocation', +); + +my %remote = +( + 'rightcert' => 'certs', + 'rightca' => 'cacerts', + 'rightcacert' => 'cacerts', + 'rightauth' => 'auth', + 'rightid' => 'id', + 'eap_identity' => 'eap_id', + 'aaa_identity' => 'aaa_id', + 'xauth_identity'=> 'xauth_id', + 'rightgroups' => 'groups', + 'strictcrlpolicy'=> 'revocation', +); + +my %cert_dirs = +( + 'certs' => 'x509', + 'cacerts' => 'x509ca', + 'aacerts' => 'x509aa', + 'crls' => 'x509crl', + 'acerts' => 'x509ac', +); + +my %ipsec_rename = +( + 'ipsec.d' => 'swanctl', + 'ipsec.conf' => 'swanctl.conf', +# 'ipsec.secrets' => 'swanctl.conf', +); + +sub cmd +{ + my $shell_cmd = $_[0]; + + my $shell_out = `$shell_cmd`; + + if ( $? != 0 ) + { + if ( $debug > $debug_level{ 'WARN' } ) + { + print "WARN: EXEC $shell_cmd\n"; + print "WARN: cmd returned with code $?"; + } + } + elsif ( $debug > $debug_level{ 'DEBUG' } ) + { + print "DEBUG: EXEC: $shell_cmd\n"; + } + + return $shell_out; +} + +sub copy_ipsec_d +{ + my $ipsec_dir = "$etc/ipsec.d"; + + if ( $debug > $debug_level{ 'INFO' } ) + { print "INFO: creating $etc/swanctl from $ipsec_dir\n"; } + + if ( ! -d $ipsec_dir ) { die ( "Cannot locate $ipsec_dir cert directory !" ); } + + &cmd( "mkdir -p $etc/swanctl" ); + + &cmd( "cp -pR $etc/ipsec.d/* $etc/swanctl/" ); + + foreach my $dir ( keys %cert_dirs ) + { + &cmd( "mv $etc/swanctl/$dir $etc/swanctl/$cert_dirs{ $dir }" ); + } + + &cmd( "mkdir $etc/swanctl/rsa" ); + &cmd( "mkdir $etc/swanctl/ecdsa" ); + &cmd( "mkdir $etc/swanctl/pkcs8" ); + + &cmd( "mv -v $etc/swanctl/private/*.der $etc/swanctl/ecdsa" ); + &cmd( "mv -v $etc/swanctl/private/*.rsa $etc/swanctl/rsa" ); + &cmd( "mv -v $etc/swanctl/private/* $etc/swanctl/pkcs8" ); + + &cmd( "rmdir $etc/swanctl/private" ); +} + +my @connections = (); + +my %conn_values = (); + +my %conn_order = (); + +my %conn_remark = (); + +my %conn_deps = (); + +my %leftright_groups = (); + +my %also_list = (); + +my @ipsec_conf = (); + +sub any_ike_sa_count +{ + my ( $val ) = ( @_ ); + my $count = 0; + + foreach my $k ( keys %$val ) + { + if ( defined( $any_ike_sa{ $k } ) ) + { $count++; } + } + return $count; +} + +sub get_leftright_key +{ + my ( $vals ) = ( @_ ); + + if ( defined $$vals{ 'left' } && defined $$vals{ 'right' } ) + { + return $$vals{ 'left' }.'-'.$$vals{ 'right' }; + } + return undef +} + +sub push_leftright_group +{ + my ( $conn_name, $left, $right, $vals ) = ( @_ ); + + $leftright = "$left-$right"; + my $group = []; + + if ( defined( $leftright_groups{ $leftright } ) ) + { + $group = $leftright_groups{ $leftright }; + } + + push @$group, $conn_name; + + $leftright_groups{ $leftright } = $group; +} + +sub parse_ipsec_conf +{ + my $conn_name = undef; + my $comments = []; + my $vals = {}; + my $order = []; + my $also = []; + my $any_ike_sa_count = 0; + my $left = undef; + my $right = undef; + my $ipsec_file = "$etc/ipsec.conf"; + + if ( $debug > $debug_level{ 'INFO' } ) + { print "INFO: parsing $ipsec_file\n"; } + + open( IPSEC_FILE, "< $ipsec_file" ) || die "Could not open $ipsec_file: $!\n"; + @ipsec_conf = ; + + close( IPSEC_FILE ); + + foreach my $l ( 0..$#ipsec_conf ) + { + my $line = $ipsec_conf[ $l ]; + + if ( $line =~ /^\s*conn\s+(.+)/ || + $line =~ /^\s*config\s+(.+)/ ) + { + if ( defined ( $conn_name ) ) + { + push @connections, $conn_name; + $conn_values{ $conn_name } = $vals; + $conn_order{ $conn_name } = $order; + + if ( defined( $left ) && defined( $right ) ) + { + &push_leftright_group( $conn_name, $left, $right, $vals ); + undef $left; + undef $right; + } + + if ( 0 < scalar( @$also ) ) + { + $conn_deps{ $conn_name } = $also; + $also = []; + } + + $vals = {}; + $order = []; + } + + $conn_name = $1; + + if ( @$comments ) + { + $conn_remark{ $conn_name } = $comments; + $comments = []; + } + } + elsif ( $line =~ /^\s*(#.+)/ ) + { + push @$comments, $1; + } + elsif ( $line =~ /^\s*$/ ) + { + push @$comments, ""; + } + elsif ( $line =~ /^\s*(\w+)\s*=\s*(.+)$/ ) + { + if ( @$comments ) + { + push @$order, @$comments; + $comments = []; + } + my $key = $1; + my $val = $2; # note this may include value and comments ! + my $value = $val; + + # sanitize remarks out of value for post-processing: + if ( $val =~ /^(\S+)\s*#.*/ ) + { $value = $1; } + + if ( $key eq 'also' ) + { + $also_list{ $value }++; + push @$also, $value; + next; + } + elsif ( $key eq "right" ) + { $right = $value; } + elsif ( $key eq "left" ) + { $left = $value; } + + push @$order, $key; + $$vals{ $key } = $val; + } + } + + if ( defined ( $conn_name ) ) + { + push @connections, $conn_name; + $conn_values{ $conn_name } = $vals; + $conn_order{ $conn_name } = $order; + + if ( defined( $left ) && defined( $right ) ) + { &push_leftright_group( $conn_name, $left, $right, $vals ); } + } +} + +sub dump_ipsec_conf +{ + foreach my $conn ( @connections ) + { + if ( defined $conn_remark{ $conn } ) + { + my $remark = $conn_remark{ $conn }; + + foreach my $l ( @$remark ) + { + print $l."\n"; + } + } + + print "conn $conn\n"; + my $order = $conn_order{ $conn }; + my $vals = $conn_values{ $conn }; + + foreach my $l ( @$order ) + { + if ( $l eq "" || '#' eq substr( $l, 0, 1 ) ) + { + print $l."\n"; + } + else + { + print $l."=".$$vals{ $l }."\n"; + } + } + } +} + +sub process_dependencies +{ + my @new_conn = (); + + my $default = undef; + + if ( defined( $conn_order{ '%default' } ) ) + { $default = $conn_order{ '%default' }; } + + foreach my $conn ( @connections ) + { + my $exclude = 0; + + # exclude the "setup" and "conn %default" from getting converted: + if ( $conn eq "setup" || + $conn eq "%default" ) + { + if ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: excluding conn $conn\n"; } + $exclude = 1; + } + # Check if any "also" referenced conn are valid + elsif ( defined( $also_list{ $conn } ) ) + { + $exclude = 1; + + my $vals = $conn_values{ $conn }; + + # Look for a "left" and "right" keyword + if ( defined( &get_leftright_key( $vals ) ) ) + { + $exclude = 0; + } + } + # check if a conn has "also" references, add the "also" ordered + # set of values to conn's ordered set of values + else + { + if ( defined( $conn_order{ $conn } ) ) + { + my $order = []; + push @$order, @{$conn_order{ $conn }}; + + if ( defined $conn_deps{ $conn } ) + { + foreach my $also ( @{ $conn_deps{ $conn } } ) + { + if ( defined( $conn_order{ $also } ) ) + { + push @$order, @{ $conn_order{ $also } }; + } + } + } + + if ( defined( $default ) ) + { push @$order, @{ $default }; } + + $conn_order{ $conn } = $order; + + if ( $debug > $debug_level{ 'DEBUG' } ) + { + print "DEBUG: new_order $conn\n"; + foreach my $o( @$order ) + { print "DEBUG: $o\n"; } + } + } + } + + if ( ! $exclude ) + { push @new_conn, $conn; } + elsif ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: excluding conn $conn\n"; } + } + + @connections = @new_conn; +} + +sub get_conn_values +{ + my ( $conn_name, $default ) = ( @_ ); + my %conn_union_vals = %$default; + my $conn_vals = $conn_values{ $conn_name }; + + # Overlay also dependant values + if ( defined $conn_deps{ $conn_name } ) + { + foreach my $also ( @{ $conn_deps{ $conn_name } } ) + { + my $also_vals = &get_conn_values( $also, $default ); + @conn_union_vals{ keys %$also_vals } = values %$also_vals; + } + } + + # Overlay the specific conn values + @conn_union_vals{ keys %$conn_vals } = values %$conn_vals; + + # add id: + $conn_union_vals{ 'CONNECTION_NAME' } = $conn_name; + + # fix some obsolete variable values + foreach my $direction ( 'left', 'right' ) + { + if ( defined( $conn_union_vals{ $direction.'protoport' } ) && + defined( $conn_union_vals{ $direction } ) ) + { + + if ( defined( $conn_union_vals{ $direction.'subnet' } ) ) + { + print "ERROR: cannot convert ".$direction.'protoport because '.$direction.'subnet already exists\n'; + } + + my $val = $conn_union_vals{ $direction.'protoport' }; + my $host = $conn_union_vals{ $direction }; + my $remark = ""; + + # first sanitize for comment + if ( $val =~ /^([^#]+)(#.*)/ ) + { + $remark = $2; + my $val_space = $1; + + if ( $val_space =~ /^(\S+)(\s*)/ ) + { + $remark = $2.$remark; + $val = $1; + } + } + # set {left,right}protoport equal to the {left,right}subnet format + $conn_union_vals{ $direction.'protoport' } = $host.'['.$val.']'.$remark; + } + } + + if ( $debug > $debug_level{ 'DEBUG' } ) + { + print "DEBUG: GET_CONN_VALUES ".$conn_union_vals{ 'CONNECTION_NAME' }."\n"; + + foreach my $k ( keys %conn_union_vals ) + { + print "DEBUG: ".$k." = ".$conn_union_vals{ $k }."\n"; + } + } + + return \%conn_union_vals; +} + +sub output_section +{ + my ( $SWANCTL, $conn_vals, $section_keys, $indent, $info, $conn_child ) = ( @_ ); + + my $conn_name = $$conn_vals{ 'CONNECTION_NAME' }; + my $order = $conn_order{ $conn_name }; + if ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: output_section $conn_name\n"; } + for( my $o = 0; $o < $#$order; $o++ ) + { + my $oldkey = $$order[ $o ]; + if ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: $conn_name $oldkey\n"; } + next if ( ! defined( $$conn_vals{ $oldkey } ) ); + next if ( ! defined( $$section_keys{ $oldkey } ) ); + my $newkey = $$section_keys{ $oldkey }; + + if ( $info ne "" ) + { + print $SWANCTL $indent.$info."\n"; + # only output this info section once + $info = ""; + } + # check if there were comments associated with this key/value: + my $p = $o; + $o--; + while( $o >= 0 && substr( $$order[ $o ], 0, 1 ) eq "#" ) + { $o--; } + $o++; + for(; $o < $p; $o++ ) + { + print $SWANCTL $indent.$$order[ $o ]."\n"; + } + + # write out the $newkey = $value. + my $value = $$conn_vals{ $oldkey }; + delete $$conn_vals{ $oldkey }; + print $SWANCTL $indent.$newkey." = ".$value."\n"; + + next if ( ! defined( $conn_child ) || ref( $conn_child ) ne 'ARRAY' ); + + # loop through all the children. For any values *not* the same leave a WARN comment + foreach my $child_vals ( @$conn_child ) + { + next if ( ! defined ( $$child_vals{ $oldkey } ) ); + my $child_val = $$child_vals{ $oldkey }; + delete $$child_vals{ $oldkey }; + + # sanitize remarks out for comparison + if ( $value =~ /^(\S+)\s*#.*/ ) + { $value = $1; } + if ( $child_val =~ /^(\S+)\s*#.*/ ) + { $child_val = $1; } + + if ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: $conn_name compare $value eq $child_val\n"; } + + if ( $child_val ne $value ) + { + print $SWANCTL $indent."# WARN ".$$child_vals{ 'CONNECTION_NAME' }." ".$newkey." = ".$value."\n"; + } + } + } + + # loop through all the children. For any values that weren't in the + # main conn add them with an INFO comment + foreach my $child_vals ( @$conn_child ) + { + &output_section( $SWANCTL, $child_vals, $section_keys, $indent, "# INFO: from conn ".$$child_vals{ 'CONNECTION_NAME' } ); + } + + return; +} + +sub output_block +{ + my ( $SWANCTL, $conn_vals, $section_keys, $indent, $block, $conn_child ) = ( @_ ); + + if ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: output_block $block\n"; } + + print $SWANCTL "\n"; + print $SWANCTL $indent.$block." {\n"; + + &output_section( $SWANCTL, $conn_vals, $section_keys, $indent.$tab, "", $conn_child ); + + print $SWANCTL $indent."}\n"; +} + +sub output_swanctl_conf +{ + my $swanctl_file = "$etc/swanctl/swanctl.conf"; + + if ( $debug > $debug_level{ 'INFO' } ) + { print "INFO: output $swanctl_file\n"; } + + open( my $SWANCTL, "> $swanctl_file" ) || die ( "ERROR: could not open $swanctl_file: $!\n" ); + + my @indent = ( "" ); + print $SWANCTL $indent[0]."connections {\n"; + + my $default = {}; + + if ( defined( $conn_values{ '%default' } ) ) + { $default = $conn_values{ '%default' }; } + + # We want to quasi follow the ordered structure of the original ipsec.conf, + # in that we will write the swanctl.conf connections based on $leftright pairs + # as they appeared in the ipsec.conf + foreach my $conn ( @connections ) + { + my @name_chars = split( //, $conn ); + my $name_len = $#name_chars; + my $conn_vals = &get_conn_values( $conn, $default ); + my @conn_child = (); + + my $leftright = &get_leftright_key( $conn_vals ); + + next if ( ! defined( $leftright ) ); + next if ( ! defined( $leftright_groups{ $leftright } ) ); + + my $conn_count = &any_ike_sa_count( $conn_vals ); + + # figure out which of the connections that share the same $leftright key + # should be considered the master because it has the most values: + foreach my $child ( @{ $leftright_groups{ $leftright } } ) + { + next if ( $child eq $conn ); + + my $child_vals = &get_conn_values( $child, $default ); + my $child_count = &any_ike_sa_count( $child_vals ); + + # Figure out the name for the connection - hopefully there is commonality ! + my @child_name = split( //, $child ); + my $name_len = $name_len > $#child_name ? $#child_name : $name_len; + foreach my $i ( 0 .. $name_len ) + { + if ( $name_chars[ $i ] ne $child_name[ $i ] ) + { + $name_len = $i; + last; + } + } + + if ( $child_count > $conn_count ) + { + push @conn_child, $conn_vals; + $conn_vals = $child_vals; + $conn_count = $child_count; + $conn = $child; + @name_chars = @child_name; + } + else + { + push @conn_child, $child_vals; + } + } + + # at this point, we should have the "master" to use for the $leftright conn + delete $leftright_groups{ $leftright }; + + # name to use: check min, length, sanitize any "_-" ending chars + my $conn_name = $conn; + + foreach my $char ( [ '-', '_' ] ) + { + my $len = index( $conn_name, '-' ); + if ( $len >= $min_connection_name && $len < $name_len ) + { $name_len = $len; } + } + + if ( $name_len >= $min_connection_name ) + { $conn_name = substr( $conn, 0, $name_len ); } + + if ( $debug > $debug_level{ 'DEBUG' } ) + { print "DEBUG: connection name = $conn_name\n"; } + + # Done processing. Now write to swanctl.conf + unshift @indent, $indent[0].$tab; + + if ( defined $conn_remark{ $conn } ) + { + my $remark = $conn_remark{ $conn }; + + foreach my $l ( @$remark ) + { + print $SWANCTL $indent[0].$l."\n"; + } + } + + # conn block + print $SWANCTL $indent[0].$conn_name." {\n"; + unshift @indent, $indent[0].$tab; + + &output_section( $SWANCTL, $conn_vals, \%any_ike_sa, $indent[0], "", \@conn_child ); + + # local block + &output_block( $SWANCTL, $conn_vals, \%local, $indent[0], "local", \@conn_child ); + + # remote block + &output_block( $SWANCTL, $conn_vals, \%remote, $indent[0], "remote", \@conn_child ); + + # add the master $conn_vals to $conn_child so simple iteration + # for remaining of output + unshift @conn_child, $conn_vals; + + # children block + print $SWANCTL "\n"; + print $SWANCTL $indent[0]."children {"; + unshift @indent, $indent[0].$tab; + foreach my $children ( @conn_child ) + { + &output_block( $SWANCTL, $children, \%child_sa, $indent[0], $$children{ 'CONNECTION_NAME' } ); + } + + shift @indent; + print $SWANCTL $indent[0]."}\n"; + + # WARN about any remaining variables that haven't been mapped + unshift @indent, $indent[0]."# "; + foreach my $remain ( @conn_child ) + { + my %remain_keys = map{ $_ => $_ } keys %$remain; + + &output_section( $SWANCTL, $remain, \%remain_keys, $indent[0], "WARN: nomap ".$$remain{ 'CONNECTION_NAME' } ); + } + # remove just add "# " + shift @indent; + + shift @indent; + print $SWANCTL $indent[0]."}\n"; + shift @indent; + } + + print $SWANCTL $indent[0]."} # connections\n"; + + my $secrets_file = "$etc/ipsec.secrets"; + + if ( -f $secrets_file ) + { + if ( $debug > $debug_level{ 'INFO' } ) + { print "INFO: processing $secrets_file\n"; } + + print $SWANCTL "\n"; + print $SWANCTL "secrets {\n"; + print $SWANCTL $tab."eap {\n"; + print $SWANCTL $tab."}\n"; + print $SWANCTL $tab."ike {\n"; + print $SWANCTL $tab."}\n"; + print $SWANCTL "} # secrets\n"; + } + + close( $SWANCTL ); +} + +sub print_usage +{ + print < $debug_level{ 'DEBUG' } ) + { &dump_ipsec_conf(); } + + &process_dependencies(); + + &output_swanctl_conf(); +} + +&main();