diff options
-rwxr-xr-x | git/gitaclhook | 276 | ||||
-rwxr-xr-x | upload/gnupload | 3 |
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 @@ | |||
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 | |||
17 | use strict; | ||
18 | use File::Spec; | ||
19 | |||
20 | =doc | ||
21 | This hook is intended to be run as an "update" hook by git. | ||
22 | It is called by git-receive-pack with arguments: refname old-sha1 new-sha1. | ||
23 | |||
24 | If the hooks.aclfile keyword is defined in the repository's config file, | ||
25 | this hook will parse the file and allow or deny update depending on | ||
26 | its settings. If hooks.aclfile is not defined, update is allowed | ||
27 | unconditionally. | ||
28 | |||
29 | The ACL file has the usual line-oriented syntax. Comments are introduced | ||
30 | by the # sign and extend to the end of the physical line. Comments and | ||
31 | empty lines are ignored. | ||
32 | |||
33 | Non-empty lines introduce ACL rules. The syntax is: | ||
34 | |||
35 | VERB PROJECT USER [OP REF] | ||
36 | |||
37 | where brackets denote optional parts. The parts of an ACL are: | ||
38 | |||
39 | VERB Either 'allow' or 'deny', to allow or deny the operation, | ||
40 | correspondingly. | ||
41 | |||
42 | PROJECT 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 | |||
47 | USER 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 | |||
53 | The optional parts are: | ||
54 | |||
55 | OP 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 | |||
63 | REF 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 | |||
69 | The rule applies only if its PROJECT and USER parts match the project which is | ||
70 | being updated and the user who requests the update, its OP contains the opcode | ||
71 | of the requested operation and REF matches the affected ref. Missing REF | ||
72 | and/or OP are treated as a match. | ||
73 | |||
74 | If no rule applies, the operation is allowed. | ||
75 | |||
76 | For example, assume you have the following ACL file: | ||
77 | |||
78 | allow myprog %devel U heads/master | ||
79 | allow myprog %pm CDUR heads/ | ||
80 | allow myprog %pm C ^heads/tags/v\\d+$ | ||
81 | allow myprog admin CDUR | ||
82 | deny myprog all | ||
83 | |||
84 | Then the users from the 'devel' group will be able to push updates to | ||
85 | refs/heads/master, the users from the 'pm' group will be allowed to do | ||
86 | anything with refs under refs/heads and to create tags with names beginning | ||
87 | with 'v' and containing only digits afterwards, and the user 'admin' will | ||
88 | be allowed to do anything he pleases. No other users will be allowed to | ||
89 | update that repository. | ||
90 | |||
91 | Configuration settings: | ||
92 | |||
93 | hooks.aclfile STRING Name of the ACL file | ||
94 | hooks.acllog STRING Send log info to this file | ||
95 | hooks.acldebug BOOL Enable debugging | ||
96 | hooks.aclquiet BOOL Suppress diagnostics on stderr | ||
97 | |||
98 | =cut | ||
99 | |||
100 | my $debug = $ENV{GIT_UPDATE_DEBUG} > 0; | ||
101 | my $logfile; | ||
102 | my $quiet; | ||
103 | my ($user_name) = getpwuid $<; | ||
104 | my $git_dir = $ENV{GIT_DIR}; | ||
105 | my $ref = $ARGV[0]; | ||
106 | my $old = $ARGV[1]; | ||
107 | my $new = $ARGV[2]; | ||
108 | |||
109 | my $project_name; | ||
110 | my $op; | ||
111 | |||
112 | my %opstr = ('C' => 'create', | ||
113 | 'D' => 'delete', | ||
114 | 'U' => 'update', | ||
115 | 'R' => 'rewind/rebase'); | ||
116 | |||
117 | sub 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 | |||
134 | sub 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 | |||
147 | sub 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 | |||
155 | sub info ($) { | ||
156 | logmsg("INFO", $_[0]); | ||
157 | print STDERR "info: $_[0]\n" if $debug; | ||
158 | } | ||
159 | |||
160 | sub git_value (@) { | ||
161 | my $fd; | ||
162 | |||
163 | open($fd,'-|','git',@_); | ||
164 | local $_ = <$fd>; | ||
165 | chop; | ||
166 | close($fd); | ||
167 | return $_; | ||
168 | } | ||
169 | |||
170 | sub 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 | |||
186 | sub 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 | |||
193 | sub 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 | ||
236 | deny "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'); | ||
240 | if ($logfile && $logfile !~ /[>|]/) { | ||
241 | $logfile = ">>$logfile"; | ||
242 | } | ||
243 | $quiet = git_value('config', 'hooks.aclquiet') unless ($debug); | ||
244 | |||
245 | deny "need a ref name" unless $ref; | ||
246 | deny "bogus ref $ref" unless $ref =~ s,^refs/,,; | ||
247 | deny "bad old value $old" unless $old =~ /^[a-z0-9]{40}$/; | ||
248 | deny "bad new value $new" unless $new =~ /^[a-z0-9]{40}$/; | ||
249 | deny "no such user" unless $user_name; | ||
250 | allow "no change requested" if $old eq $new; | ||
251 | |||