summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org.ua>2013-04-11 09:07:26 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2013-04-11 09:07:26 (GMT)
commit5b089678b03537f27fac73a2a08136ff9b1cbf66 (patch) (unidiff)
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.
Diffstat (more/less context) (ignore whitespace changes)
-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
--- a/dev/null
+++ b/git/gitaclhook
@@ -0,0 +1,276 @@
1#! /usr/bin/perl
2# Copyright (C) 2013 Sergey Poznyakoff <gray@gnu.org>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3, or (at your option)
7# any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17use strict;
18use File::Spec;
19
20=doc
21This hook is intended to be run as an "update" hook by git.
22It is called by git-receive-pack with arguments: refname old-sha1 new-sha1.
23
24If the hooks.aclfile keyword is defined in the repository's config file,
25this hook will parse the file and allow or deny update depending on
26its settings. If hooks.aclfile is not defined, update is allowed
27unconditionally.
28
29The ACL file has the usual line-oriented syntax. Comments are introduced
30by the # sign and extend to the end of the physical line. Comments and
31empty lines are ignored.
32
33Non-empty lines introduce ACL rules. The syntax is:
34
35 VERB PROJECT USER [OP REF]
36
37where brackets denote optional parts. The parts of an ACL are:
38
39VERB Either 'allow' or 'deny', to allow or deny the operation,
40 correspondingly.
41
42PROJECT The name of the project. It is obtained by removing the directory
43 and suffix parts of the repository pathname. Thus, if the repository
44 is located in /var/gitroot/foobar.git, then the corresponding name of
45 the project is 'foobar'.
46
47USER Name of the user. The word 'all' stands for any user, the word 'none'
48 matches no one at all. Otherwise, if this part begins with a percent
49 sign (%), the rest of characters aretreated as the name of the UNIX
50 group to check and the rule matches any user in that group. Otherwise,
51 the literal match is assumed.
52
53The optional parts are:
54
55OP Requested operation codes. It is a string consisting of one or more
56 of the following letters (case-insensitive):
57
58 C: create new ref
59 D: delete existing ref
60 U: fast-forward existing ref (no commit loss)
61 R: rewind or rebase existing ref (commit loss)
62
63REF Affected ref, relative to the git refs/ directory. If it begins with
64 a caret (^), it is treated as a Perl regular expression (with the ^
65 being its part). If it ends with a /, it is treated as a prefix match,
66 so, e.g., "heads/baz/" matches "refs/heads/baz" and anything below.
67 Otherwise, it must match exactly the affected ref.
68
69The rule applies only if its PROJECT and USER parts match the project which is
70being updated and the user who requests the update, its OP contains the opcode
71of the requested operation and REF matches the affected ref. Missing REF
72and/or OP are treated as a match.
73
74If no rule applies, the operation is allowed.
75
76For example, assume you have the following ACL file:
77
78allow myprog %devel U heads/master
79allow myprog %pm CDUR heads/
80allow myprog %pm C ^heads/tags/v\\d+$
81allow myprog admin CDUR
82deny myprog all
83
84Then the users from the 'devel' group will be able to push updates to
85refs/heads/master, the users from the 'pm' group will be allowed to do
86anything with refs under refs/heads and to create tags with names beginning
87with 'v' and containing only digits afterwards, and the user 'admin' will
88be allowed to do anything he pleases. No other users will be allowed to
89update that repository.
90
91Configuration settings:
92
93hooks.aclfile STRING Name of the ACL file
94hooks.acllog STRING Send log info to this file
95hooks.acldebug BOOL Enable debugging
96hooks.aclquiet BOOL Suppress diagnostics on stderr
97
98=cut
99
100my $debug = $ENV{GIT_UPDATE_DEBUG} > 0;
101my $logfile;
102my $quiet;
103my ($user_name) = getpwuid $<;
104my $git_dir = $ENV{GIT_DIR};
105my $ref = $ARGV[0];
106my $old = $ARGV[1];
107my $new = $ARGV[2];
108
109my $project_name;
110my $op;
111
112my %opstr = ('C' => 'create',
113 'D' => 'delete',
114 'U' => 'update',
115 'R' => 'rewind/rebase');
116
117sub logmsg($$;$) {
118 return 0 unless $logfile;
119
120 my $status = shift;
121 my $message = shift;
122 my $loc = shift;
123 my $fd;
124
125 open($fd, $logfile);
126 if ($loc) {
127 print $fd "$status:$loc: $message\n";
128 } else {
129 print $fd "$status: $message\n";
130 }
131 close($fd);
132}
133
134sub deny ($;$) {
135 my $msg = shift;
136 my $loc = shift;
137
138 logmsg("DENY",
139 "$project_name:$user_name:$opstr{$op}:$ref:$old:$new: $msg",
140 $loc);
141
142 print STDERR "debug: denied by $loc\n" if ($debug and $loc);
143 print STDERR "denied: $msg\n" unless $quiet;
144 exit 1;
145}
146
147sub allow ($) {
148 logmsg("ALLOW",
149 "$project_name:$user_name:$opstr{$op}:$ref:$old:$new",
150 $_[0]);
151 print STDERR "debug: allow $_[0]\n" if $debug;
152 exit 0;
153}
154
155sub info ($) {
156 logmsg("INFO", $_[0]);
157 print STDERR "info: $_[0]\n" if $debug;
158}
159
160sub git_value (@) {
161 my $fd;
162
163 open($fd,'-|','git',@_);
164 local $_ = <$fd>;
165 chop;
166 close($fd);
167 return $_;
168}
169
170sub match_user($) {
171 my $user = shift;
172 return 1 if ($user eq 'all');
173 return 0 if ($user eq 'none');
174 if ($user =~ /^%(.+)/) {
175 my ($name,$passwd,$gid,$members) = getgrnam($1) or return 0;
176 my @a = split(/\s+/,$members);
177 for (my $i = 0; $i <= $#a; $i++) {
178 return 1 if $a[$i] eq $user_name;
179 }
180 } elsif ($user eq $user_name) {
181 return 1;
182 }
183 return 0;
184}
185
186sub match_ref($) {
187 my $expr = shift;
188 return ($ref =~ /$expr/) if ($expr =~ /^\^/);
189 return ("$ref/" eq $expr or index($ref, $expr) == 0) if ($expr =~ /\/$/);
190 return $ref eq $expr;
191}
192
193sub check_acl($$$) {
194 my $project = shift;
195 my $op = shift;
196 my $ref = shift;
197 my $fd;
198 my $line = 0;
199 my @ret;
200
201 my $filename = git_value('config', 'hooks.aclfile');
202 allow("no ACL configured for $project")
203 unless defined($filename);
204
205 open($fd, "<", $filename) or deny("cannot open configuration file: $!");
206 while (<$fd>) {
207 ++$line;
208 chomp;
209 s/^\s+//;
210 s/\s+$//;
211 s/#.*//;
212 next if ($_ eq "");
213 my @x = split(/\s+/, $_, 5);
214
215 deny("unknown keyword", "$filename:$line")
216 unless ($x[0] eq 'allow' || $x[0] eq 'deny');
217 deny("malformed line", "$filename:$line")
218 unless $#x >= 2;
219
220 next if ($x[1] ne $project);
221 next unless match_user($x[2]);
222 next if ($#x >= 3 && index(uc $x[3], $op) == -1);
223 next if ($#x == 4 && !match_ref($x[4]));
224
225 allow("$filename:$line") if ($x[0] eq 'allow');
226 deny("you are not permitted to " . $opstr{$op} . " $ref",
227 "$filename:$line");
228 }
229 close($fd);
230 allow("default rule");
231}
232
233####
234
235# Sanity checks
236deny "don't run this script from the command line" unless ($git_dir);
237
238$debug = git_value('config', '--bool', 'hooks.acldebug') unless ($debug);
239$logfile = git_value('config', 'hooks.acllog');
240if ($logfile && $logfile !~ /[>|]/) {
241 $logfile = ">>$logfile";
242}
243$quiet = git_value('config', 'hooks.aclquiet') unless ($debug);
244
245deny "need a ref name" unless $ref;
246deny "bogus ref $ref" unless $ref =~ s,^refs/,,;
247deny "bad old value $old" unless $old =~ /^[a-z0-9]{40}$/;
248deny "bad new value $new" unless $new =~ /^[a-z0-9]{40}$/;
249deny "no such user" unless $user_name;
250allow "no change requested" if $old eq $new;
251
252$project_name = File::Spec->rel2abs($git_dir);
253$project_name =~ m,/([^/]+)(?:\.git|/\.git)$,;
254$project_name = $1;
255
256if ($old =~ /^0{40}$/) {
257 $op = 'C';
258} elsif ($new =~ /^0{40}$/) {
259 $op = 'D';
260} elsif ($ref =~ m,^heads/, && $old eq git_value('merge-base',$old,$new)) {
261 $op = 'U';
262} else {
263 $op = 'R';
264}
265
266info "$user_name requested $opstr{$op} on $ref in $project_name";
267
268check_acl($project_name, $op, $ref);
269
270# Finis
271
272
273
274
275
276
diff --git a/upload/gnupload b/upload/gnupload
index 3ec9761..8863771 100755
--- a/upload/gnupload
+++ b/upload/gnupload
@@ -344,12 +344,13 @@ upload() {
344 done | $dbg sftp -b - download.gnu.org.ua:/incoming/${destdir%%/*} 344 done | $dbg sftp -b - download.gnu.org.ua:/incoming/${destdir%%/*}
345 ;; 345 ;;
346 *@download.gnu.org.ua:alpha/*|*@download.gnu.org.ua:ftp/*|*@download.gnu.org.ua:test/*) 346 *@download.gnu.org.ua:alpha/*|*@download.gnu.org.ua:ftp/*|*@download.gnu.org.ua:test/*)
347 user=${dest%%@*}
347 mkdirective "${destdir#*/}" "$base" "$file" "$stmt" 348 mkdirective "${destdir#*/}" "$base" "$file" "$stmt"
348 echo "$passphrase" | $GPG --passphrase-fd 0 --clearsign $base.directive 349 echo "$passphrase" | $GPG --passphrase-fd 0 --clearsign $base.directive
349 for f in $files $base.directive.asc 350 for f in $files $base.directive.asc
350 do 351 do
351 echo put $f 352 echo put $f
352 done | $dbg sftp -b - download.gnu.org.ua:/incoming/${destdir%%/*} 353 done | $dbg sftp -b - ${user}@download.gnu.org.ua:/incoming/${destdir%%/*}
353 ;; 354 ;;
354 /*) 355 /*)
355 mkdirective "$destdir" "$base" "$file" "$stmt" 356 mkdirective "$destdir" "$base" "$file" "$stmt"

Return to:

Send suggestions and report system problems to the System administrator.