diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-06-22 20:05:42 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-06-22 20:05:42 +0300 |
commit | 1e744ae787312413fb8600dd622b5fd7374b45ae (patch) | |
tree | 52c94147ac95fcf4e75265f56d15d318a9cb0ac2 /gitaclhook | |
parent | 9e6f5e5fe3c9422f5ab7182e88e8cfd05470c86c (diff) | |
download | gitaclhook-1e744ae787312413fb8600dd622b5fd7374b45ae.tar.gz gitaclhook-1e744ae787312413fb8600dd622b5fd7374b45ae.tar.bz2 |
gitaclhook: Improve CLI.
Diffstat (limited to 'gitaclhook')
-rwxr-xr-x | gitaclhook | 152 |
1 files changed, 96 insertions, 56 deletions
@@ -15,12 +15,13 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. use strict; use File::Spec; use Pod::Man; use Pod::Usage; +use Getopt::Long qw(:config gnu_getopt no_ignore_case); =head1 NAME gitaclhook - control access to git repositories =head1 SYNOPSIS @@ -231,29 +232,36 @@ B<git-receive-pack>(1). =head1 AUTHOR Sergey Poznyakoff, <gray@gno.org> =cut -my $debug = $ENV{GIT_UPDATE_DEBUG} > 0; +my $debug_level = $ENV{GIT_UPDATE_DEBUG} > 0; my $logfile; my $quiet; my ($user_name) = getpwuid $<; my $git_dir = $ENV{GIT_DIR}; -my $ref = $ARGV[0]; -my $old = $ARGV[1]; -my $new = $ARGV[2]; +my $ref; +my $old; +my $new; my $project_name; my $op; my %opstr = ('C' => 'create', 'D' => 'delete', 'U' => 'update', 'R' => 'rewind/rebase'); +sub debug($$) { + my ($level,$msg) = @_; + if ($level <= $debug_level) { + print STDERR "debug: $msg\n"; + } +} + sub logmsg($$;$) { return 0 unless $logfile; my $status = shift; my $message = shift; my $loc = shift; @@ -273,28 +281,28 @@ sub deny($;$) { my $loc = shift; logmsg("DENY", "$project_name:$user_name:$opstr{$op}:$ref:$old:$new: $msg", $loc); - print STDERR "debug: denied by $loc\n" if ($debug and $loc); + debug(1, "denied by $loc") if $loc; print STDERR "denied: $msg\n" unless $quiet; exit 1; } sub allow($) { logmsg("ALLOW", "$project_name:$user_name:$opstr{$op}:$ref:$old:$new", $_[0]); - print STDERR "debug: allow $_[0]\n" if $debug; + debug(1, "allow $_[0]"); exit 0; } sub info($) { logmsg("INFO", $_[0]); - print STDERR "info: $_[0]\n" if $debug; + print STDERR "info: $_[0]\n" if $debug_level; } sub project_name($) { my $dir = shift; File::Spec->rel2abs($dir) =~ m,/([^/]+)(?:\.git|/\.git)$,; @@ -331,105 +339,137 @@ sub match_ref($) { my $expr = shift; return ($ref =~ /$expr/) if ($expr =~ /^\^/); return ("$ref/" eq $expr or index($ref, $expr) == 0) if ($expr =~ /\/$/); return $ref eq $expr; } -sub check_acl($$$) { - my $project = shift; - my $op = shift; - my $ref = shift; +sub match_tuple($) { + my $tuple = shift; + my @x = @{$tuple}; + + return ( \&deny, "malformed line" ) unless $#x >= 2; + return ( \&deny, "unknown keyword" ) + unless ($x[0] eq 'allow' || $x[0] eq 'deny'); + + return ( 0, "project mismatch" ) + if ($x[1] ne "*" and $x[1] ne $project_name); + return ( 0, "user mismatch" ) + unless match_user($x[2]); + return ( 0, "op mismatch" ) + if ($#x >= 3 && index(uc $x[3], $op) == -1); + return ( 0, "ref mismatch" ) + if ($#x == 4 && !match_ref($x[4])); + if ($x[0] eq 'allow') { + return ( \&allow ); + } else { + my $s = "you are not permitted to " . $opstr{$op} . " $ref"; + return ( \&deny, $s ); + } +} + + +sub check_acl { my $fd; my $line = 0; my @ret; my $filename = git_value('config', 'hooks.aclfile'); - allow("no ACL configured for $project") + allow("no ACL configured for $project_name") unless defined($filename); open($fd, "<", $filename) or deny("cannot open configuration file: $!"); while (<$fd>) { ++$line; chomp; s/^\s+//; s/\s+$//; s/#.*//; next if ($_ eq ""); my @x = split(/\s+/, $_, 5); - deny("unknown keyword", "$filename:$line") - unless ($x[0] eq 'allow' || $x[0] eq 'deny'); - deny("malformed line", "$filename:$line") - unless $#x >= 2; - - next if ($x[1] ne "*" and $x[1] ne $project); - next unless match_user($x[2]); - next if ($#x >= 3 && index(uc $x[3], $op) == -1); - next if ($#x == 4 && !match_ref($x[4])); - - allow("$filename:$line") if ($x[0] eq 'allow'); - deny("you are not permitted to " . $opstr{$op} . " $ref", - "$filename:$line"); + my @res = match_tuple(\@x); + if ($res[0] == 0) { + debug(2, "$filename:$line: $res[1]"); + next; + } + close($fd); + if ($res[1]) { + $res[0]->($res[1], "$filename:$line"); + } else { + $res[0]->("$filename:$line"); + } + exit(127); } close($fd); allow("default rule"); } #### # Sanity checks -unless ($git_dir) { - pod2usage(-exitstatus => 0, -verbose => 2) if ($ref eq "--help"); - if ($ref eq "--test") { - deny("--test requires four arguments") unless ($#ARGV == 4); - $ENV{GIT_DIR} = $ARGV[1]; - $user_name = $ARGV[2]; - $op = $ARGV[3]; - deny("invalid op") unless defined($opstr{$op}); - $ref = $ARGV[4]; - check_acl(project_name($ARGV[1]), $op, $ref); - exit(0); +if ($git_dir) { + $ref = $ARGV[0]; + $old = $ARGV[1]; + $new = $ARGV[2]; + + deny "bad old value $old" unless $old =~ /^[a-z0-9]{40}$/; + deny "bad new value $new" unless $new =~ /^[a-z0-9]{40}$/; + + if ($old =~ /^0{40}$/) { + $op = 'C'; + } elsif ($new =~ /^0{40}$/) { + $op = 'D'; + } elsif ($ref =~ m,^heads/, && $old eq git_value('merge-base',$old,$new)) { + $op = 'U'; + } else { + $op = 'R'; } - deny "try \"$0 --help\" for fore info" -} -$debug = git_value('config', '--bool', 'hooks.acldebug') unless ($debug); -$logfile = git_value('config', 'hooks.acllog'); -if ($logfile && $logfile !~ /[>|]/) { - $logfile = ">>$logfile"; + $debug_level = git_value('config', '--bool', 'hooks.acldebug') + unless ($debug_level); + $logfile = git_value('config', 'hooks.acllog'); + if ($logfile && $logfile !~ /[>|]/) { + $logfile = ">>$logfile"; + } + $quiet = git_value('config', 'hooks.aclquiet') unless ($debug_level); +} else { + my $test; + GetOptions("help|h" => sub { pod2usage(-exitstatus => 0, -verbose => 2); }, + "debug|d+" => \$debug_level, + "test|t" => \$test) + or exit (3); + if ($test) { + abend("--test requires four arguments") unless ($#ARGV == 3); + $git_dir = $ENV{GIT_DIR} = $ARGV[0]; + $user_name = $ARGV[1]; + $op = $ARGV[2]; + deny("invalid op") unless defined($opstr{$op}); + $ref = $ARGV[3]; + $old = '0000000000000000000000000000000000000000'; + $new = '0000000000000000000000000000000000000001'; + } else { + deny "try \"$0 --help\" for fore info" + } } -$quiet = git_value('config', 'hooks.aclquiet') unless ($debug); my $httpdusr = git_value('config', 'hooks.httpd-user'); if (defined($httpdusr) and $user_name eq $httpdusr) { deny "need authenticated user" unless $ENV{AUTH_TYPE}; $user_name = $ENV{REMOTE_USER}; } deny "need a ref name" unless $ref; deny "bogus ref $ref" unless $ref =~ s,^refs/,,; -deny "bad old value $old" unless $old =~ /^[a-z0-9]{40}$/; -deny "bad new value $new" unless $new =~ /^[a-z0-9]{40}$/; deny "no such user" unless $user_name; allow "no change requested" if $old eq $new; $project_name = project_name($git_dir); -if ($old =~ /^0{40}$/) { - $op = 'C'; -} elsif ($new =~ /^0{40}$/) { - $op = 'D'; -} elsif ($ref =~ m,^heads/, && $old eq git_value('merge-base',$old,$new)) { - $op = 'U'; -} else { - $op = 'R'; -} - info "$user_name requested $opstr{$op} on $ref in $project_name"; -check_acl($project_name, $op, $ref); +&check_acl; # Finis |