Viewing file: ipsec-lib.pl (10.3 KB) -rwxr-xr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# ipsec-lib.pl # Common functions for managing the freeswan config file # XXX check for new errors in log after applying # XXX option to download connection as .conf file, and upload an existing # .conf file for addition
BEGIN { push(@INC, ".."); }; use WebminCore; &init_config();
# get_config([file]) # Returns an array of configured connections sub get_config { local $file = $_[0] || $config{'file'}; local (@rv, $sect); local $lnum = 0; local $fh = "CONF".$get_config_fh++; open($fh, $file); while(<$fh>) { s/\r|\n//g; s/#.*$//; if (/^\s*([^= ]+)\s*=\s*"([^"]*)"/ || /^\s*([^= ]+)\s*=\s*'([^"]*)'/ || /^\s*([^= ]+)\s*=\s*(\S+)/) { # Directive within a section if ($sect) { if ($sect->{'values'}->{lc($1)}) { $sect->{'values'}->{lc($1)} .= "\0".$2; } else { $sect->{'values'}->{lc($1)} = $2; } $sect->{'eline'} = $lnum; } } elsif (/^\s*include\s+(\S+)/) { # Including possibly multiple files local $inc = $1; if ($inc !~ /^\//) { $file =~ /^(.*)\//; $inc = "$1/$inc"; } local $g; foreach $g (glob($inc)) { local @inc = &get_config($g); map { $_->{'index'} += scalar(@rv) } @inc; push(@rv, @inc); } } elsif (/^\s*(\S+)\s+(\S+)/) { # Start of a section $sect = { 'name' => $1, 'value' => $2, 'line' => $lnum, 'eline' => $lnum, 'file' => $file, 'index' => scalar(@rv), 'values' => { } }; push(@rv, $sect); } $lnum++; } close($fh); return @rv; }
# create_conn(&conn) # Add a new connection to the config file sub create_conn { local $lref = &read_file_lines($_[0]->{'file'} || $config{'file'}); push(@$lref, "", &conn_lines($_[0])); &flush_file_lines(); }
# modify_conn(&conn) sub modify_conn { local $lref = &read_file_lines($_[0]->{'file'}); splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1, &conn_lines($_[0])); &flush_file_lines(); }
# delete_conn(&conn) sub delete_conn { local $lref = &read_file_lines($_[0]->{'file'}); splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1); &flush_file_lines(); }
# swap_conns(&conn1, &conn2) # Swaps two connections in the config file sub swap_conns { local ($first, $second) = @_; if ($first->{'line'} > $second->{'line'}) { ($first, $second) = ($second, $first); } local $lref1 = &read_file_lines($first->{'file'}); local $lref2 = &read_file_lines($second->{'file'}); splice(@$lref2, $second->{'line'}, $second->{'eline'} - $second->{'line'} + 1, @$lref1[$first->{'line'} .. $first->{'eline'}]); splice(@$lref2, $first->{'line'}, $first->{'eline'} - $first->{'line'} + 1, @$lref1[$second->{'line'} .. $second->{'eline'}]); &flush_file_lines(); }
# conn_lines(&conn) sub conn_lines { local @rv; push(@rv, $_[0]->{'name'}." ".$_[0]->{'value'}); foreach $o (sort { $a cmp $b } keys %{$_[0]->{'values'}}) { local $v = $_[0]->{'values'}->{$o}; local $vv; foreach $vv (split(/\0/, $v)) { if ($vv =~ /\s|=/) { push(@rv, "\t".$o."=\"".$vv."\""); } else { push(@rv, "\t".$o."=".$vv); } } } return @rv; }
# is_ipsec_running() sub is_ipsec_running { local $out = `$config{'ipsec'} auto --status 2>&1`; return $? || $out =~ /not running/i ? 0 : 1; }
# get_public_key() # Returns this system's public key sub get_public_key { local $out = `$config{'ipsec'} showhostkey --file '$config{'secrets'}' --left 2>&1`; if ($out =~ /leftrsasigkey=(\S+)/) { return $1; } return undef; }
# get_public_key_dns() # Returns the flags, protocol, algorithm and key data for the public key, # suitable for creating a DNS KEY record sub get_public_key_dns { local $out = `$config{'ipsec'} showhostkey --file '$config{'secrets'}' 2>&1`; if ($out =~ /KEY\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) { return ($1, $2, $3, $4); } else { # Try with new --key argument $out = `$config{'ipsec'} showhostkey --key --file '$config{'secrets'}' 2>&1`; if ($out =~ /KEY\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) { return ($1, $2, $3, $4); } } return (); }
# list_policies() # Returns a list of all policy files sub list_policies { local ($f, @rv); opendir(DIR, $config{'policies_dir'}); while($f = readdir(DIR)) { push(@rv, $f) if ($f !~ /^\./ && $f !~ /\.rpmsave$/); } closedir(DIR); return @rv; }
# read_policy(name) sub read_policy { local @rv; open(FILE, "$config{'policies_dir'}/$_[0]"); while(<FILE>) { push(@rv, "$1/$2") if (/^\s*([0-9\.]+)\/(\d+)/); } close(FILE); return @rv; }
# write_policy(name, &nets) sub write_policy { local $lref = &read_file_lines("$config{'policies_dir'}/$_[0]"); local $l = 0; foreach $p (@{$_[1]}) { while($l < @$lref && $lref->[$l] !~ /^\s*([0-9\.]+)\/(\d+)/) { $l++; } if ($l < @$lref) { # Found line to replace $lref->[$l] = $p; } else { # Add at end push(@$lref, $p); } $l++; } while($l < @$lref) { if ($lref->[$l] =~ /^\s*([0-9\.]+)\/(\d+)/) { splice(@$lref, $l, 1); } else { $l++; } } &flush_file_lines(); }
# wrap_lines(text, width) # Given a multi-line string, return an array of lines wrapped to # the given width sub wrap_lines { local $rest = $_[0]; local @rv; while(length($rest) > $_[1]) { push(@rv, substr($rest, 0, $_[1])); $rest = substr($rest, $_[1]); } push(@rv, $rest) if ($rest ne ''); return @rv; }
# before_start() # Work out which log file IPsec messages go to, and record the size sub before_start { @ipsec_logfiles = ( $config{'logfile'} ); if (&foreign_check("syslog")) { # Find all syslog logfiles &foreign_require("syslog", "syslog-lib.pl"); local $conf = &syslog::get_config(); foreach $c (@$conf) { push(@ipsec_logfiles, $c->{'file'}) if ($c->{'file'} && -f $c->{'file'}); } } @ipsec_logfiles = &unique(@ipsec_logfiles); @ipsec_logfile_sizes = map { local @st = stat($_); $st[7] } @ipsec_logfiles; }
# after_start() # Check any new IPsec-related messages in the log for errors sub after_start { # Give the server a chance to start sleep(5);
# Look for new error messages local $i; for($i=0; $i<@ipsec_logfiles; $i++) { open(LOG, $ipsec_logfiles[$i]) || next; seek(LOG, $ipsec_logfile_sizes[$i], 0); while(<LOG>) { s/\r|\n//g; if (/ipsec/i && /error/i) { s/^(\S+)\s+(\d+)\s+(\d+:\d+:\d+)\s+(\S+)\s+//; push(@errs, $_); } } close(LOG); }
# Fail if there were any if (@errs) { &error(&text('start_elog', "<p><tt>".join("<br>", @errs)."</tt><br>")); } }
# get_ipsec_version(&out) sub get_ipsec_version { local $out = `$config{'ipsec'} --version 2>&1`; ${$_[0]} = $out; return $out =~ /(FreeS\/WAN|Openswan|StrongSWAN)\s+([^ \n\(]+)/i ? ($2,$1) : (undef); }
# got_secret() # Returns 1 if a valid secret key file exists, 0 if not sub got_secret { local $gotkey; open(SEC, $config{'secrets'}) || return 0; while(<SEC>) { s/\r|\n//g; s/#.*$//; if (/Modulus:\s*(\S+)/) { $gotkey = 1; } } close(SEC); return $gotkey; }
# expand_conf(&config) sub expand_conf { my $conf = shift; for my $n (0..scalar(@$conf)-1) { $conn = @$conf[$n]; foreach my $key (keys(%{$conn->{'values'}})) { $expanded{$conn->{'value'}}->{$key} = $conn->{'values'}->{$key}; } } # now go through and expand alsos foreach my $k (keys(%expanded)) { $conn = \%{$expanded{$k}}; # XXX - only supporing a single level of redirection # - this should be moved into a function that could be called # recursively if ($$conn{'also'}) { foreach my $also (split(/\000/, $$conn{'also'})) { foreach my $i (keys(%{$expanded{$also}})) { $$conn{$i} = $expanded{$also}{$i}; } } # there is only one also key next; } } return %expanded; }
# restart_ipsec() # Apply the current configuration, and return an error message on failure or # undef on success sub restart_ipsec { local $cmd = $config{'restart_cmd'} || "($config{'stop_cmd'} && $config{'start_cmd'})"; &before_start(); local $out = &backquote_logged("$cmd 2>&1"); if ($?) { return "<pre>$out</pre>"; } &after_start(); return undef; }
# list_secrets() # Returns a list of IPsec secret keys sub list_secrets { if (!scalar(@list_secrets_cache)) { local (@lines); local $lnum = 0; open(SEC, $config{'secrets'}); while(<SEC>) { s/\r|\n//g; s/^\s*#.*$//; if (/^(\S.*)$/) { push(@lines, { 'value' => $1, 'line' => $lnum, 'eline' => $lnum }); } elsif (/^\s+(.*)/ && @lines) { $lines[$#rv]->{'value'} .= "\n".$1; $lines[$#rv]->{'eline'} = $lnum; } $lnum++; } close(SEC);
# Turn joined lines into secrets local $l; foreach $l (@lines) { $l->{'value'} =~ /^([^:]*)\s*:\s+(\S+)\s+((.|\n)*)$/ || next; local $sec = { 'type' => $2, 'name' => $1, 'value' => $3, 'line' => $l->{'line'}, 'eline' => $l->{'eline'}, 'idx' => scalar(@list_secrets_cache), }; $sec->{'name'} =~ s/\n/ /g; $sec->{'name'} =~ s/\s+$//; push(@list_secrets_cache, $sec); } } return @list_secrets_cache; }
# delete_secret(&sec) # Removes one secret from the file sub delete_secret { local $lref = &read_file_lines($config{'secrets'}); local $lines = $_[0]->{'eline'} - $_[0]->{'line'} + 1; splice(@$lref, $_[0]->{'line'}, $lines); &flush_file_lines(); local $s; splice(@list_secrets_cache, $_[0]->{'idx'}, 1); foreach $s (@list_secrets_cache) { if ($s->{'line'} > $_[0]->{'line'}) { $s->{'line'} -= $lines; $s->{'eline'} -= $lines; } if ($s->{'idx'} > $_[0]->{'idx'}) { $s->{'idx'}--; } } }
# create_secret(&sec) # Add one secret to the file sub create_secret { &list_secrets(); # force cache init local $lref = &read_file_lines($config{'secrets'}); $_[0]->{'line'} = scalar(@$lref); local @lines = &secret_lines($_[0]); push(@$lref, @lines); &flush_file_lines(); $_[0]->{'eline'} = scalar(@$lref)-1; $_[0]->{'idx'} = scalar(@list_secrets_cache); push(@list_secrets_cache, $_[0]); }
# modify_secret(&sec) # Update one secret in the file sub modify_secret { local $lref = &read_file_lines($config{'secrets'}); local @newlines = &secret_lines($_[0]); local $oldlines = $_[0]->{'eline'} - $_[0]->{'line'} + 1; splice(@$lref, $_[0]->{'line'}, $oldlines, @newlines); &flush_file_lines(); local $s; foreach $s (@list_secrets_cache) { if ($s ne $_[0] && $s->{'line'} > $_[0]->{'line'}) { $s->{'line'} += @newlines - $oldlines; $s->{'eline'} += @newlines - $oldlines; } } $_[0]->{'eline'} += @newlines - $oldlines; }
sub secret_lines { local $str = $_[0]->{'name'} ? $_[0]->{'name'}." : " : ": "; $str .= uc($_[0]->{'type'}); $str .= " ".$_[0]->{'value'}; return split(/\n/, $str); }
@rsa_attribs = ( "Modulus", "PublicExponent", "PrivateExponent", "Prime1", "Prime2", "Exponent1", "Exponent2", "Coefficient" );
1;
|