summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rwxr-xr-xgit/gitaclhook371
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
+++ b/dev/null
@@ -1,371 +0,0 @@
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;
19use Pod::Man;
20use Pod::Usage;
21
22=head1 NAME
23
24gitaclhook - control access to git repositories
25
26=head1 SYNOPSIS
27
28B<gitaclhook> I<refname> I<old-sha1> I<new-sha1>
29
30B<gitaclhook --help>
31
32=head1 DESCRIPTION
33
34This program is intended to be run as an "update" hook by git.
35It is called by B<git-receive-pack> with arguments:
36I<refname> I<old-sha1> I<new-sha1>.
37
38If the B<hooks.aclfile> keyword is defined in the repository's config file,
39this hook will parse the file and allow or deny update depending on
40its settings. If B<hooks.aclfile> is not defined, update is allowed
41unconditionally.
42
43=head1 ACL FILE
44
45The ACL file has the usual line-oriented syntax. Comments are introduced
46by the # sign and extend to the end of the physical line. Comments and
47empty lines are ignored.
48
49Non-empty lines introduce ACL rules. The syntax is:
50
51=over 4
52
53I<VERB> I<PROJECT> I<USER> [I<OP> I<REF>]
54
55=back
56
57where brackets denote optional parts. The parts of an ACL are:
58
59=over 4
60
61=item I<VERB>
62
63Either B<allow> or B<deny>, to allow or deny the operation, correspondingly.
64
65=item I<PROJECT>
66
67The name of the project. It is obtained by removing the directory
68and suffix parts of the repository pathname. Thus, if the repository
69is located in B</var/gitroot/foobar.git>, then the corresponding name of
70the project is B<foobar>.
71
72An asterisk matches any project name.
73
74=item I<USER>
75
76Name of the user. The word B<all> stands for any user, the word B<none>
77matches no one at all. Otherwise, if this part begins with a percent
78sign (B<%>), the rest of characters are treated as the name of the UNIX
79group to check and the rule matches any user in that group. Otherwise,
80the literal match is assumed.
81
82=back
83
84The optional parts are:
85
86=over 4
87
88=item I<OP>
89
90Requested operation codes. It is a string consisting of one or more
91of the following letters (case-insensitive):
92
93=over 8
94
95=item B<C>
96
97Create new ref.
98
99=item B<D>
100
101Delete existing ref.
102
103=item B<U>
104
105Fast-forward existing ref (no commit loss).
106
107=item B<R>
108
109Rewind or rebase existing ref (commit loss).
110
111=back
112
113=item I<REF>
114
115Affected ref, relative to the git B<refs/> directory. If it begins with
116a caret (B<^>), it is treated as a Perl regular expression (with the B<^>
117being its part). If it ends with a B</>, it is treated as a prefix match,
118so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below.
119Otherwise, it must match exactly the affected ref.
120
121=back
122
123=head1 RULE MATCHING
124
125The rule applies only if its I<PROJECT> and I<USER> parts match the project
126which is being updated and the user who requests the update, its I<OP>
127contains the opcode of the requested operation and I<REF> matches the affected
128ref. Missing I<REF> and/or I<OP> are treated as a match.
129
130If no rule applies, the operation is allowed.
131
132For example, assume you have the following ACL file:
133
134 allow myprog %devel U heads/master
135 allow myprog %pm CDUR heads/
136 allow myprog %pm C ^heads/tags/v\\d+$
137 allow myprog admin CDUR
138 deny myprog all
139
140Then the users from the B<devel> group will be able to push updates to
141B<refs/heads/master>, the users from the B<pm> group will be allowed to do
142anything with refs under B<refs/heads> and to create tags with names beginning
143with B<v> and containing only digits afterwards, and the user B<admin> will
144be allowed to do anything he pleases. No other users will be allowed to
145update that repository.
146
147=head1 CONFIGURATION SETTINGS
148
149=over 4
150
151=item B<hooks.aclfile> STRING
152
153Name of the ACL file.
154
155=item B<hooks.acllog> STRING
156
157Send log info to this file.
158
159=item B<hooks.acldebug> BOOL
160
161Enable debugging.
162
163=item B<hooks.aclquiet> BOOL
164
165Suppress diagnostics on stderr.
166
167=item B<hooks.httpd-user> STRING
168
169Name of the user httpd runs as. Define it if the repository can be
170accessed via HTTP(S). If B<gitaclhook> is run as this user, it will
171get the name of the user on behalf of which the update is performed
172from the environment variable B<REMOTE_USER>.
173
174=back
175
176=head1 SEE ALSO
177
178B<git-receive-pack>(1).
179
180=head1 AUTHOR
181
182Sergey Poznyakoff, <gray@gno.org>
183
184=cut
185
186my $debug = $ENV{GIT_UPDATE_DEBUG} > 0;
187my $logfile;
188my $quiet;
189my ($user_name) = getpwuid $<;
190my $git_dir = $ENV{GIT_DIR};
191my $ref = $ARGV[0];
192my $old = $ARGV[1];
193my $new = $ARGV[2];
194
195my $project_name;
196my $op;
197
198my %opstr = ('C' => 'create',
199 'D' => 'delete',
200 'U' => 'update',
201 'R' => 'rewind/rebase');
202
203sub logmsg($$;$) {
204 return 0 unless $logfile;
205
206 my $status = shift;
207 my $message = shift;
208 my $loc = shift;
209 my $fd;
210
211 open($fd, $logfile);
212 if ($loc) {
213 print $fd "$status:$loc: $message\n";
214 } else {
215 print $fd "$status: $message\n";
216 }
217 close($fd);
218}
219
220sub deny($;$) {
221 my $msg = shift;
222 my $loc = shift;
223
224 logmsg("DENY",
225 "$project_name:$user_name:$opstr{$op}:$ref:$old:$new: $msg",
226 $loc);
227
228 print STDERR "debug: denied by $loc\n" if ($debug and $loc);
229 print STDERR "denied: $msg\n" unless $quiet;
230 exit 1;
231}
232
233sub allow($) {
234 logmsg("ALLOW",
235 "$project_name:$user_name:$opstr{$op}:$ref:$old:$new",
236 $_[0]);
237 print STDERR "debug: allow $_[0]\n" if $debug;
238 exit 0;
239}
240
241sub info($) {
242 logmsg("INFO", $_[0]);
243 print STDERR "info: $_[0]\n" if $debug;
244}
245
246sub git_value(@) {
247 my $fd;
248
249 open($fd,'-|','git',@_);
250 local $_ = <$fd>;
251 chop;
252 close($fd);
253 return $_;
254}
255
256sub match_user($) {
257 my $user = shift;
258 return 1 if ($user eq 'all');
259 return 0 if ($user eq 'none');
260 if ($user =~ /^%(.+)/) {
261 my ($name,$passwd,$gid,$members) = getgrnam($1) or return 0;
262 my @a = split(/\s+/,$members);
263 for (my $i = 0; $i <= $#a; $i++) {
264 return 1 if $a[$i] eq $user_name;
265 }
266 } elsif ($user eq $user_name) {
267 return 1;
268 }
269 return 0;
270}
271
272sub match_ref($) {
273 my $expr = shift;
274 return ($ref =~ /$expr/) if ($expr =~ /^\^/);
275 return ("$ref/" eq $expr or index($ref, $expr) == 0) if ($expr =~ /\/$/);
276 return $ref eq $expr;
277}
278
279sub check_acl($$$) {
280 my $project = shift;
281 my $op = shift;
282 my $ref = shift;
283 my $fd;
284 my $line = 0;
285 my @ret;
286
287 my $filename = git_value('config', 'hooks.aclfile');
288 allow("no ACL configured for $project")
289 unless defined($filename);
290
291 open($fd, "<", $filename) or deny("cannot open configuration file: $!");
292 while (<$fd>) {
293 ++$line;
294 chomp;
295 s/^\s+//;
296 s/\s+$//;
297 s/#.*//;
298 next if ($_ eq "");
299 my @x = split(/\s+/, $_, 5);
300
301 deny("unknown keyword", "$filename:$line")
302 unless ($x[0] eq 'allow' || $x[0] eq 'deny');
303 deny("malformed line", "$filename:$line")
304 unless $#x >= 2;
305
306 next if ($x[1] ne "*" and $x[1] ne $project);
307 next unless match_user($x[2]);
308 next if ($#x >= 3 && index(uc $x[3], $op) == -1);
309 next if ($#x == 4 && !match_ref($x[4]));
310
311 allow("$filename:$line") if ($x[0] eq 'allow');
312 deny("you are not permitted to " . $opstr{$op} . " $ref",
313 "$filename:$line");
314 }
315 close($fd);
316 allow("default rule");
317}
318
319####
320
321# Sanity checks
322unless ($git_dir) {
323 pod2usage(-exitstatus => 0, -verbose => 2) if ($ref eq "--help");
324 deny "try \"$0 --help\" for fore info"
325}
326
327$debug = git_value('config', '--bool', 'hooks.acldebug') unless ($debug);
328$logfile = git_value('config', 'hooks.acllog');
329if ($logfile && $logfile !~ /[>|]/) {
330 $logfile = ">>$logfile";
331}
332$quiet = git_value('config', 'hooks.aclquiet') unless ($debug);
333
334my $httpdusr = git_value('config', 'hooks.httpd-user');
335if (defined($httpdusr) and $user_name eq $httpdusr) {
336 deny "need authenticated user" unless defined($ENV{AUTH_TYPE});
337 $user_name = $ENV{REMOTE_USER};
338}
339
340deny "need a ref name" unless $ref;
341deny "bogus ref $ref" unless $ref =~ s,^refs/,,;
342deny "bad old value $old" unless $old =~ /^[a-z0-9]{40}$/;
343deny "bad new value $new" unless $new =~ /^[a-z0-9]{40}$/;
344deny "no such user" unless $user_name;
345allow "no change requested" if $old eq $new;
346
347$project_name = File::Spec->rel2abs($git_dir);
348$project_name =~ m,/([^/]+)(?:\.git|/\.git)$,;
349$project_name = $1;
350
351if ($old =~ /^0{40}$/) {
352 $op = 'C';
353} elsif ($new =~ /^0{40}$/) {
354 $op = 'D';
355} elsif ($ref =~ m,^heads/, && $old eq git_value('merge-base',$old,$new)) {
356 $op = 'U';
357} else {
358 $op = 'R';
359}
360
361info "$user_name requested $opstr{$op} on $ref in $project_name";
362
363check_acl($project_name, $op, $ref);
364
365# Finis
366
367
368
369
370
371

Return to:

Send suggestions and report system problems to the System administrator.