#!/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( 2, "'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 if (@gl_keys) { 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 ); } # ---------------------------------------------------------------------- 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; _warn "$akdir missing; creating a new one" if not -d $akdir; _warn "$akfile missing; creating a new one" 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]"; }