#!/usr/bin/perl use strict; use warnings; use lib $ENV{GL_LIBDIR}; use Gitolite::Rc; use Gitolite::Common; =for usage Please see usage at https://www.immae.eu/docs/forge-logicielle/gitolite.html#inviter-des-collaborateurs =cut usage() if @ARGV and ($ARGV[0] eq '-h' or $ARGV[0] eq '--help'); my $rb = $rc{GL_REPO_BASE}; my $ab = $rc{GL_ADMIN_BASE}; # get to the keydir _chdir("$ab/keydir"); # save arguments for later my $operation = shift || 'list'; my $invitekeyid = shift || ''; $invitekeyid and $invitekeyid !~ /^[-0-9a-z_]+@[-0-9a-z_]+$/i and die "invalid keyid $invitekeyid\n"; my ($invited, $keyid) = split /@/, $invitekeyid; # get the actual userid and keytype my $gl_user = $ENV{GL_USER}; die "This function is reserved for actual users" if $gl_user =~ s/-invite-(.*)$//; # ---- # first collect the keys my ( @invited_keys ); for my $pubkey (`find . -type f -name "*.pub" | sort`) { chomp($pubkey); $pubkey =~ s(^./)(); # artifact of the find command my $user = $pubkey; $user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub $user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz if ( $user =~ m(^(zzz-marked-for-...-)?$gl_user-invite-) ) { push @invited_keys, $pubkey; } } # ---- # list mode; just do it and exit sub print_keylist { my ( $message, @list ) = @_; return unless @list; print "== $message ==\n"; my $count = 1; for (@list) { my $fp = fingerprint($_); s/(zzz-marked-for-...-)?$gl_user-invite-//g; s/\.pub$//; s(.*/)(); print $count++ . ": $fp : $_\n"; } } if ( $operation eq 'list' ) { print "you have the following invited keys:\n"; print_keylist( "keys for invited persons", @invited_keys ); print "\n\n"; exit; } # ---- # please see docs for details on how a user interacts with this die "valid operations: add, del\n" unless $operation =~ /^(add|del)$/; if ( $operation eq 'add' ) { print STDERR "please supply the new key on STDIN. (I recommend you don't try to do this interactively, but use a pipe)\n"; kf_add( $gl_user, $invited, $keyid, safe_stdin() ); } elsif ( $operation eq 'del' ) { kf_del( $gl_user, $invited, $keyid ); } exit; # ---- # make a temp clone and switch to it our $TEMPDIR; BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; } END { `rm -rf $TEMPDIR`; } sub cd_temp_clone { chomp($TEMPDIR); hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR" ); chdir($TEMPDIR); my $hostname = `hostname`; chomp($hostname); hushed_git( "config", "--get", "user.email" ) and hushed_git( "config", "user.email", $ENV{USER} . "@" . $hostname ); hushed_git( "config", "--get", "user.name" ) and hushed_git( "config", "user.name", "$ENV{USER} on $hostname" ); } sub fingerprint { my ($fp, $output) = ssh_fingerprint_file(shift); # Do not print the output of $output to an untrusted destination. die "does not seem to be a valid pubkey\n" unless $fp; return $fp; } sub safe_stdin { # read one line from STDIN my $data; my $ret = read STDIN, $data, 4096; # current pubkeys are approx 400 bytes so we go a little overboard die "could not read pubkey data" . ( defined($ret) ? "" : ": $!" ) . "\n" unless $ret; die "pubkey data seems to have more than one line\n" if $data =~ /\n./; return $data; } sub hushed_git { local (*STDOUT) = \*STDOUT; local (*STDERR) = \*STDERR; open( STDOUT, ">", "/dev/null" ); open( STDERR, ">", "/dev/null" ); system( "git", @_ ); } sub highlander { # there can be only one my ( $keyid, $die_if_empty, @a ) = @_; # too many? if ( @a > 1 ) { print STDERR " more than one key satisfies this condition, and I can't deal with that! The keys are: "; print STDERR "\t" . join( "\n\t", @a ), "\n\n"; exit 1; } # too few? die "no keys with " . ( $keyid || "empty" ) . " keyid found\n" if $die_if_empty and not @a; return @a; } sub kf_add { my ( $gl_user, $invited, $keyid, $keymaterial ) = @_; # add a new "invited" key for $gl_user. cd_temp_clone(); chdir("keydir"); mkdir("invited"); _print( "invited/$gl_user-invite-$invited\@$keyid.pub", $keymaterial ); hushed_git( "add", "." ) and die "git add failed\n"; my $fp = fingerprint("invited/$gl_user-invite-$invited\@$keyid.pub"); hushed_git( "commit", "-m", "invite add $gl_user-invite-$invited\@$keyid ($fp)" ) and die "git commit failed\n"; system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n"; } sub kf_del { my ( $gl_user, $invited, $keyid ) = @_; cd_temp_clone(); chdir("keydir"); my @pk = highlander( $keyid, 1, grep { m(^(.*/)?(zzz-marked-for-...-)?$gl_user-invite-$invited\@$keyid.pub$) } @invited_keys ); my $fp = fingerprint( $pk[0] ); hushed_git( "rm", $pk[0]) and die "git mv failed\n"; hushed_git( "commit", "-m", "invite del $pk[0] ($fp)" ) and die "git commit failed\n"; system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n"; }