#! /usr/bin/perl
use strict;
use warnings;
use File::Spec qw(rel2abs);
use File::Basename;

my @args = ();
my $enabled = 0;
my $debug = 0;
my $debug_symlinks = 0;
my $debug_fd = *STDERR;

# Set up defaults
my %default;
$default{'DEB_BUILD_HARDENING'}=0;
$default{'DEB_BUILD_HARDENING_DEBUG'}=0;
$default{'DEB_BUILD_HARDENING_DEBUG_SYMLINKS'}=0;

# Architecture settings
# #OS# #ARCH#
$default{'DEB_BUILD_HARDENING_STACKPROTECTOR'}=1;
$default{'DEB_BUILD_HARDENING_FORTIFY'}=1;
$default{'DEB_BUILD_HARDENING_FORMAT'}=1;
$default{'DEB_BUILD_HARDENING_PIE'}=1;

# System settings
my $system_conf = '/etc/hardening-wrapper.conf';
if (-r $system_conf) {
    open(CONF,$system_conf) || warn "Cannot read $system_conf\n";
    while (my $line = <CONF>) {
        if ($line =~ /^\s*(DEB_BUILD_HARDENING[_A-Z]*)\s*=\s*(\d)$/) {
            $default{$1}=$2+0;
        }
    }
    close(CONF);
}

# Environment settings
$enabled =          defined($ENV{'DEB_BUILD_HARDENING'}) ?
                            $ENV{'DEB_BUILD_HARDENING'} :
                            $default{'DEB_BUILD_HARDENING'};
$debug =            defined($ENV{'DEB_BUILD_HARDENING_DEBUG'}) ?
                            $ENV{'DEB_BUILD_HARDENING_DEBUG'} :
                            $default{'DEB_BUILD_HARDENING_DEBUG'};
$debug_symlinks =   defined($ENV{'DEB_BUILD_HARDENING_DEBUG_SYMLINKS'}) ?
                            $ENV{'DEB_BUILD_HARDENING_DEBUG_SYMLINKS'} :
                            $default{'DEB_BUILD_HARDENING_DEBUG_SYMLINKS'};
my $force_stack =   defined($ENV{'DEB_BUILD_HARDENING_STACKPROTECTOR'}) ?
                            $ENV{'DEB_BUILD_HARDENING_STACKPROTECTOR'} :
                            $default{'DEB_BUILD_HARDENING_STACKPROTECTOR'};
my $force_fortify = defined($ENV{'DEB_BUILD_HARDENING_FORTIFY'}) ?
                            $ENV{'DEB_BUILD_HARDENING_FORTIFY'} :
                            $default{'DEB_BUILD_HARDENING_FORTIFY'};
my $force_format =  defined($ENV{'DEB_BUILD_HARDENING_FORMAT'}) ?
                            $ENV{'DEB_BUILD_HARDENING_FORMAT'} :
                            $default{'DEB_BUILD_HARDENING_FORMAT'};
my $force_pie =     defined($ENV{'DEB_BUILD_HARDENING_PIE'}) ?
                            $ENV{'DEB_BUILD_HARDENING_PIE'} :
                            $default{'DEB_BUILD_HARDENING_PIE'};
my $force_fPIE =    defined($ENV{'DEB_BUILD_HARDENING_PIE'}) ?
                            $ENV{'DEB_BUILD_HARDENING_PIE'} :
                            $default{'DEB_BUILD_HARDENING_PIE'};

if ($enabled) {
    # Scan arguments
    my $linking = 1;
    foreach my $arg (@ARGV) {
        if ($arg eq "-fno-PIC" ||
            $arg eq "-fno-pic" ||
            $arg eq "-fno-PIE" ||
            $arg eq "-fno-pie" ||
            $arg eq "-nopie" ||
            $arg eq "-static" ||
            $arg eq "-shared" ||
            $arg eq "-D__KERNEL__" ||
            $arg eq "-nostdlib" ||
            $arg eq "-nostartfiles")
        {
            # If any PIC or PIE things are explicitly disabled,
            # disable all our PIE flags.
            $force_fPIE = 0;
            $force_pie = 0;
        }
        if ($arg eq "-fPIC" ||
            $arg eq "-fpic")
        {
            # fPIC is a stricter version of fPIE, so don't use fPIE when
            # we encounter fPIC.  However, the inclusion of -fPIC does
            # not mean we need to block the use of "-pie", which is still
            # possible with fPIC.
            $force_fPIE = 0;
        }
        if ($arg eq "-c") {
            $linking = 0;
        }
        if ($arg =~ /^-D_FORTIFY_SOURCE(=|$)/) {
            $force_fortify = 0;
        }
        if ($arg eq "-nostdlib" ||
            $arg eq "-ffreestanding") {
            $force_stack = 0;
        }
    }

    # Enable SSP by default
    if ($force_stack) {
        push(@args,'-fstack-protector','--param=ssp-buffer-size=4');
    }

    # Enable -fPIE by default
    if ($force_fPIE) {
        push(@args, '-fPIE');
    }
    if ($force_pie) {
        if ($linking) {
            # "-pie" is really only meaningful when calling the linker
            push(@args, '-pie');
        }
    }

    # Enable glibc protections by default (-02 should already be defined...)
    # (disable with -D_FORTIFY_SOURCE=0)
    if ($force_fortify) {
        push(@args,'-D_FORTIFY_SOURCE=2');
    }

    # Enable format string checking
    if ($force_format) {
        push(@args,'-Wformat','-Wformat-security','-Werror=format-security');
    }
}

my $self = "\Qhardened-cc\E";
my $link = "";
my $arg0 = File::Spec->rel2abs(basename($0),dirname($0));
my $tool = $arg0;
if ($tool =~ /$self$/) {
    $tool = "/usr/bin/cc";
}

if (defined($ENV{'DEB_BUILD_HARDENING_DEBUG_OUTPUT'})) {
    $debug_fd = undef;
    if (!open($debug_fd, ">>$ENV{'DEB_BUILD_HARDENING_DEBUG_OUTPUT'}")) {
        die "Cannot open $ENV{'DEB_BUILD_HARDENING_DEBUG_OUTPUT'}: $!\n";
    }
}

sub resolve_link($)
{
    my $origin = $_[0];
    my $link = readlink($origin);
    return File::Spec->rel2abs($link,dirname($origin));
}

while (-l $tool && ($link = resolve_link($tool)) !~ /$self$/) {
    print $debug_fd "$tool -> $link\n" if ($debug_symlinks);
    $tool = $link;
}
if (-x "$tool.real") {
    print $debug_fd "$tool -real> $tool.real\n" if ($debug_symlinks);
    $tool = "$tool.real";
}
# Abort if we ended up on a circular symlink resolution
if ($tool eq $arg0) {
    my $short = $tool;
    $short =~ s/.*\///g;
	print STDERR "$tool: not found (maybe $short is not installed?)\n";
	exit(127);
}
my @target = ($tool, @args, @ARGV);

print $debug_fd join(" ",@target),"\n" if ($debug);

exec @target or die "Unable to exec $target[0]: $!\n";
