#! /usr/bin/perl

# 
# Copyright 1999-2016 University of Chicago
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
# http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 

use strict;
use warnings;

use Fcntl qw( SEEK_END );
use Getopt::Long;

#
# grid-mapfile-add-entry
#

my $PROGRAM_NAME=$0; $PROGRAM_NAME =~ s|.*/||;
my $PROGRAM_VERSION="12.7";

my $VERSION="12.7";
my $PACKAGE="globus_gss_assist";

my $DIRT_TIMESTAMP="1653033972";
my $DIRT_BRANCH_ID="1";

my $NEW_GRID_MAP_FILE;
my $secconfdir="/etc/grid-security";
my $GRID_MAP_FILE = $ENV{GRIDMAP} || "${secconfdir}/grid-mapfile";
my $dryrun = 0;
my $force = 0;

my $short_usage="$PROGRAM_NAME -dn DN -ln LN [-help] [-d] [-f mapfile FILE] [-force]";

sub long_usage
{
    print STDERR <<EOF;
${short_usage}

$PROGRAM_NAME adds an entry to a Grid mapfile.

Options:
  -help, -usage           Displays help
  -version                Displays version
  -dn DN                  Distinguished Name (DN) to add. Remember to 
                          quote the DN if it contains spaces.
  -ln LN1 [LN2...]        Local login name(s) to map DN to
  -dryrun, -d             Shows what would be done but will not add the entry
  -mapfile FILE, -f FILE  Path of Grid map file to be used

  -force                  Make modifications even if user does not exist (needed for B2STAGE)

EOF
}

sub globus_args_option_error
{
    print STDERR <<EOF;
ERROR: option $1 : $2

Syntax : ${short_usage}

Use -help to display full usage.
EOF
    exit(1);
}

my $help = 0;
my $version = 0;
my $versions = 0;
my $dn;
my @ln;
my $lnstring;

Getopt::Long::Configure('no_auto_abbrev');
GetOptions(
    "help|h|usage" => \$help,
    "version" => \$version,
    "versions" => \$versions,
    "dn=s" => \$dn,
    "ln=s{1,}" => \@ln,
    "d|dryrun" => \$dryrun,
    "f|mapfile=s" => \$GRID_MAP_FILE,
    "force" => \$force);

$lnstring = join(" ", @ln);
if ($help)
{
    &long_usage();
    exit(0);
}
elsif ($version)
{
    print "$0: ${PROGRAM_VERSION}\n";
    exit(0);
}
elsif ($versions)
{
    print "${PACKAGE}: ${VERSION} (${DIRT_TIMESTAMP}-${DIRT_BRANCH_ID})\n";
    exit(0);
}

if (scalar(@ln) == 0 || $dn eq '')
{
    print STDERR <<EOF;
Syntax : ${short_usage}

Use -help to display full usage.

EOF
    exit(1);
}


my $secure_tmpdir=$GRID_MAP_FILE;
if ($secure_tmpdir =~ m|/|) {
    $secure_tmpdir =~ s|/[^/]*$||;
} else {
    $secure_tmpdir = ".";
}


if (! (-r $secure_tmpdir && -w $secure_tmpdir) )
{
    print STDERR "ERROR: This script requires read/write permissions in ${secure_tmpdir}";
    exit(1);
}

$NEW_GRID_MAP_FILE="${secure_tmpdir}/.new_mapfile.$$";

# Verify mapfile existence
print "Modifying $GRID_MAP_FILE ...\n";
local(*GRID_MAP_FILE);

if (! -f $GRID_MAP_FILE)
{
    umask 022;
    open(GRID_MAP_FILE, "+>", $GRID_MAP_FILE)
        || die "ERROR: Could not create $GRID_MAP_FILE";
}
else
{
    open(GRID_MAP_FILE, "+<", $GRID_MAP_FILE)
        || globus_args_option_error("-f", \"${GRID_MAP_FILE}\" is not readable.");
}

# Change mode of existing map file to read only (logical UNIX lock)
chmod 0400, $GRID_MAP_FILE
    || die "ERROR: Could not change mode of $GRID_MAP_FILE\n";

print "Verifying that Local Name(s)=($lnstring) are legitimate local accounts.\n"
    if ($dryrun);

if (not $force)
{
  for my $name (@ln)
  {
    print "Checking ln(s)=$name\n" if ($dryrun);

    my @s = getpwnam($name);
    if (scalar(@s) > 0)
    {
        print "Local Name=$name does exist\n" if ($dryrun);
    }
    else
    {
        my $res = $!;
        print "entry not added because the LN(s) is/are not legitimate\n";
        print "Local Name=$name does *NOT* exist\n" if ($dryrun);
        print "Entry *NOT* added\n" if ($dryrun);
        print "Result: $res\n" if ($dryrun);
        exit(1);
    }
  }
}

print "Local Name(s)=($lnstring) is/are valid. Requested entry will be added.\n"
    if ($dryrun);


my $updated_existing_dn;

my $dn_already_exists=0;
my $omitted_map = "";
my $added_map = "";
my @map_entries=();
my $new_mapfile_entry="";
my $new_grid_mapfile_data="";
my $orig_grid_mapfile_data="";

