1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
#!/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";
}
|