aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2013-04-11 12:07:26 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2013-04-11 12:07:26 +0300
commit5b089678b03537f27fac73a2a08136ff9b1cbf66 (patch)
tree393ed2b4720f59c4d578156d0b73097a7d4d8f35
parent4bf0a7b054c17e39acdd0818b203c5a2eb723adc (diff)
downloadgsc-5b089678b03537f27fac73a2a08136ff9b1cbf66.tar.gz
gsc-5b089678b03537f27fac73a2a08136ff9b1cbf66.tar.bz2
gitaclhook: an update hook for git implementing ACLs.
* git/gitaclhook: New file. * upload/gnupload: Bugfix.
-rwxr-xr-xgit/gitaclhook276
-rwxr-xr-xupload/gnupload3
2 files changed, 278 insertions, 1 deletions
diff --git a/git/gitaclhook b/git/gitaclhook
new file mode 100755
index 0000000..f9b3d3a
--- /dev/null
+++ b/git/gitaclhook
@@ -0,0 +1,276 @@
+#! /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;
+
+=doc
+This hook is intended to be run as an "update" hook by git.
+It is called by git-receive-pack with arguments: refname old-sha1 new-sha1.
+
+If the 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 hooks.aclfile is not defined, update is allowed
+unconditionally.
+
+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:
+
+ VERB PROJECT USER [OP REF]
+
+where brackets denote optional parts. The parts of an ACL are:
+
+VERB Either 'allow' or 'deny', to allow or deny the operation,
+ correspondingly.
+
+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 /var/gitroot/foobar.git, then the corresponding name of
+ the project is 'foobar'.
+
+USER Name of the user. The word 'all' stands for any user, the word 'none'
+ matches no one at all. Otherwise, if this part begins with a percent
+ sign (%), the rest of characters aretreated as the name of the UNIX
+ group to check and the rule matches any user in that group. Otherwise,
+ the literal match is assumed.
+
+The optional parts are:
+
+OP Requested operation codes. It is a string consisting of one or more
+ of the following letters (case-insensitive):
+
+ C: create new ref
+ D: delete existing ref
+ U: fast-forward existing ref (no commit loss)
+ R: rewind or rebase existing ref (commit loss)
+
+REF Affected ref, relative to the git refs/ directory. If it begins with
+ a caret (^), it is treated as a Perl regular expression (with the ^
+ being its part). If it ends with a /, it is treated as a prefix match,
+ so, e.g., "heads/baz/" matches "refs/heads/baz" and anything below.
+ Otherwise, it must match exactly the affected ref.
+
+The rule applies only if its PROJECT and USER parts match the project which is
+being updated and the user who requests the update, its OP contains the opcode
+of the requested operation and REF matches the affected ref. Missing REF
+and/or 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 'devel' group will be able to push updates to
+refs/heads/master, the users from the 'pm' group will be allowed to do
+anything with refs under refs/heads and to create tags with names beginning
+with 'v' and containing only digits afterwards, and the user 'admin' will
+be allowed to do anything he pleases. No other users will be allowed to
+update that repository.
+
+Configuration settings:
+
+hooks.aclfile STRING Name of the ACL file
+hooks.acllog STRING Send log info to this file
+hooks.acldebug BOOL Enable debugging
+hooks.aclquiet BOOL Suppress diagnostics on stderr
+
+=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 $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
+deny "don't run this script from the command line" unless ($git_dir);
+
+$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);
+
+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
+
+
+
+
+
+
diff --git a/upload/gnupload b/upload/gnupload
index 3ec9761..8863771 100755
--- a/upload/gnupload
+++ b/upload/gnupload
@@ -344,12 +344,13 @@ upload() {
done | $dbg sftp -b - download.gnu.org.ua:/incoming/${destdir%%/*}
;;
*@download.gnu.org.ua:alpha/*|*@download.gnu.org.ua:ftp/*|*@download.gnu.org.ua:test/*)
+ user=${dest%%@*}
mkdirective "${destdir#*/}" "$base" "$file" "$stmt"
echo "$passphrase" | $GPG --passphrase-fd 0 --clearsign $base.directive
for f in $files $base.directive.asc
do
echo put $f
- done | $dbg sftp -b - download.gnu.org.ua:/incoming/${destdir%%/*}
+ done | $dbg sftp -b - ${user}@download.gnu.org.ua:/incoming/${destdir%%/*}
;;
/*)
mkdirective "$destdir" "$base" "$file" "$stmt"

Return to:

Send suggestions and report system problems to the System administrator.