while (my $line = <GRID_MAP_FILE>)
{
    my $existing_dn;
    my %existing_lns;

    $orig_grid_mapfile_data .= $line;

    chomp($line);
    next if ($line eq '');

    if ($line !~ m/^["][^"]*["]/ && $line !~ m/^[^"]/)
    {
        print "The following entry is missing a closing double quote\n";
        print "$line\n";
        exit(1);
    }
    if ($line =~ m/^"([^"]*)"\s+(.*)$/)
    {
        $existing_dn = $1;
        if ($existing_dn eq $dn)
        {
            push(@map_entries, split(",", $2));
            %existing_lns = map { $_ => 1 } @map_entries;
        }
    }
    elsif ($line =~ m/^([^"]\S+)\s+(.*$)/)
    {
        $existing_dn = $1;
        if ($existing_dn eq $dn)
        {
            push(@map_entries, split(",", $2));
            %existing_lns = map { $_ => 1 } @map_entries;
        }
    }

    if ($existing_dn eq $dn)
    {
        $dn_already_exists=1;
        for my $name (@ln)
        {
            if (exists $existing_lns{$name})
            {
                $omitted_map .= " $name";
            }
            else
            {
                $added_map .= " $name";
                push(@map_entries, $name);
            }
        }
    }
    else
    {
        $new_grid_mapfile_data .= "$line\n";
    }
}
push(@map_entries, @ln);
if (@map_entries)
{
    my %seen = ();
    my $delim="";
    $new_mapfile_entry = "\"$dn\" ";

    foreach my $entry (@map_entries) 
    {
        if (!exists $seen{$entry})
        {
            $new_mapfile_entry .= "${delim}${entry}";
            $delim=",";
            $seen{$entry} = 1;
        }
    }
    $new_grid_mapfile_data .= "$new_mapfile_entry\n";
}

# Verify that no changes to original map file
# during the execution of this program
local(*CONSISTENCY_CHECK);
open(CONSISTENCY_CHECK, "<", $GRID_MAP_FILE)
    || die "ERROR: Unable to compare $GRID_MAP_FILE with original contents\n";
my $consistency_check = "";
while (<CONSISTENCY_CHECK>)
{
    $consistency_check .= $_;
}
close(CONSISTENCY_CHECK);

if ($orig_grid_mapfile_data ne $consistency_check)
{
    print STDERR "ERROR: $GRID_MAP_FILE has changed since this program started\n";
    print STDERR "No changes will be made.\n";
    exit(1);
}
else
{
    local(*GRID_MAP_FILE_COPY);
    open(GRID_MAP_FILE_COPY, ">", "$GRID_MAP_FILE.old")
        || die "ERROR: Could not create a copy of $GRID_MAP_FILE\n";
    print GRID_MAP_FILE_COPY $orig_grid_mapfile_data;
    close(GRID_MAP_FILE_COPY);

    # Restore proper permissions to original grid map file
    chmod 0644, $GRID_MAP_FILE
        || die "ERROR: Could not change mode of $GRID_MAP_FILE\n";
}

if (!$dn_already_exists)
{
    if ($dryrun)
    {
        print "Appending new entry $new_mapfile_entry\n";
        print "Since ( dryrun, -d ) option was used no actions were carried out";
        exit(0);
    }
    seek(GRID_MAP_FILE, 0, SEEK_END);
    print GRID_MAP_FILE "$new_mapfile_entry\n";

    print "New entry:\n";
    print "$new_mapfile_entry\n";
    print "(1) entry added\n";
}
else
{
    print "DN $dn already exists\n";

    print "Updating entry to $new_mapfile_entry\n";
    if ($dryrun)
    {
        print "Since ( dryrun, -d ) option was used no actions were carried out\n";
        exit(0);
    }
    if ($added_map ne '')
    {
        local(*NEW_GRID_MAP_FILE);
        open(NEW_GRID_MAP_FILE, ">", $NEW_GRID_MAP_FILE)
            || die "Could not create temporary gridmap file $NEW_GRID_MAP_FILE\n";
        print NEW_GRID_MAP_FILE $new_grid_mapfile_data;
        close(NEW_GRID_MAP_FILE);
        rename($NEW_GRID_MAP_FILE, $GRID_MAP_FILE)
            || die "ERROR: Could not create a new $GRID_MAP_FILE\n";
        $NEW_GRID_MAP_FILE=undef;

        if ($omitted_map ne '')
        {
            $omitted_map=", already present and ignored:$omitted_map";
        }
        print "(added mappings:$added_map$omitted_map)\n";
        print "Updated entry:";
        print "$new_mapfile_entry\n";
        print "(1) entry modified\n";
    }
    else
    {
        print "No changes were made - already present and ignored:$omitted_map\n"
    }
}

END
{

    unlink($NEW_GRID_MAP_FILE) if ($NEW_GRID_MAP_FILE && -f $NEW_GRID_MAP_FILE);

    if ($GRID_MAP_FILE)
    {
        chmod 0644, $GRID_MAP_FILE
            || die "ERROR: Could not change mode of $GRID_MAP_FILE back to 0644";
    }
}
