#!/usr/bin/perl
# udptunnel-reconf
# Set up the relevant stuff in /etc/userv/vpn, and then run
# this.  It should tell you what to do to inittab and ipif-networks.

# This file is part of ipif, part of userv-utils
#
# Copyright 1996-2013 Ian Jackson <ijackson@chiark.greenend.org.uk>
# Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk>
# Copyright 1999,2003
#    Chancellor Masters and Scholars of the University of Cambridge
# Copyright 2010 Tony Finch <fanf@dotat.at>
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with userv-utils; if not, see http://www.gnu.org/licenses/.

use Socket;

# @@@-
$shareuserv= "`pwd`";
$etcvpn= "`pwd`";
$varlibvpn= "`pwd`";
# -@@@

sub badusage () { die "usage: udptunnel-reconf [<directory>]\n"; }
$debug=0;

sub fault ($) { die "$0: $_[0]\n"; }

while ($ARGV[0] =~ m/^-/) {
    $_= shift @ARGV;
    last if m/^--$/;
    if (m/^-d$/) { $debug++; next; }
    badusage();
}

if (@ARGV) { $etcvpn= shift @ARGV; }
badusage() if @ARGV;

chdir $etcvpn or fault("$etcvpn: $!");

sub run_m4 ($$$) {
    my ($wanted, $site, $variable) = @_;
    $x= "m4 -P -DWANTED=$wanted -DWHVARIABLE=V_$variable -DV_global=global ".
	"-DV_site=$site -DV_varlibvpn=$varlibvpn ".
  	"-DV_defaults=$shareuserv/udptunnel-vpn-defaults ".
	"$shareuserv/udptunnel-vpn-config.m4";
    print STDERR $x,"\n" if $debug>=2;
    open X, "$x |" or die $!;
    undef $/;
    $m4out= <X>;
    $/= "\n";
    $!=0; close X; $? and die "m4 failed with code $? $!";
    $m4out =~ s/^\s+//;
    $m4out =~ s/\n+/\n/g;
    $m4out =~ s/\s+$//;
    print STDERR "$wanted/$variable($site) -> \`$m4out'\n" if $debug>=1;
#    $m4out='' if $wanted eq 'VARIABLE' && substr($m4out,0,2) eq 'V_';
    return $m4out;
}

sub check_junk ($$) {
    my ($emsg, $site) = @_;
    $j= run_m4('JUNK',$site,'');
    $j =~ s/^\# //g;
    fault("$emsg: $j") if length $j;
}

sub var_global ($) { return run_m4('VARIABLE','',$_[0]) }
sub var_site ($) { return run_m4('VARIABLE',$site,$_[0]) }

check_junk("error in config",'');

@actives= split /\s+/, run_m4('ACTIVES','','');
@passives= split /\s+/, run_m4('PASSIVES','','');

foreach $site (@actives, @passives) {
    check_junk("error in config for site $site",$site);
}

sub parse_addr_mask ($) {
    my ($r) = @_;
    my ($mask,$iaddr);
    if ($r =~ s,/(\d+)$,,) { $mask=$1; } else { $mask=32; }
    fault("invalid mask length $1") if $mask<0 || $mask>32;
    $mask= $mask ? ~0 << (32-$mask) : 0;
    $iaddr= inet_aton($r); fault("invalid address $r") unless defined $iaddr;
    $iaddr= (unpack "N",$iaddr)[0];
    return ($iaddr, $mask);
}

sub ipif_permit ($$$$) {
    my ($group,$local,$net,$why) = @_;
    my ($pmask,$piaddr,$fmask,$fiaddr,@lgroup,$lgid);

    @lgroup= getgrnam($group);
    @lgroup or fault("invalid group \`$group' ($why)");
    $lgid= $lgroup[2];

    if (!$local) {
	($piaddr,$pmask) = parse_addr_mask($net);
	foreach $fref (@forbid_remote) {
	    ($fiaddr,$fmask) = @$fref;
	    $jmask= $fmask & $pmask;
#printf STDERR "%8lx %8lx %l8x %8lx", $pmask,$pmask
	    fault("local network $net claimed as remote ($why)")
		if (($fiaddr&$jmask) == ($piaddr&$jmask));
	}
    }
    $ipif_file .= "$lgid,$local$net,	$group,	$why\n";
}

$glgroup= var_global('lgroup');
$glend= var_site('lend')."/32";
if ($glend !~ m/^V_/ && $glgroup !~ m/^V_/ &&
    length $glend && length $glgroup) {
    ipif_permit($glgroup, '=', "$glend", 'local endpoint');
} else {
    $glend='X'; $glgroup='X';
}

foreach $site (@actives, @passives) {
    $forbid_remote= var_site('forbid_remote');
    @forbid_remote= ();
    if ($forbid_remote ne '-') {
	foreach $r (split /[, \t]+/, $forbid_remote) {
	    push @forbid_remote, [ parse_addr_mask($r) ];
	}
    }

    $tlend= var_site('lend')."/32";
    $tlgroup= var_site('lgroup');
    if ($tlend ne $glend || $tlgroup ne $glgroup) {
	ipif_permit($tlgroup, '=', $tlend, "$site - local endpoint");
    }
    $trend= var_site('rend').'/32';
    $ix= 0;
    $trnets= var_site('rnets');
    ipif_permit($tlgroup, '', $trend, "$site - remote endpoint");
    if ($trnets ne '-') {
	foreach $rnet (split /,/, $trnets) {
	    ipif_permit($tlgroup, '', $rnet, "$site - remote network #$ix");
		$ix++;
	}
    }
}

sub write_file ($$$$) {
    my ($fn,$why,$head,$body) = @_;
    length $fn or fault("location to write $why not specified");
    open F, ">$fn.new" or fault("create $fn.new: $!");
    print F $head."\n# AUTOGENERATED BY $0 - DO NOT EDIT\n".$body or die $!;
    close F or die $!;
    rename "$fn.new",$fn or die $!;
}

$ipifnetsfile= var_global(ipifnetsfile);
write_file($ipifnetsfile,'ipifnetsfile','', $ipif_file);

$active_file= '';
$knownhosts_file= '';
$inittab= '';
$ix= 0;
foreach $site (@actives) {
    $active_file.= "$site\t".var_site('activesxinfo')."\n";
    $inittab.= sprintf("t%d", $ix++).':'.var_site('inittab_line')."\n";
    $hostkey= var_site('rhostkey');
    $knownhosts_file.= var_site('sshdest').' '.$hostkey."\n"
	if length $hostkey;
    $invoke_file= var_site('invoke_file');
    write_file($invoke_file, 'invoke_file',
	       var_site('invoke_head')."\n", var_site('invoke_body'));
    chmod 0777&~umask, $invoke_file or die $!;
}
write_file(var_global('knownhostsfile'),'knownhostsfile', '',$knownhosts_file);
write_file(var_global('activesfile'),'activesfile', '',$active_file);

print
"# You can cut and paste all or part of this into your inittab if you like:\n",
    $inittab;

print
"# And consider adding this line, or some of this file's contents,\n".
"# to your /etc/userv/ipif-networks:\n",
    "$ipifnetsfile\n"
    if $ipifnetsfile =~ m,^/,;

$passive_file= '';
foreach $site (@passives) {
    $passive_file.= "$site\t".var_site('passivesxinfo')."\n";
}
write_file(var_global('passivesfile'),'passivesfile', '',$passive_file);

system var_global('postconfigure'); $? and exit -1;

exit 0;
