#!/usr/bin/perl5

#
# Copyright (c) 1996 Berkeley Software Design, Inc.
# All rights reserved.
# The Berkeley Software Design Inc. software License Agreement specifies
# the terms and conditions for redistribution.
#
#	BSDI	routed.pl,v 1.5 1998/02/03 03:34:48 jch Exp

#
#  Routed emulation using gated
#

use POSIX qw(:sys_wait_h setsid);

sub _POSIX_C_SOURCE { 1; }

BEGIN {
    push(@INC, "/usr/libdata/perl");
    require 'paths.ph';

}

require 'getopts.pl';

$LOCK_EX = 0x01;
$LOCK_NB = 0x04;

$tracing = "";
$_PATH_PID = "/var/run/gated.pid";
$CONF = "/tmp/gated.conf.$$";
$ERRS = "/tmp/gated.errs.$$";
$GATEWAYS = "/etc/gateways";
($ARGV0 = $0) =~ s#.*/##;
$ARGS = join($",@ARGV);

# Fork (a couple of times), detach from the controling terminal
# and redirect stdio to /dev/null.
sub daemon {
    unless ( $pid = fork() ) {
	unless ( fork ) {
	    setsid() || 
		&err("setsid: $!");
	    open(STDIN, &_PATH_DEVNULL) ||
		&err("open /dev/null: $!");
	    open(STDOUT, ">" . &_PATH_DEVNULL) ||
		&err("open /dev/null: $!");
	    open(STDERR, ">" . &_PATH_DEVNULL) ||
		&err("open /dev/null: $!");
	    return;
	}
	exit(0);
    }
    waitpid($pid, 0);

    exit(0);
}

# Decode the exit status of a child process
sub decode_status {
    my($status) = @_;

    if ( &WIFSTOPPED($status) ) {
	return "stopped by SIG" . &WSTOPSIG($status);
    } elsif ( &WIFSIGNALED($status) ) {
	# XXX - core dump
	return "terminated by SIG" . &WSTOPSIG($status);
    } elsif ( &WIFEXITED($status) && &WEXITSTATUS($status) ) {
	return "exited with code " . &WEXITSTATUS($status);
    } else {
	return;
    }
}

sub usage {
    print(STDERR "Usage: $ARGV0 [-q|s] [-t] [logfile]\n");
    &terminate(1);
}

# Invoke logger to log a message to syslog and the specified file.
sub syslog {
    my($level, $msg) = @_;

    system("logger -s -t $ARGV0 -p daemon.$level -- $msg");
}

# Log the specified file to syslog with the specified priority.
sub syslog_file {
    my($pri, $FILE) = @_;

    open(FILE) || return;
    while (<FILE>) {
	&syslog($pri, $_);
    }
    close(FILE);
}

# Log the specified message with syslog and print it on stderr, then terminate
sub err {
    &syslog('err', @_);
    &terminate(1);
}

# Print a warning on stderr
sub warn {
    printf(STDERR "%s: %s\n", $ARGV0, @_);
}

# Return the value of the specified sysctl variable
sub sysctl {
    my($name, $value) = @_;

    unless (open(SYSCTL, "sysctl -n $name|")) {
	&warn("no such sysctl variable: $name");
	return undef;
    }
    chop($nvalue = <SYSCTL>);
    close(SYSCTL);

    return $nvalue;
}

# If gated is running it will have a lock on the PID file.
sub check_running {

    open(PID, $_PATH_PID) || return;
    if ( ! flock(PID, $LOCK_EX|$LOCK_NB) ) {
	chop($pid = <PID>);
	&err("gated is already running, pid $pid!");
    }
    close(PID);
}

# Ask Gated to check the configuration
sub check_conf {
    my($CONF) = @_;

    system("gated -C -f $CONF 2> $ERRS");
    if ( $_ = decode_status($?) ) {
	&syslog('err', "internal error with configuration, gated $_\n");
	&syslog_file('err', $ERRS);
	&terminate(1);
    }
    unlink($ERRS);
}

# Write a configuration file
sub write_config {

    unlink($CONF);
    open(CONF, ">$CONF") || &err("unable to create $CONF: $!");

    print(CONF "#\n#\n# THIS FILE IS AUTOMATICALLY GENERATED DO NOT EDIT!\n#\n");
    printf(CONF "# Gated configuration to emulate: \"%s %s\"\n#\n\n", 
	   $ARGV0, $ARGS);

    if ( $tracing ) {
	printf(CONF "\ntraceoptions%s ;\n", $tracing);
    }

    # RIP
    print(CONF "\nrip yes {\n");
    if ( $opt_t ) {
	printf(CONF "\ttraceoptions%s detail packets ;\n", $tracing);
    }
    if ( $opt_q ) {
	printf(CONF "\tnobroadcast ;\n");
    } elsif ( $opt_s ) {
	print(CONF "\tbroadcast ;\ninterface all version 2 broadcast ;\n");
    }
    print(CONF "} ;\n");
    

    # Router discovery
    if ( $opt_s || ( !$opt_q && $ipforwarding ) ) {
	print(CONF "\nrouterdiscovery server yes ;\n");
    } else {
	printf(CONF "\nrouterdiscovery client yes ;\n");
    }
    if ( $opt_t ) {
	printf(CONF "\nicmp {\n\ttraceoptions%s detail routerdiscovery ;\n} ;\n",
	       $tracing);
    }

    # If Gated tries to talk to snmpd and it's not around gated will generate 
    # an error message every minute :-(
    print(CONF "\nsnmp no ;\n");

    close(CONF) || &err("error closing $CONF: $!");
}

#

# Remove temp files and exit with the specified code
sub terminate {
    my($rc) = @_;

    # Remove the configuration file
    unlink($CONF, $ERRS);

    exit $rc;
}

# If gated is running, kill it.  If not, terminate.
sub sig_term {
    if ( ! $gated_pid ) {
	&terminate(0);
    }
    kill("TERM", $gated_pid);
}

# We do not support SIGINT
sub sig_delete {
    my($sig) = @_;

    &syslog('warning', "SIG$sig to delete routes is not supported");
}

# We do not support SIGUSR1 and SIGUSR2
sub sig_trace {
    my($sig) = @_;

    &syslog('warning', "SIG$sig to manipulate tracing is not supported, use \`gdc toggletrace\`");
}
#

# Parse and verify the arguments
&Getopts("dgqst") || &usage;
if ( $opt_g ) {
    &err("-g not supported");
} elsif ( $opt_d ) {
    &warn("-d ignored");
}
# Setup tracing
if ( $#ARGV == 0 ) {
    $logfile = shift(@ARGV);
    if ( $opt_t ) {
	$tracing = ' general';
    } else {
	$tracing = ' normal';
    }
}
# Check for extra arguments
if ( $#ARGV != -1 ) {
    &usage;
}
if ( $opt_q && $opt_s ) {
    &usage();
}
$ipforwarding = &sysctl("net.inet.ip.forwarding") + 0;
if ( $opt_s && ! $ipforwarding ) {
    syslog('warning', '-s used with IP forwarding disabled');
}

if ( ( ! $opt_t ) || $logfile ) {
    $detach = 1;
}

# We don't emulate /etc/gateways
if ( -e $GATEWAYS ) {
    &err("emulation for /etc/gateways is not provided");
}

# Write a configuration
&write_config;

# Verify that gated likes the configuration
&check_conf($CONF);

# Verify that gated is not running
&check_running;

# Verify that we are root
unless ( $> == 0 ) {
    &err("may only be run as root");
}

# Go into daemon mode if we are not tracing to the terminal
if ( $detach ) {
    &daemon;
} 

# Set signal handlers
$SIG{'HUP'} = 'sig_term';
$SIG{'TERM'} = 'sig_term';
$SIG{'INT'} = 'sig_delete';
$SIG{'USR1'} = 'sig_trace';
$SIG{'USR2'} = 'sig_trace';

# Build the arg list
@args = ('gated', '-N', "-f$CONF");
if ( $opt_t ) {
    push(@args, '-troute');
} elsif ( $logfile ) {
    push(@args, '-troute', $logfile);
}

# Fork and run gated.
unless ( $gated_pid = fork() ) {
    if ( ! $detach ) {
	# Make sure gated does not get our signals
	setsid();
	print(STDERR "Starting @args\n");
    }
    $ENV{'GATED_ROUTED_EMUL'} = "1";
    exec(@args);
    &err(1, "exec of \"@args\" failed: $!");
}

# Wait for gated to exit or a signal
$pid = waitpid($gated_pid, 0);
if ( $_ = decode_status($?) ) {
    &syslog('notice', "@args $_");
} elsif ( ! $detach ) {
    printf(STDERR "@args terminated normally\n");
}

&terminate(0);
