#!/usr/bin/perl
use strict;
use warnings;

use File::Temp qw(tempfile);
use Getopt::Long;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;

$|++;

# best called via 'gitolite trigger POST_COMPILE'; other modes at your own
# risk, especially if the rc file specifies arguments for it.  (That is also
# why it doesn't respond to "-h" like most gitolite commands do).

# option procesing
# ----------------------------------------------------------------------

# currently has one option:
#   -kfn, --key-file-name        adds the keyfilename as a second argument

my $kfn = '';
GetOptions( 'key-file-name|kfn' => \$kfn, );

tsh_try("sestatus");
my $selinux = ( tsh_text() =~ /enabled/ );

my $ab = $rc{GL_ADMIN_BASE};
trace( 1, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
my $akdir        = "$ENV{HOME}/.ssh";
my $akfile       = "$ENV{HOME}/.ssh/authorized_keys";
my $glshell      = $rc{GL_BINDIR} . "/gitolite-shell";
my $auth_options = auth_options();

sanity();

# ----------------------------------------------------------------------

_chdir($ab);

# old data
my $old_ak = slurp($akfile);
my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile);
chomp(@non_gl);
my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );

# pubkey files
chomp( my @pubkeys = `find keydir/ -type f -name "*.pub" | sort` );
my @gl_keys = ();
for my $f (@pubkeys) {
    my $fp = fp($f);
    if ( $seen{$fp} ) {
        _warn "$f duplicates $seen{$fp}, sshd will ignore it";
    } else {
        $seen{$fp} = $f;
    }
    push @gl_keys, grep { /./ } optionise($f);
}

# dump it out
my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";

my $ak = slurp($akfile);
_die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
_print( $akfile, $out );

_warn "you have no keys left; I hope you intended to do that!" unless @gl_keys;

# ----------------------------------------------------------------------

sub sanity {
    _die "'$glshell' not found; this should NOT happen..."                if not -f $glshell;
    _die "'$glshell' found but not readable; this should NOT happen..."   if not -r $glshell;
    _die "'$glshell' found but not executable; this should NOT happen..." if not -x $glshell;

    my $n = "    (this is normal on a brand new install)";
    _warn "$akdir missing; creating a new one\n$n"  if not -d $akdir;
    _warn "$akfile missing; creating a new one\n$n" if not -f $akfile;

    _mkdir( $akdir, 0700 ) if not -d $akdir;
    if ( not -f $akfile ) {
        _print( $akfile, "" );
        chmod 0700, $akfile;
    }
}

sub auth_options {
    my $auth_options = $rc{AUTH_OPTIONS};
    $auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";

    return $auth_options;
}

sub fp {
    # input: see below
    # output: a (list of) FPs
    my $in = shift || '';
    if ( $in =~ /\.pub$/ ) {
        # single pubkey file
        _die "bad pubkey file '$in'" unless $in =~ $REPONAME_PATT;
        return fp_file($in);
    } elsif ( -f $in ) {
        # an authkeys file
        return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in);
    } else {
        # one or more actual keys
        return map { fp_line($_) } grep { !/^#/ and /\S/ } ( $in, @_ );
    }
}

sub fp_file {
    return $selinux++ if $selinux;    # return a unique "fingerprint" to prevent noise
    my $f  = shift;
    my $fp = `ssh-keygen -l -f '$f'`;
    chomp($fp);
    _die "fingerprinting failed for '$f'" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
    $fp = $1;
    return $fp;
}

sub fp_line {
    my ( $fh, $fn ) = tempfile();
    print $fh shift() . "\n";
    close $fh;
    my $fp = fp_file($fn);
    unlink $fn;
    return $fp;
}

sub optionise {
    my $f = shift;

    my $user = $f;
    $user =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
    $user =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz@home.pub -> baz

    my @line = slurp($f);
    if ( @line != 1 ) {
        _warn "$f does not contain exactly 1 line; ignoring";
        return '';
    }
    chomp(@line);
    return "command=\"$glshell $user" . ( $kfn ? " $f" : "" ) . "\",$auth_options $line[0]";
}

