#!/usr/bin/env perl
# $Id: envauth.pl,v 1.10 2012/10/19 19:58:21 ksb Exp $
# $Source: /usr/msrc/usr/local/libexec/jacket/RCS/envauth.pl,v $
#
# Check for a list of environment variables which must match an RE:
#	$ENVAUTH_VAR_${name}=re	for each $name given, consumed
#	$ENVAUTH_NOT_${name}=forbidden for each $name given, consumed
#	$ENVAUTH_WARN=message	the failure message for stderr, consumed
#	$ENVAUTH_REVEAL=prefix	post action reveal action (op ~prefix command)
# Note that op itself can do this, so it would look like this is not
# a jacket you really need.  If you use "coat" then you might need it,
# because other jackets can push values into the environment, which you
# might want to compare before you allow the escalation.  For example
# timestamp's PAM hook might set something for us (or xdisplay) which
# must have to complete the access. --ksb

use strict;
use Getopt::Std;
# Auto-flush standard out, we need the child to see it ASAP
select STDERR; $| = 1;
select STDOUT; $| = 1;

my($progname, $usage, %opts);
($progname = $0) =~ s,.*/,,;

$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/openwin/bin:/usr/X11/bin:/usr/X11R6/bin';
my($Reveal) = 'ENVAUTH_REVEAL';

$usage = "$progname: usage -P pid [helmet-opts] -- mnemonic program euid:egid cred_type:cred";
getopts("VhHP:u:g:f:R:C:", \%opts);

if ($opts{'V'}) {
	print "$progname: ", '$Id: envauth.pl,v 1.10 2012/10/19 19:58:21 ksb Exp $', "\n";
	exit 0;
}
if ($opts{'H'}) {
	print "$progname: match target environment to allow/deny access\n",
		"ENVAUTH_VAR_\$name[=RE]\trequired match\n",
		"ENVAUTH_NOT_\$name[=RE]\tforbidden match \n",
		"ENVAUTH_WARN\t\tstandard failure message\n",
		"$Reveal\t\tstandard reveal feature\n";
	exit 0;
}

my($failMessage);
if (exists $ENV{'ENVAUTH_WARN'}) {
	$failMessage = $ENV{'ENVAUTH_WARN'};
	print "-ENVAUTH_WARN\n" unless $opts{'h'};
} else {
	$failMessage = 'Sorry';
}

if ($opts{'h'}) {
        print   "$usage\n",
                "$progname: usage -h\n",
                "$progname: usage -V\n",
		"P pid       process that we jacket\n",
                "h           print only this help message\n",
                "V           show only version info\n",
                "helmet-opts arguments to any op helmet program\n",
		"mnemonic    the requested op mnemonic\n",
		"program     client application\n",
		"euid:egid   target login and group uids\n",
		"cred_type   the credential type that granted access (groups, users, or netgroups)\n",
		"cred        the matching group, login, or netgroup\n";
	exit 0;
}

# Untaint our params, common code to most jackets. --ksb
shift @ARGV if (scalar(@ARGV) && $ARGV[0] =~ m/^--$/o);
if (scalar(@ARGV) != 4) {
	print STDERR "$progname: exactly 4 positional parameters required\n";
	print "64\n" if $opts{'P'};
	exit 64;
}
if ($ARGV[0] !~ m|^([-/\@\w.]+)$|o) {
	print STDERR "$progname: mnemonic is zero width, or spelled badly\n";
	print "64\n" if $opts{'P'};
	exit 64;
}
my($MNEMONIC) = $1;
if ($ARGV[1] !~ m|^([-/\@\w.]+)$|o) {
	print STDERR "$progname: program specification looks bogus\n";
	print "64\n" if $opts{'P'};
	exit 64;
}
my($PROGRAM) = $1;
if ($ARGV[2] !~ m/^(\d+):(\d+)$/o) {
	print STDERR "$progname: euid:egid format error (wants digits:digits)\n";
	print "65\n" if $opts{'P'};
	exit 65;
}
my($EUID, $EGID) = ($1, $2);
if ($ARGV[3] !~ m/^([^:]*):([^:]*)$/o) {
	print STDERR "$progname: cred_type:cred $ARGV[3] missing colon\n";
	print "76\n" if $opts{'P'};
	exit 76;
}

my($kid, $status);
$status = 0;

my($v, $m);
foreach $v (keys %ENV) {
	next unless ($v =~ m/^ENVAUTH_VAR_(.+)/o);
	$m = $1;
	if (!exists $ENV{$m}) {
		print "# failed existance for $m\n";
		print STDERR $failMessage, "\n";
		$status = 69;
		last;
	}
	if ($ENV{$m} !~ m/$ENV{$v}/) {
		print "# failed RE match for $m /$ENV{$v}/\n";
		print STDERR $failMessage, "\n";
		$status = 77;
		last;
	}
	print "-$v\n";
}

# Check for fobidden values
foreach $v (keys %ENV) {
	next unless ($v =~ m/^ENVAUTH_NOT_(.+)/o);
	$m = $1;
	if (!exists $ENV{$m}) {
		next;
	}
	if ($ENV{$m} =~ m/$ENV{$v}/) {
		print "# failed RE exclusion for $m /$ENV{$v}/\n";
		print STDERR $failMessage, "\n";
		$status = 77;
		last;
	}
	print "-$v\n";
}


if ($status) {
	print "$status\n" if $opts{'P'};
	exit $status;
}

if (exists $ENV{$Reveal}) {
	print "-$Reveal\n~$ENV{$Reveal}\n";
}

print "# $progname access OK\n";
if ($opts{'P'}) {
	open STDOUT, ">/dev/null";
	$kid = waitpid $opts{'P'}, 0;
	$status = $? < 256 ? $? : ($? >> 8);
}

# no cleanup code for env auth

# Exit with her exit code.
exit $status;
