2011-06-01 08:46:19 +02:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
|
|
|
|
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
|
|
|
|
|
|
|
|
# pull in modules we need
|
|
|
|
unshift @INC, $ENV{GL_BINDIR};
|
|
|
|
require gitolite_rc or die "parse gitolite_rc.pm failed\n";
|
|
|
|
gitolite_rc->import;
|
|
|
|
require gitolite or die "parse gitolite.pm failed\n";
|
|
|
|
gitolite->import;
|
|
|
|
|
|
|
|
# get to the keydir
|
|
|
|
die "keydir not accessible\n" unless -d $gitolite_rc::GL_KEYDIR;
|
|
|
|
chdir($gitolite_rc::GL_KEYDIR);
|
|
|
|
|
|
|
|
# save arguments for later
|
|
|
|
my $operation = shift || 'list';
|
|
|
|
my $keyid = shift || '';
|
|
|
|
# keyid must fit a very specific pattern
|
|
|
|
$keyid and $keyid !~ /^@[-0-9a-z_]+$/i and die "invalid keyid $keyid\n";
|
|
|
|
|
|
|
|
# get the actual userid and keytype
|
|
|
|
my $gl_user = $ENV{GL_USER};
|
|
|
|
my $keytype = '';
|
|
|
|
$keytype = $1 if $gl_user =~ s/^zzz-marked-for-(...)-//;
|
|
|
|
print STDERR "hello $gl_user, you are currently using " .
|
|
|
|
($keytype ? "a key in the 'marked for $keytype' state\n"
|
|
|
|
: "a normal (\"active\") key\n" );
|
|
|
|
|
|
|
|
# ----
|
|
|
|
# first collect the keys
|
|
|
|
|
|
|
|
my (@pubkeys, @marked_for_add, @marked_for_del);
|
|
|
|
# get the list of pubkey files for this user, including pubkeys marked for
|
|
|
|
# add/delete
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
next unless $user eq $gl_user or $user =~ /^zzz-marked-for-...-$gl_user/;
|
|
|
|
|
|
|
|
if ($user =~ m(^zzz-marked-for-add-)) {
|
|
|
|
push @marked_for_add, $pubkey;
|
|
|
|
} elsif ($user =~ m(^zzz-marked-for-del-)) {
|
|
|
|
push @marked_for_del, $pubkey;
|
|
|
|
} else {
|
|
|
|
push @pubkeys, $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-...-)//g;
|
|
|
|
print $count++ . ": $fp : $_\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($operation eq 'list') {
|
|
|
|
print "you have the following keys:\n";
|
|
|
|
print_keylist("active keys", @pubkeys);
|
|
|
|
print_keylist("keys marked for addition/replacement", @marked_for_add);
|
|
|
|
print_keylist("keys marked for deletion", @marked_for_del);
|
|
|
|
print "\n\n";
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
# ----
|
|
|
|
# please see docs for details on how a user interacts with this
|
|
|
|
|
|
|
|
if ($keytype eq '') {
|
|
|
|
# user logging in with a normal key
|
|
|
|
die "valid operations: add, del, undo-add, confirm-del\n" unless $operation =~ /^(add|del|confirm-del|undo-add)$/;
|
|
|
|
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, $keyid, safe_stdin());
|
|
|
|
} elsif ($operation eq 'del') {
|
|
|
|
kf_del($gl_user, $keyid);
|
|
|
|
} elsif ($operation eq 'confirm-del') {
|
|
|
|
die "you dont have any keys marked for deletion\n" unless @marked_for_del;
|
|
|
|
kf_confirm_del($gl_user, $keyid);
|
|
|
|
} elsif ($operation eq 'undo-add') {
|
|
|
|
die "you dont have any keys marked for addition\n" unless @marked_for_add;
|
|
|
|
kf_undo_add($gl_user, $keyid);
|
|
|
|
}
|
|
|
|
} elsif ($keytype eq 'del') {
|
|
|
|
# user is using a key that was marked for deletion. The only possible use
|
|
|
|
# for this is that she changed her mind for some reason (maybe she marked
|
|
|
|
# the wrong key for deletion) or is not able to get her client-side sshd
|
|
|
|
# to stop using this key
|
|
|
|
die "valid operations: undo-del\n" unless $operation eq 'undo-del';
|
|
|
|
|
|
|
|
# reinstate the key
|
|
|
|
kf_undo_del($gl_user, $keyid);
|
|
|
|
} elsif ($keytype eq 'add') {
|
|
|
|
die "valid operations: confirm-add\n" unless $operation eq 'confirm-add';
|
|
|
|
# user is trying to validate a key that has been previously marked for
|
|
|
|
# addition. This isn't interactive, but it *could* be... if someone asked
|
|
|
|
kf_confirm_add($gl_user, $keyid);
|
|
|
|
}
|
|
|
|
|
|
|
|
exit;
|
|
|
|
|
|
|
|
# ----
|
|
|
|
|
|
|
|
# make a temp clone and switch to it
|
|
|
|
our $TEMPDIR;
|
|
|
|
BEGIN { $TEMPDIR=`mktemp -d -t tmp.XXXXXXXXXX`; }
|
|
|
|
END { `/bin/rm -rf $TEMPDIR`; }
|
|
|
|
sub cd_temp_clone {
|
|
|
|
chomp($TEMPDIR);
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("clone", "$ENV{GL_REPO_BASE_ABS}/gitolite-admin.git", "$TEMPDIR");
|
2011-06-01 08:46:19 +02:00
|
|
|
chdir($TEMPDIR);
|
2011-05-27 03:26:38 +02:00
|
|
|
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");
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub fingerprint {
|
|
|
|
my $fp = `ssh-keygen -l -f $_[0]`;
|
|
|
|
die "does not seem to be a valid pubkey\n" unless $fp =~ /(([0-9a-f]+:)+[0-9a-f]+ )/i;
|
|
|
|
return $1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-05-27 03:26:38 +02:00
|
|
|
sub hushed_git {
|
|
|
|
local(*STDOUT) = \*STDOUT;
|
|
|
|
local(*STDERR) = \*STDERR;
|
|
|
|
open(STDOUT, ">", "/dev/null");
|
|
|
|
open(STDERR, ">", "/dev/null");
|
|
|
|
system("git", @_);
|
|
|
|
}
|
|
|
|
|
2011-06-01 08:46:19 +02:00
|
|
|
sub highlander {
|
|
|
|
# there can be only one
|
2011-05-26 19:26:42 +02:00
|
|
|
my($keyid, $die_if_empty, @a) = @_;
|
2011-06-01 08:46:19 +02:00
|
|
|
# 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?
|
2011-05-26 19:26:42 +02:00
|
|
|
die "no keys with " . ($keyid || "empty") . " keyid found\n" if $die_if_empty and not @a;
|
2011-06-01 08:46:19 +02:00
|
|
|
|
|
|
|
return @a;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub kf_add {
|
|
|
|
my($gl_user, $keyid, $keymaterial) = @_;
|
|
|
|
|
|
|
|
# add a new "marked for addition" key for $gl_user.
|
|
|
|
cd_temp_clone();
|
|
|
|
chdir("keydir");
|
|
|
|
|
|
|
|
mkdir("zzz-marked");
|
|
|
|
wrap_print("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub", $keymaterial);
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("add", ".") and die "git add failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
my $fp = fingerprint("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub");
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("commit", "-m", "sskm: add $gl_user$keyid ($fp)") and die "git commit failed\n";
|
|
|
|
system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub kf_confirm_add {
|
|
|
|
my($gl_user, $keyid) = @_;
|
|
|
|
# find entries in both @pubkeys and @marked_for_add whose basename matches $gl_user$keyid
|
2011-05-26 19:26:42 +02:00
|
|
|
my @pk = highlander($keyid, 0, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys);
|
|
|
|
my @mfa = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add);
|
2011-06-01 08:46:19 +02:00
|
|
|
|
|
|
|
cd_temp_clone();
|
|
|
|
chdir("keydir");
|
|
|
|
|
|
|
|
my $fp = fingerprint($mfa[0]);
|
|
|
|
if ($pk[0]) {
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("mv", "-f", $mfa[0], $pk[0]);
|
|
|
|
hushed_git("commit", "-m", "sskm: confirm-add (replace) $pk[0] ($fp)") and die "git commit failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
} else {
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("mv", "-f", $mfa[0], "$gl_user$keyid.pub");
|
|
|
|
hushed_git("commit", "-m", "sskm: confirm-add $gl_user$keyid ($fp)") and die "git commit failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
2011-05-27 03:26:38 +02:00
|
|
|
system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub kf_undo_add {
|
|
|
|
# XXX some code at start is shared with kf_confirm_add
|
|
|
|
my($gl_user, $keyid) = @_;
|
2011-05-26 19:26:42 +02:00
|
|
|
my @mfa = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add);
|
2011-06-01 08:46:19 +02:00
|
|
|
|
|
|
|
cd_temp_clone();
|
|
|
|
chdir("keydir");
|
|
|
|
|
|
|
|
my $fp = fingerprint($mfa[0]);
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("rm", $mfa[0]);
|
|
|
|
hushed_git("commit", "-m", "sskm: undo-add $gl_user$keyid ($fp)") and die "git commit failed\n";
|
|
|
|
system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub kf_del {
|
|
|
|
my($gl_user, $keyid) = @_;
|
|
|
|
|
|
|
|
cd_temp_clone();
|
|
|
|
chdir("keydir");
|
|
|
|
|
|
|
|
mkdir("zzz-marked");
|
2011-05-26 19:26:42 +02:00
|
|
|
my @pk = highlander($keyid, 1, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys);
|
2011-06-01 08:46:19 +02:00
|
|
|
|
|
|
|
my $fp = fingerprint($pk[0]);
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("mv", $pk[0], "zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub") and die "git mv failed\n";
|
|
|
|
hushed_git("commit", "-m", "sskm: del $pk[0] ($fp)") and die "git commit failed\n";
|
|
|
|
system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub kf_confirm_del {
|
|
|
|
my($gl_user, $keyid) = @_;
|
2011-05-26 19:26:42 +02:00
|
|
|
my @mfd = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del);
|
2011-06-01 08:46:19 +02:00
|
|
|
|
|
|
|
cd_temp_clone();
|
|
|
|
chdir("keydir");
|
|
|
|
|
|
|
|
my $fp = fingerprint($mfd[0]);
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("rm", $mfd[0]);
|
|
|
|
hushed_git("commit", "-m", "sskm: confirm-del $gl_user$keyid ($fp)") and die "git commit failed\n";
|
|
|
|
system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub kf_undo_del {
|
|
|
|
my ($gl_user, $keyid) = @_;
|
|
|
|
|
2011-05-26 19:26:42 +02:00
|
|
|
my @mfd = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del);
|
2011-06-01 08:46:19 +02:00
|
|
|
|
|
|
|
print STDERR "
|
|
|
|
You're undeleting a key that is currently marked for deletion.
|
|
|
|
Hit ENTER to undelete this key
|
|
|
|
Hit Ctrl-C to cancel the undelete
|
|
|
|
Please see documentation for caveats on the undelete process as well as how to
|
|
|
|
actually delete it.
|
|
|
|
";
|
|
|
|
<>; # yeay... always wanted to do that -- throw away user input!
|
|
|
|
|
|
|
|
cd_temp_clone();
|
|
|
|
chdir("keydir");
|
|
|
|
|
|
|
|
my $fp = fingerprint($mfd[0]);
|
2011-05-27 03:26:38 +02:00
|
|
|
hushed_git("mv", "-f", $mfd[0], "$gl_user$keyid.pub" );
|
|
|
|
hushed_git("commit", "-m", "sskm: undo-del $gl_user$keyid ($fp)") and die "git commit failed\n";
|
|
|
|
system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n";
|
2011-06-01 08:46:19 +02:00
|
|
|
}
|