diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-06-23 12:58:35 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-06-24 15:03:18 +0300 |
commit | 441249c985e2295963b0495e29f91e5815a6f58a (patch) | |
tree | d0752cf9eff1595e0a8a646487ce413eb90b9db2 | |
parent | 850a3e03dfc227f058647ff65092a3c791012df8 (diff) | |
download | gsc-441249c985e2295963b0495e29f91e5815a6f58a.tar.gz gsc-441249c985e2295963b0495e29f91e5815a6f58a.tar.bz2 |
-rwxr-xr-x | git/gitaclhook | 371 |
1 files changed, 0 insertions, 371 deletions
diff --git a/git/gitaclhook b/git/gitaclhook deleted file mode 100755 index dd31dd8..0000000 --- a/git/gitaclhook +++ /dev/null @@ -1,371 +0,0 @@ -#! /usr/bin/perl -# Copyright (C) 2013 Sergey Poznyakoff <gray@gnu.org> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -use strict; -use File::Spec; -use Pod::Man; -use Pod::Usage; - -=head1 NAME - -gitaclhook - control access to git repositories - -=head1 SYNOPSIS - -B<gitaclhook> I<refname> I<old-sha1> I<new-sha1> - -B<gitaclhook --help> - -=head1 DESCRIPTION - -This program is intended to be run as an "update" hook by git. -It is called by B<git-receive-pack> with arguments: -I<refname> I<old-sha1> I<new-sha1>. - -If the B<hooks.aclfile> keyword is defined in the repository's config file, -this hook will parse the file and allow or deny update depending on -its settings. If B<hooks.aclfile> is not defined, update is allowed -unconditionally. - -=head1 ACL FILE - -The ACL file has the usual line-oriented syntax. Comments are introduced -by the # sign and extend to the end of the physical line. Comments and -empty lines are ignored. - -Non-empty lines introduce ACL rules. The syntax is: - -=over 4 - -I<VERB> I<PROJECT> I<USER> [I<OP> I<REF>] - -=back - -where brackets denote optional parts. The parts of an ACL are: - -=over 4 - -=item I<VERB> - -Either B<allow> or B<deny>, to allow or deny the operation, correspondingly. - -=item I<PROJECT> - -The name of the project. It is obtained by removing the directory -and suffix parts of the repository pathname. Thus, if the repository -is located in B</var/gitroot/foobar.git>, then the corresponding name of -the project is B<foobar>. - -An asterisk matches any project name. - -=item I<USER> - -Name of the user. The word B<all> stands for any user, the word B<none> -matches no one at all. Otherwise, if this part begins with a percent -sign (B<%>), the rest of characters are treated as the name of the UNIX -group to check and the rule matches any user in that group. Otherwise, -the literal match is assumed. - -=back - -The optional parts are: - -=over 4 - -=item I<OP> - -Requested operation codes. It is a string consisting of one or more -of the following letters (case-insensitive): - -=over 8 - -=item B<C> - -Create new ref. - -=item B<D> - -Delete existing ref. - -=item B<U> - -Fast-forward existing ref (no commit loss). - -=item B<R> - -Rewind or rebase existing ref (commit loss). - -=back - -=item I<REF> - -Affected ref, relative to the git B<refs/> directory. If it begins with -a caret (B<^>), it is treated as a Perl regular expression (with the B<^> -being its part). If it ends with a B</>, it is treated as a prefix match, -so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below. -Otherwise, it must match exactly the affected ref. - -=back - -=head1 RULE MATCHING - -The rule applies only if its I<PROJECT> and I<USER> parts match the project -which is being updated and the user who requests the update, its I<OP> -contains the opcode of the requested operation and I<REF> matches the affected -ref. Missing I<REF> and/or I<OP> are treated as a match. - -If no rule applies, the operation is allowed. - -For example, assume you have the following ACL file: - - allow myprog %devel U heads/master - allow myprog %pm CDUR heads/ - allow myprog %pm C ^heads/tags/v\\d+$ - allow myprog admin CDUR - deny myprog all - -Then the users from the B<devel> group will be able to push updates to -B<refs/heads/master>, the users from the B<pm> group will be allowed to do -anything with refs under B<refs/heads> and to create tags with names beginning -with B<v> and containing only digits afterwards, and the user B<admin> will -be allowed to do anything he pleases. No other users will be allowed to -update that repository. - -=head1 CONFIGURATION SETTINGS - -=over 4 - -=item B<hooks.aclfile> STRING - -Name of the ACL file. - -=item B<hooks.acllog> STRING - -Send log info to this file. - -=item B<hooks.acldebug> BOOL - -Enable debugging. - -=item B<hooks.aclquiet> BOOL - -Suppress diagnostics on stderr. - -=item B<hooks.httpd-user> STRING - -Name of the user httpd runs as. Define it if the repository can be -accessed via HTTP(S). If B<gitaclhook> is run as this user, it will -get the name of the user on behalf of which the update is performed -from the environment variable B<REMOTE_USER>. - -=back - -=head1 SEE ALSO - -B<git-receive-pack>(1). - -=head1 AUTHOR - -Sergey Poznyakoff, <gray@gno.org> - -=cut - -my $debug = $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 $project_name; -my $op; - -my %opstr = ('C' => 'create', - 'D' => 'delete', - 'U' => 'update', - 'R' => 'rewind/rebase'); - -sub logmsg($$;$) { - return 0 unless $logfile; - - my $status = shift; - my $message = shift; - my $loc = shift; - my $fd; - - open($fd, $logfile); - if ($loc) { - print $fd "$status:$loc: $message\n"; - } else { - print $fd "$status: $message\n"; - } - close($fd); -} - -sub deny($;$) { - my $msg = shift; - 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); - 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; - exit 0; -} - -sub info($) { - logmsg("INFO", $_[0]); - print STDERR "info: $_[0]\n" if $debug; -} - -sub git_value(@) { - my $fd; - - open($fd,'-|','git',@_); - local $_ = <$fd>; - chop; - close($fd); - return $_; -} - -sub match_user($) { - my $user = shift; - return 1 if ($user eq 'all'); - return 0 if ($user eq 'none'); - if ($user =~ /^%(.+)/) { - my ($name,$passwd,$gid,$members) = getgrnam($1) or return 0; - my @a = split(/\s+/,$members); - for (my $i = 0; $i <= $#a; $i++) { - return 1 if $a[$i] eq $user_name; - } - } elsif ($user eq $user_name) { - return 1; - } - return 0; -} - -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; - my $fd; - my $line = 0; - my @ret; - - my $filename = git_value('config', 'hooks.aclfile'); - allow("no ACL configured for $project") - 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"); - } - close($fd); - allow("default rule"); -} - -#### - -# Sanity checks -unless ($git_dir) { - pod2usage(-exitstatus => 0, -verbose => 2) if ($ref eq "--help"); - 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"; -} -$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 defined($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 = File::Spec->rel2abs($git_dir); -$project_name =~ m,/([^/]+)(?:\.git|/\.git)$,; -$project_name = $1; - -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); - -# Finis - - - - - - |