fixed up several broken URLs (minor but annoying)
[gitolite.git] / src / commands / sskm
blob435e15a5852386366a209fc2b8e759b7fde0c0d4
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
5 use lib $ENV{GL_LIBDIR};
6 use Gitolite::Rc;
7 use Gitolite::Common;
9 =for usage
10 Usage for this command is not that simple. Please read the full documentation
11 in doc/sskm.mkd or online at http://gitolite.com/gitolite/contrib/sskm.html.
12 =cut
14 usage() if @ARGV and $ARGV[0] eq '-h';
16 my $rb = $rc{GL_REPO_BASE};
17 my $ab = $rc{GL_ADMIN_BASE};
18 # get to the keydir
19 _chdir("$ab/keydir");
21 # save arguments for later
22 my $operation = shift || 'list';
23 my $keyid = shift || '';
24 # keyid must fit a very specific pattern
25 $keyid and $keyid !~ /^@[-0-9a-z_]+$/i and die "invalid keyid $keyid\n";
27 # get the actual userid and keytype
28 my $gl_user = $ENV{GL_USER};
29 my $keytype = '';
30 $keytype = $1 if $gl_user =~ s/^zzz-marked-for-(...)-//;
31 print STDERR "hello $gl_user, you are currently using "
32 . (
33 $keytype
34 ? "a key in the 'marked for $keytype' state\n"
35 : "a normal (\"active\") key\n"
38 # ----
39 # first collect the keys
41 my ( @pubkeys, @marked_for_add, @marked_for_del );
42 # get the list of pubkey files for this user, including pubkeys marked for
43 # add/delete
45 for my $pubkey (`find . -type f -name "*.pub" | sort`) {
46 chomp($pubkey);
47 $pubkey =~ s(^./)(); # artifact of the find command
49 my $user = $pubkey;
50 $user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub
51 $user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz
53 next unless $user eq $gl_user or $user =~ /^zzz-marked-for-...-$gl_user/;
55 if ( $user =~ m(^zzz-marked-for-add-) ) {
56 push @marked_for_add, $pubkey;
57 } elsif ( $user =~ m(^zzz-marked-for-del-) ) {
58 push @marked_for_del, $pubkey;
59 } else {
60 push @pubkeys, $pubkey;
64 # ----
65 # list mode; just do it and exit
66 sub print_keylist {
67 my ( $message, @list ) = @_;
68 return unless @list;
69 print "== $message ==\n";
70 my $count = 1;
71 for (@list) {
72 my $fp = fingerprint($_);
73 s/zzz-marked(\/|-for-...-)//g;
74 print $count++ . ": $fp : $_\n";
77 if ( $operation eq 'list' ) {
78 print "you have the following keys:\n";
79 print_keylist( "active keys", @pubkeys );
80 print_keylist( "keys marked for addition/replacement", @marked_for_add );
81 print_keylist( "keys marked for deletion", @marked_for_del );
82 print "\n\n";
83 exit;
86 # ----
87 # please see docs for details on how a user interacts with this
89 if ( $keytype eq '' ) {
90 # user logging in with a normal key
91 die "valid operations: add, del, undo-add, confirm-del\n" unless $operation =~ /^(add|del|confirm-del|undo-add)$/;
92 if ( $operation eq 'add' ) {
93 print STDERR "please supply the new key on STDIN. (I recommend you
94 don't try to do this interactively, but use a pipe)\n";
95 kf_add( $gl_user, $keyid, safe_stdin() );
96 } elsif ( $operation eq 'del' ) {
97 kf_del( $gl_user, $keyid );
98 } elsif ( $operation eq 'confirm-del' ) {
99 die "you dont have any keys marked for deletion\n" unless @marked_for_del;
100 kf_confirm_del( $gl_user, $keyid );
101 } elsif ( $operation eq 'undo-add' ) {
102 die "you dont have any keys marked for addition\n" unless @marked_for_add;
103 kf_undo_add( $gl_user, $keyid );
105 } elsif ( $keytype eq 'del' ) {
106 # user is using a key that was marked for deletion. The only possible use
107 # for this is that she changed her mind for some reason (maybe she marked
108 # the wrong key for deletion) or is not able to get her client-side sshd
109 # to stop using this key
110 die "valid operations: undo-del\n" unless $operation eq 'undo-del';
112 # reinstate the key
113 kf_undo_del( $gl_user, $keyid );
114 } elsif ( $keytype eq 'add' ) {
115 die "valid operations: confirm-add\n" unless $operation eq 'confirm-add';
116 # user is trying to validate a key that has been previously marked for
117 # addition. This isn't interactive, but it *could* be... if someone asked
118 kf_confirm_add( $gl_user, $keyid );
121 exit;
123 # ----
125 # make a temp clone and switch to it
126 our $TEMPDIR;
127 BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; }
128 END { `/bin/rm -rf $TEMPDIR`; }
130 sub cd_temp_clone {
131 chomp($TEMPDIR);
132 hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR" );
133 chdir($TEMPDIR);
134 my $hostname = `hostname`; chomp($hostname);
135 hushed_git( "config", "--get", "user.email" ) and hushed_git( "config", "user.email", $ENV{USER} . "@" . $hostname );
136 hushed_git( "config", "--get", "user.name" ) and hushed_git( "config", "user.name", "$ENV{USER} on $hostname" );
139 sub fingerprint {
140 my ($fp, $output) = ssh_fingerprint_file(shift);
141 # Do not print the output of $output to an untrusted destination.
142 die "does not seem to be a valid pubkey\n" unless $fp;
143 return $fp;
146 sub safe_stdin {
147 # read one line from STDIN
148 my $data;
149 my $ret = read STDIN, $data, 4096;
150 # current pubkeys are approx 400 bytes so we go a little overboard
151 die "could not read pubkey data" . ( defined($ret) ? "" : ": $!" ) . "\n" unless $ret;
152 die "pubkey data seems to have more than one line\n" if $data =~ /\n./;
153 return $data;
156 sub hushed_git {
157 local (*STDOUT) = \*STDOUT;
158 local (*STDERR) = \*STDERR;
159 open( STDOUT, ">", "/dev/null" );
160 open( STDERR, ">", "/dev/null" );
161 system( "git", @_ );
164 sub highlander {
165 # there can be only one
166 my ( $keyid, $die_if_empty, @a ) = @_;
167 # too many?
168 if ( @a > 1 ) {
169 print STDERR "
170 more than one key satisfies this condition, and I can't deal with that!
171 The keys are:
174 print STDERR "\t" . join( "\n\t", @a ), "\n\n";
175 exit 1;
177 # too few?
178 die "no keys with " . ( $keyid || "empty" ) . " keyid found\n" if $die_if_empty and not @a;
180 return @a;
183 sub kf_add {
184 my ( $gl_user, $keyid, $keymaterial ) = @_;
186 # add a new "marked for addition" key for $gl_user.
187 cd_temp_clone();
188 chdir("keydir");
190 mkdir("zzz-marked");
191 _print( "zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub", $keymaterial );
192 hushed_git( "add", "." ) and die "git add failed\n";
193 my $fp = fingerprint("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub");
194 hushed_git( "commit", "-m", "sskm: add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
195 system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
198 sub kf_confirm_add {
199 my ( $gl_user, $keyid ) = @_;
200 # find entries in both @pubkeys and @marked_for_add whose basename matches $gl_user$keyid
201 my @pk = highlander( $keyid, 0, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys );
202 my @mfa = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add );
204 cd_temp_clone();
205 chdir("keydir");
207 my $fp = fingerprint( $mfa[0] );
208 if ( $pk[0] ) {
209 hushed_git( "mv", "-f", $mfa[0], $pk[0] );
210 hushed_git( "commit", "-m", "sskm: confirm-add (replace) $pk[0] ($fp)" ) and die "git commit failed\n";
211 } else {
212 hushed_git( "mv", "-f", $mfa[0], "$gl_user$keyid.pub" );
213 hushed_git( "commit", "-m", "sskm: confirm-add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
215 system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
218 sub kf_undo_add {
219 # XXX some code at start is shared with kf_confirm_add
220 my ( $gl_user, $keyid ) = @_;
221 my @mfa = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add );
223 cd_temp_clone();
224 chdir("keydir");
226 my $fp = fingerprint( $mfa[0] );
227 hushed_git( "rm", $mfa[0] );
228 hushed_git( "commit", "-m", "sskm: undo-add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
229 system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
232 sub kf_del {
233 my ( $gl_user, $keyid ) = @_;
235 cd_temp_clone();
236 chdir("keydir");
238 mkdir("zzz-marked");
239 my @pk = highlander( $keyid, 1, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys );
241 my $fp = fingerprint( $pk[0] );
242 hushed_git( "mv", $pk[0], "zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub" ) and die "git mv failed\n";
243 hushed_git( "commit", "-m", "sskm: del $pk[0] ($fp)" ) and die "git commit failed\n";
244 system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
247 sub kf_confirm_del {
248 my ( $gl_user, $keyid ) = @_;
249 my @mfd = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del );
251 cd_temp_clone();
252 chdir("keydir");
254 my $fp = fingerprint( $mfd[0] );
255 hushed_git( "rm", $mfd[0] );
256 hushed_git( "commit", "-m", "sskm: confirm-del $gl_user$keyid ($fp)" ) and die "git commit failed\n";
257 system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
260 sub kf_undo_del {
261 my ( $gl_user, $keyid ) = @_;
263 my @mfd = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del );
265 print STDERR "
266 You're undeleting a key that is currently marked for deletion.
267 Hit ENTER to undelete this key
268 Hit Ctrl-C to cancel the undelete
269 Please see documentation for caveats on the undelete process as well as how to
270 actually delete it.
272 <>; # yeay... always wanted to do that -- throw away user input!
274 cd_temp_clone();
275 chdir("keydir");
277 my $fp = fingerprint( $mfd[0] );
278 hushed_git( "mv", "-f", $mfd[0], "$gl_user$keyid.pub" );
279 hushed_git( "commit", "-m", "sskm: undo-del $gl_user$keyid ($fp)" ) and die "git commit failed\n";
280 system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";