1 |
#! xPERL_PATHx |
#! @PERL@ -T |
2 |
# -*-Perl-*- |
# -*-Perl-*- |
3 |
|
|
4 |
|
# Copyright (C) 1994-2005 The Free Software Foundation, Inc. |
5 |
|
|
6 |
|
# This program is free software; you can redistribute it and/or modify |
7 |
|
# it under the terms of the GNU General Public License as published by |
8 |
|
# the Free Software Foundation; either version 2, or (at your option) |
9 |
|
# any later version. |
10 |
# |
# |
11 |
# Access control lists for CVS. dgg@ksr.com (David G. Grubbs) |
# This program is distributed in the hope that it will be useful, |
12 |
# |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
# CVS "commitinfo" for matching repository names, running the program it finds |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
# on the same line. More information is available in the CVS man pages. |
# GNU General Public License for more details. |
15 |
# |
|
16 |
# ==== INSTALLATION: |
############################################################################### |
17 |
# |
############################################################################### |
18 |
# To use this program as I intended, do the following four things: |
############################################################################### |
|
# |
|
|
# 0. Install PERL. :-) |
|
|
# |
|
|
# 1. Put one line, as the *only* non-comment line, in your commitinfo file: |
|
|
# |
|
|
# DEFAULT /usr/local/bin/cvs_acls |
|
|
# |
|
|
# 2. Install this file as /usr/local/bin/cvs_acls and make it executable. |
|
|
# |
|
|
# 3. Create a file named $CVSROOT/CVSROOT/avail. |
|
|
# |
|
|
# ==== FORMAT OF THE avail FILE: |
|
|
# |
|
|
# The avail file determines whether you may commit files. It contains lines |
|
|
# read from top to bottom, keeping track of a single "bit". The "bit" |
|
|
# defaults to "on". It can be turned "off" by "unavail" lines and "on" by |
|
|
# "avail" lines. ==> Last one counts. |
|
|
# |
|
|
# Any line not beginning with "avail" or "unavail" is ignored. |
|
|
# |
|
|
# Lines beginning with "avail" or "unavail" are assumed to be '|'-separated |
|
|
# triples: (All spaces and tabs are ignored in a line.) |
|
|
# |
|
|
# {avail.*,unavail.*} [| user,user,... [| repos,repos,...]] |
|
|
# |
|
|
# 1. String starting with "avail" or "unavail". |
|
|
# 2. Optional, comma-separated list of usernames. |
|
|
# 3. Optional, comma-separated list of repository pathnames. |
|
|
# These are pathnames relative to $CVSROOT. They can be directories or |
|
|
# filenames. A directory name allows access to all files and |
|
|
# directories below it. |
|
|
# |
|
|
# Example: (Text from the ';;' rightward may not appear in the file.) |
|
|
# |
|
|
# unavail ;; Make whole repository unavailable. |
|
|
# avail|dgg ;; Except for user "dgg". |
|
|
# avail|fred, john|bin/ls ;; Except when "fred" or "john" commit to |
|
|
# ;; the module whose repository is "bin/ls" |
|
|
# |
|
|
# PROGRAM LOGIC: |
|
|
# |
|
|
# CVS passes to @ARGV an absolute directory pathname (the repository |
|
|
# appended to your $CVSROOT variable), followed by a list of filenames |
|
|
# within that directory. |
|
|
# |
|
|
# We walk through the avail file looking for a line that matches both |
|
|
# the username and repository. |
|
19 |
# |
# |
20 |
# A username match is simply the user's name appearing in the second |
# THIS SCRIPT IS PROBABLY BROKEN. REMOVING THE -T SWITCH ON THE #! LINE ABOVE |
21 |
# column of the avail line in a space-or-comma separate list. |
# WOULD FIX IT, BUT THIS IS INSECURE. WE RECOMMEND FIXING THE ERRORS WHICH THE |
22 |
|
# -T SWITCH WILL CAUSE PERL TO REPORT BEFORE RUNNING THIS SCRIPT FROM A CVS |
23 |
|
# SERVER TRIGGER. PLEASE SEND PATCHES CONTAINING THE CHANGES YOU FIND |
24 |
|
# NECESSARY TO RUN THIS SCRIPT WITH THE TAINT-CHECKING ENABLED BACK TO THE |
25 |
|
# <@PACKAGE_BUGREPORT@> MAILING LIST. |
26 |
# |
# |
27 |
# A repository match is either: |
# For more on general Perl security and taint-checking, please try running the |
28 |
# - One element of the third column matches $ARGV[0], or some |
# `perldoc perlsec' command. |
|
# parent directory of $ARGV[0]. |
|
|
# - Otherwise *all* file arguments ($ARGV[1..$#ARGV]) must be |
|
|
# in the file list in one avail line. |
|
|
# - In other words, using directory names in the third column of |
|
|
# the avail file allows committing of any file (or group of |
|
|
# files in a single commit) in the tree below that directory. |
|
|
# - If individual file names are used in the third column of |
|
|
# the avail file, then files must be committed individually or |
|
|
# all files specified in a single commit must all appear in |
|
|
# third column of a single avail line. |
|
29 |
# |
# |
30 |
|
############################################################################### |
31 |
|
############################################################################### |
32 |
|
############################################################################### |
33 |
|
|
34 |
|
=head1 Name |
35 |
|
|
36 |
|
cvs_acls - Access Control List for CVS |
37 |
|
|
38 |
|
=head1 Synopsis |
39 |
|
|
40 |
|
In 'commitinfo': |
41 |
|
|
42 |
|
repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER][-f <logfile>] |
43 |
|
|
44 |
|
where: |
45 |
|
|
46 |
|
-d turns on debug information |
47 |
|
-u passes the client-side userId to the cvs_acls script |
48 |
|
-f specifies an alternate filename for the restrict_log file |
49 |
|
|
50 |
|
In 'cvsacl': |
51 |
|
|
52 |
|
{allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]] |
53 |
|
|
54 |
|
where: |
55 |
|
|
56 |
|
allow|deny - allow: commits are allowed; deny: prohibited |
57 |
|
user - userId to be allowed or restricted |
58 |
|
repos - file or directory to be allowed or restricted |
59 |
|
branch - branch to be allowed or restricted |
60 |
|
|
61 |
|
See below for examples. |
62 |
|
|
63 |
|
=head1 Licensing |
64 |
|
|
65 |
|
cvs_acls - provides access control list functionality for CVS |
66 |
|
|
67 |
|
Copyright (c) 2004 by Peter Connolly <peter.connolly@cnet.com> |
68 |
|
All rights reserved. |
69 |
|
|
70 |
|
This program is free software; you can redistribute it and/or modify |
71 |
|
it under the terms of the GNU General Public License as published by |
72 |
|
the Free Software Foundation; either version 2 of the License, or |
73 |
|
(at your option) any later version. |
74 |
|
|
75 |
|
This program is distributed in the hope that it will be useful, |
76 |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
77 |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
78 |
|
GNU General Public License for more details. |
79 |
|
|
80 |
|
You should have received a copy of the GNU General Public License |
81 |
|
along with this program; if not, write to the Free Software |
82 |
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
83 |
|
|
84 |
|
=head1 Description |
85 |
|
|
86 |
|
This script--cvs_acls--is invoked once for each directory within a |
87 |
|
"cvs commit". The set of files being committed for that directory as |
88 |
|
well as the directory itself, are passed to this script. This script |
89 |
|
checks its 'cvsacl' file to see if any of the files being committed |
90 |
|
are on the 'cvsacl' file's restricted list. If any of the files are |
91 |
|
restricted, then the cvs_acls script passes back an exit code of 1 |
92 |
|
which disallows the commits for that directory. |
93 |
|
|
94 |
|
Messages are returned to the committer indicating the file(s) that |
95 |
|
he/she are not allowed to committ. Additionally, a site-specific |
96 |
|
set of messages (e.g., contact information) can be included in these |
97 |
|
messages. |
98 |
|
|
99 |
|
When a commit is prohibited, log messages are written to a restrict_log |
100 |
|
file in $CVSROOT/CVSROOT. This default file can be redirected to |
101 |
|
another destination. |
102 |
|
|
103 |
|
The script is triggered from the 'commitinfo' file in $CVSROOT/CVSROOT/. |
104 |
|
|
105 |
|
=head1 Enhancements |
106 |
|
|
107 |
|
This section lists the bug fixes and enhancements added to cvs_acls |
108 |
|
that make up the current cvs_acls. |
109 |
|
|
110 |
|
=head2 Fixed Bugs |
111 |
|
|
112 |
|
This version attempts to get rid the following bugs from the |
113 |
|
original version of cvs_acls: |
114 |
|
|
115 |
|
=over 2 |
116 |
|
|
117 |
|
=item * |
118 |
|
Multiple entries on an 'cvsacl' line will be matched individually, |
119 |
|
instead of requiring that all commit files *exactly* match all |
120 |
|
'cvsacl' entries. Commiting a file not in the 'cvsacl' list would |
121 |
|
allow *all* files (including a restricted file) to be committed. |
122 |
|
|
123 |
|
[IMO, this basically made the original script unuseable for our |
124 |
|
situation since any arbitrary combination of committed files could |
125 |
|
avoid matching the 'cvsacl's entries.] |
126 |
|
|
127 |
|
=item * |
128 |
|
Handle specific filename restrictions. cvs_acls didn't restrict |
129 |
|
individual files specified in 'cvsacl'. |
130 |
|
|
131 |
|
=item * |
132 |
|
Correctly handle multiple, specific filename restrictions |
133 |
|
|
134 |
|
=item * |
135 |
|
Prohibit mix of dirs and files on a single 'cvsacl' line |
136 |
|
[To simplify the logic and because this would be normal usage.] |
137 |
|
|
138 |
|
=item * |
139 |
|
Correctly handle a mixture of branch restrictions within one work |
140 |
|
directory |
141 |
|
|
142 |
|
=item * |
143 |
|
$CVSROOT existence is checked too late |
144 |
|
|
145 |
|
=item * |
146 |
|
Correctly handle the CVSROOT=:local:/... option (useful for |
147 |
|
interactive testing) |
148 |
|
|
149 |
|
=item * |
150 |
|
Replacing shoddy "$universal_off" logic |
151 |
|
(Thanks to Karl-Konig Konigsson for pointing this out.) |
152 |
|
|
153 |
|
=back |
154 |
|
|
155 |
|
=head2 Enhancements |
156 |
|
|
157 |
|
=over 2 |
158 |
|
|
159 |
|
=item * |
160 |
|
Checks modules in the 'cvsacl' file for valid files and directories |
161 |
|
|
162 |
|
=item * |
163 |
|
Accurately report restricted entries and their matching patterns |
164 |
|
|
165 |
|
=item * |
166 |
|
Simplified and commented overly complex PERL REGEXPs for readability |
167 |
|
and maintainability |
168 |
|
|
169 |
|
=item * |
170 |
|
Skip the rest of processing if a mismatch on portion of the 'cvsacl' line |
171 |
|
|
172 |
|
=item * |
173 |
|
Get rid of opaque "karma" messages in favor of user-friendly messages |
174 |
|
that describe which user, file(s) and branch(es) were disallowed. |
175 |
|
|
176 |
|
=item * |
177 |
|
Add optional 'restrict_msg' file for additional, site-specific |
178 |
|
restriction messages. |
179 |
|
|
180 |
|
=item * |
181 |
|
Take a "-u" parameter for $USER from commit_prep so that the script |
182 |
|
can do restrictions based on the client-side userId rather than the |
183 |
|
server-side userId (usually 'cvs'). |
184 |
|
|
185 |
|
(See discussion below on "Admin Setup" for more on this point.) |
186 |
|
|
187 |
|
=item * |
188 |
|
Added a lot more debug trace |
189 |
|
|
190 |
|
=item * |
191 |
|
Tested these restrictions with concurrent use of pserver and SSH |
192 |
|
access to model our transition from pserver to ext access. |
193 |
|
|
194 |
|
=item * |
195 |
|
Added logging of restricted commit attempts. |
196 |
|
Restricted commits can be sent to a default file: |
197 |
|
$CVSROOT/CVSROOT/restrictlog or to one passed to the script |
198 |
|
via the -f command parameter. |
199 |
|
|
200 |
|
=back |
201 |
|
|
202 |
|
=head2 ToDoS |
203 |
|
|
204 |
|
=over 2 |
205 |
|
|
206 |
|
=item * |
207 |
|
Need to deal with pserver/SSH transition with conflicting umasks? |
208 |
|
|
209 |
|
=item * |
210 |
|
Use a CPAN module to handle command parameters. |
211 |
|
|
212 |
|
=item * |
213 |
|
Use a CPAN module to clone data structures. |
214 |
|
|
215 |
|
=back |
216 |
|
|
217 |
|
=head1 Version Information |
218 |
|
|
219 |
|
This is not offered as a fix to the original 'cvs_acls' script since it |
220 |
|
differs substantially in goals and methods from the original and there |
221 |
|
are probably a significant number of people out there that still require |
222 |
|
the original version's functionality. |
223 |
|
|
224 |
|
The 'cvsacl' file flags of 'allow' and 'deny' were intentionally |
225 |
|
changed to 'allow' and 'deny' because there are enough differences |
226 |
|
between the original script's behavior and this one's that we wanted to |
227 |
|
make sure that users will rethink their 'cvsacl' file formats before |
228 |
|
plugging in this newer script. |
229 |
|
|
230 |
|
Please note that there has been very limited cross-platform testing of |
231 |
|
this script!!! (We did not have the time or resources to do exhaustive |
232 |
|
cross-platform testing.) |
233 |
|
|
234 |
|
It was developed and tested under Red Hat Linux 9.0 using PERL 5.8.0. |
235 |
|
Additionally, it was built and tested under Red Hat Linux 7.3 using |
236 |
|
PERL 5.6.1. |
237 |
|
|
238 |
|
$Id$ |
239 |
|
|
240 |
|
This version is based on the 1.11.13 version of cvs_acls |
241 |
|
peter.connolly@cnet.com (Peter Connolly) |
242 |
|
|
243 |
|
Access control lists for CVS. dgg@ksr.com (David G. Grubbs) |
244 |
|
Branch specific controls added by voisine@bytemobile.com (Aaron Voisine) |
245 |
|
|
246 |
|
=head1 Installation |
247 |
|
|
248 |
|
To use this program, do the following four things: |
249 |
|
|
250 |
|
0. Install PERL, version 5.6.1 or 5.8.0. |
251 |
|
|
252 |
|
1. Admin Setup: |
253 |
|
|
254 |
|
There are two choices here. |
255 |
|
|
256 |
|
a) The first option is to use the $ENV{"USER"}, server-side userId |
257 |
|
(from the third column of your pserver 'passwd' file) as the basis for |
258 |
|
your restrictions. In this case, you will (at a minimum) want to set |
259 |
|
up a new "cvsadmin" userId and group on the pserver machine. |
260 |
|
CVS administrators will then set up their 'passwd' file entries to |
261 |
|
run either as "cvs" (for regular users) or as "cvsadmin" (for power |
262 |
|
users). Correspondingly, your 'cvsacl' file will only list 'cvs' |
263 |
|
and 'cvsadmin' as the userIds in the second column. |
264 |
|
|
265 |
|
Commentary: A potential weakness of this is that the xinetd |
266 |
|
cvspserver process will need to run as 'root' in order to switch |
267 |
|
between the 'cvs' and the 'cvsadmin' userIds. Some sysadmins don't |
268 |
|
like situations like this and may want to chroot the process. |
269 |
|
Talk to them about this point... |
270 |
|
|
271 |
|
b) The second option is to use the client-side userId as the basis for |
272 |
|
your restrictions. In this case, all the xinetd cvspserver processes |
273 |
|
can run as userId 'cvs' and no 'root' userId is required. If you have |
274 |
|
a 'passwd' file that lists 'cvs' as the effective run-time userId for |
275 |
|
all your users, then no changes to this file are needed. Your 'cvsacl' |
276 |
|
file will use the individual, client-side userIds in its 2nd column. |
277 |
|
|
278 |
|
As long as the userIds in pserver's 'passwd' file match those userIds |
279 |
|
that your Linux server know about, this approach is ideal if you are |
280 |
|
planning to move from pserver to SSH access at some later point in time. |
281 |
|
Just by switching the CVSROOT var from CVSROOT=:pserver:<userId>... to |
282 |
|
CVSROOT=:ext:<userId>..., users can switch over to SSH access without |
283 |
|
any other administrative changes. When all users have switched over to |
284 |
|
SSH, the inherently insecure xinetd cvspserver process can be disabled. |
285 |
|
[http://ximbiot.com/cvs/manual/cvs-1.11.17/cvs_2.html#SEC32] |
286 |
|
|
287 |
|
:TODO: The only potential glitch with the SSH approach is the possibility |
288 |
|
that each user can have differing umasks that might interfere with one |
289 |
|
another, especially during a transition from pserver to SSH. As noted |
290 |
|
in the ToDo section, this needs a good strategy and set of tests for that |
291 |
|
yet... |
292 |
|
|
293 |
|
2. Put two lines, as the *only* non-comment lines, in your commitinfo file: |
294 |
|
|
295 |
|
ALL $CVSROOT/CVSROOT/commit_prep |
296 |
|
ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f <logfilename>] |
297 |
|
|
298 |
|
where "-d" turns on debug trace |
299 |
|
"-u $USER" passes the client-side userId to cvs_acls |
300 |
|
"-f <logfilename"> overrides the default filename used to log |
301 |
|
restricted commit attempts. |
302 |
|
|
303 |
|
(These are handled in the processArgs() subroutine.) |
304 |
|
|
305 |
|
If you are using client-side userIds to restrict access to your |
306 |
|
repository, make sure that they are in this order since the commit_prep |
307 |
|
script is required in order to pass the $USER parameter. |
308 |
|
|
309 |
|
A final note about the repository matching pattern. The example above |
310 |
|
uses "ALL" but note that this means that the cvs_acls script will run |
311 |
|
for each and every commit in your repository. Obviously, in a large |
312 |
|
repository this adds up to a lot of overhead that may not be necesary. |
313 |
|
A better strategy is to use a repository pattern that is more specific |
314 |
|
to the areas that you wish to secure. |
315 |
|
|
316 |
|
3. Install this file as $CVSROOT/CVSROOT/cvs_acls and make it executable. |
317 |
|
|
318 |
|
4. Create a file named CVSROOT/cvsacl and optionally add it to |
319 |
|
CVSROOT/checkoutlist and check it in. See the CVS manual's |
320 |
|
administrative files section about checkoutlist. Typically: |
321 |
|
|
322 |
|
$ cvs checkout CVSROOT |
323 |
|
$ cd CVSROOT |
324 |
|
[ create the cvsacl file, include 'commitinfo' line ] |
325 |
|
[ add cvsacl to checkoutlist ] |
326 |
|
$ cvs add cvsacl |
327 |
|
$ cvs commit -m 'Added cvsacl for use with cvs_acls.' cvsacl checkoutlist |
328 |
|
|
329 |
|
Note: The format of the 'cvsacl' file is described in detail immediately |
330 |
|
below but here is an important set up point: |
331 |
|
|
332 |
|
Make sure to include a line like the following: |
333 |
|
|
334 |
|
deny||CVSROOT/commitinfo CVSROOT/cvsacl |
335 |
|
allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl |
336 |
|
|
337 |
|
that restricts access to commitinfo and cvsacl since this would be one of |
338 |
|
the easiest "end runs" around this ACL approach. ('commitinfo' has the |
339 |
|
line that executes the cvs_acls script and, of course, all the |
340 |
|
restrictions are in 'cvsacl'.) |
341 |
|
|
342 |
|
5. (Optional) Create a 'restrict_msg' file in the $CVSROOT/CVSROOT directory. |
343 |
|
Whenever there is a restricted file or dir message, cvs_acls will look |
344 |
|
for this file and, if it exists, print its contents as part of the |
345 |
|
commit-denial message. This gives you a chance to print any site-specific |
346 |
|
information (e.g., who to call, what procedures to look up,...) whenever |
347 |
|
a commit is denied. |
348 |
|
|
349 |
$debug = 0; |
=head1 Format of the cvsacl file |
350 |
|
|
351 |
|
The 'cvsacl' file determines whether you may commit files. It contains lines |
352 |
|
read from top to bottom, keeping track of whether a given user, repository |
353 |
|
and branch combination is "allowed" or "denied." The script will assume |
354 |
|
"allowed" on all repository paths until 'allow' and 'deny' rules change |
355 |
|
that default. |
356 |
|
|
357 |
|
The normal pattern is to specify an 'deny' rule to turn off |
358 |
|
access to ALL users, then follow it with a matching 'allow' rule that will |
359 |
|
turn on access for a select set of users. In the case of multiple rules for |
360 |
|
the same user, repository and branch, the last one takes precedence. |
361 |
|
|
362 |
|
Blank lines and lines with only comments are ignored. Any other lines not |
363 |
|
beginning with "allow" or "deny" are logged to the restrict_log file. |
364 |
|
|
365 |
|
Lines beginning with "allow" or "deny" are assumed to be '|'-separated |
366 |
|
triples: (All spaces and tabs are ignored in a line.) |
367 |
|
|
368 |
|
{allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]] |
369 |
|
|
370 |
|
1. String starting with "allow" or "deny". |
371 |
|
2. Optional, comma-separated list of usernames. |
372 |
|
3. Optional, comma-separated list of repository pathnames. |
373 |
|
These are pathnames relative to $CVSROOT. They can be directories or |
374 |
|
filenames. A directory name allows or restricts access to all files and |
375 |
|
directories below it. One line can have either directories or filenames |
376 |
|
but not both. |
377 |
|
4. Optional, comma-separated list of branch tags. |
378 |
|
If not specified, all branches are assumed. Use HEAD to reference the |
379 |
|
main branch. |
380 |
|
|
381 |
|
Example: (Note: No in-line comments.) |
382 |
|
|
383 |
|
# ----- Make whole repository unavailable. |
384 |
|
deny |
385 |
|
|
386 |
|
# ----- Except for user "dgg". |
387 |
|
allow|dgg |
388 |
|
|
389 |
|
# ----- Except when "fred" or "john" commit to the |
390 |
|
# module whose repository is "bin/ls" |
391 |
|
allow|fred, john|bin/ls |
392 |
|
|
393 |
|
# ----- Except when "ed" commits to the "stable" |
394 |
|
# branch of the "bin/ls" repository |
395 |
|
allow|ed|/bin/ls|stable |
396 |
|
|
397 |
|
=head1 Program Logic |
398 |
|
|
399 |
|
CVS passes to @ARGV an absolute directory pathname (the repository |
400 |
|
appended to your $CVSROOT variable), followed by a list of filenames |
401 |
|
within that directory that are to be committed. |
402 |
|
|
403 |
|
The script walks through the 'cvsacl' file looking for matches on |
404 |
|
the username, repository and branch. |
405 |
|
|
406 |
|
A username match is simply the user's name appearing in the second |
407 |
|
column of the cvsacl line in a space-or-comma separate list. If |
408 |
|
blank, then any user will match. |
409 |
|
|
410 |
|
A repository match: |
411 |
|
|
412 |
|
=over 2 |
413 |
|
|
414 |
|
=item * |
415 |
|
Each entry in the modules section of the current 'cvsacl' line is |
416 |
|
examined to see if it is a dir or a file. The line must have |
417 |
|
either files or dirs, but not both. (To simplify the logic.) |
418 |
|
|
419 |
|
=item * |
420 |
|
If neither, then assume the 'cvsacl' file was set up in error and |
421 |
|
skip that 'allow' line. |
422 |
|
|
423 |
|
=item * |
424 |
|
If a dir, then each dir pattern is matched separately against the |
425 |
|
beginning of each of the committed files in @ARGV. |
426 |
|
|
427 |
|
=item * |
428 |
|
If a file, then each file pattern is matched exactly against each |
429 |
|
of the files to be committed in @ARGV. |
430 |
|
|
431 |
|
=item * |
432 |
|
Repository and branch must BOTH match together. This is to cover |
433 |
|
the use case where a user has multiple branches checked out in |
434 |
|
a single work directory. Commit files can be from different |
435 |
|
branches. |
436 |
|
|
437 |
|
A branch match is either: |
438 |
|
|
439 |
|
=over 4 |
440 |
|
|
441 |
|
=item * |
442 |
|
When no branches are listed in the fourth column. ("Match any.") |
443 |
|
|
444 |
|
=item * |
445 |
|
All elements from the fourth column are matched against each of |
446 |
|
the tag names for $ARGV[1..$#ARGV] found in the %branches file. |
447 |
|
|
448 |
|
=back |
449 |
|
|
450 |
|
=item * |
451 |
|
'allow' match remove that match from the tally map. |
452 |
|
|
453 |
|
=item * |
454 |
|
Restricted ('deny') matches are saved in the %repository_matches |
455 |
|
table. |
456 |
|
|
457 |
|
=item * |
458 |
|
If there is a match on user, repository and branch: |
459 |
|
|
460 |
|
If repository, branch and user match |
461 |
|
if 'deny' |
462 |
|
add %repository_matches entries to %restricted_entries |
463 |
|
else if 'allow' |
464 |
|
remove %repository_matches entries from %restricted_entries |
465 |
|
|
466 |
|
=item * |
467 |
|
At the end of all the 'cvsacl' line checks, check to see if there |
468 |
|
are any entries in the %restricted_entries. If so, then deny the |
469 |
|
commit. |
470 |
|
|
471 |
|
=back |
472 |
|
|
473 |
|
=head2 Pseudocode |
474 |
|
|
475 |
|
read CVS/Entries file and create branch{file}->{branch} hash table |
476 |
|
+ for each 'allow' and 'deny' line in the 'cvsacl' file: |
477 |
|
| user match? |
478 |
|
| - Yes: set $user_match = 1; |
479 |
|
| repository and branch match? |
480 |
|
| - Yes: add to %repository_matches; |
481 |
|
| did user, repository match? |
482 |
|
| - Yes: if 'deny' then |
483 |
|
| add %repository_matches -> %restricted_entries |
484 |
|
| if 'allow' then |
485 |
|
| remove %repository_matches <- %restricted_entries |
486 |
|
+ end for loop |
487 |
|
any saved restrictions? |
488 |
|
no: exit, |
489 |
|
set exit code allowing commits and exit |
490 |
|
yes: report restrictions, |
491 |
|
set exit code prohibiting commits and exit |
492 |
|
|
493 |
|
=head2 Sanity Check |
494 |
|
|
495 |
|
1) file allow trumps a dir deny |
496 |
|
deny||java/lib |
497 |
|
allow||java/lib/README |
498 |
|
2) dir allow can undo a file deny |
499 |
|
deny||java/lib/README |
500 |
|
allow||java/lib |
501 |
|
3) file deny trumps a dir allow |
502 |
|
allow||java/lib |
503 |
|
deny||java/lib/README |
504 |
|
4) dir deny trumps a file allow |
505 |
|
allow||java/lib/README |
506 |
|
deny||java/lib |
507 |
|
... so last match always takes precedence |
508 |
|
|
509 |
|
=cut |
510 |
|
|
511 |
|
$debug = 0; # Set to 1 for debug messages |
512 |
|
|
513 |
|
%repository_matches = (); # hash of match file and pattern from 'cvsacl' |
514 |
|
# repository_matches --> [branch, matching-pattern] |
515 |
|
# (Used during module/branch matching loop) |
516 |
|
|
517 |
|
%restricted_entries = (); # hash table of restricted commit files (from @ARGV) |
518 |
|
# restricted_entries --> branch |
519 |
|
# (If user/module/branch all match on an 'deny' |
520 |
|
# line, then entries added to this map.) |
521 |
|
|
522 |
|
%branch; # hash table of key: commit file; value: branch |
523 |
|
# Built from ".../CVS/Entries" file of directory |
524 |
|
# currently being examined |
525 |
|
|
526 |
|
# ---------------------------------------------------------------- get CVSROOT |
527 |
$cvsroot = $ENV{'CVSROOT'}; |
$cvsroot = $ENV{'CVSROOT'}; |
528 |
$availfile = $cvsroot . "/CVSROOT/avail"; |
die "Must set CVSROOT\n" if !$cvsroot; |
529 |
$myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"}); |
if ($cvsroot =~ /:([\/\w]*)$/) { # Filter ":pserver:", ":local:"-type prefixes |
530 |
|
$cvsroot = $1; |
531 |
|
} |
532 |
|
|
533 |
|
# ------------------------------------------------------------- set file paths |
534 |
|
$entries = "CVS/Entries"; # client-side file??? |
535 |
|
$cvsaclfile = $cvsroot . "/CVSROOT/cvsacl"; |
536 |
|
$restrictfile = $cvsroot . "/CVSROOT/restrict_msg"; |
537 |
|
$restrictlog = $cvsroot . "/CVSROOT/restrict_log"; |
538 |
|
|
539 |
|
# --------------------------------------------------------------- process args |
540 |
|
$user_name = processArgs(\@ARGV); |
541 |
|
|
542 |
|
print("$$ \@ARGV after processArgs is: @ARGV.\n") if $debug; |
543 |
|
print("$$ ========== Begin $PROGRAM_NAME for \"$ARGV[0]\" repository. ========== \n") if $debug; |
544 |
|
|
545 |
|
# --------------------------------------------------------------- filter @ARGV |
546 |
eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" |
eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" |
547 |
while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); |
while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); |
548 |
exit 255 if $die; # process any variable=value switches |
exit 255 if $die; # process any variable=value switches |
549 |
|
|
550 |
die "Must set CVSROOT\n" if !$cvsroot; |
print("$$ \@ARGV after shift processing contains:",join("\, ",@ARGV),".\n") if $debug; |
|
($repos = shift) =~ s:^$cvsroot/::; |
|
|
grep($_ = $repos . '/' . $_, @ARGV); |
|
551 |
|
|
552 |
print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug; |
# ---------------------------------------------------------------- get cvsroot |
553 |
|
($repository = shift) =~ s:^$cvsroot/::; |
554 |
|
grep($_ = $repository . '/' . $_, @ARGV); |
555 |
|
|
556 |
$exit_val = 0; # Good Exit value |
print("$$ \$cvsroot is: $cvsroot.\n") if $debug; |
557 |
|
print "$$ Repos: $repository\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug; |
558 |
|
|
559 |
$universal_off = 0; |
$exit_val = 0; # presume good exit value for commit |
560 |
open (AVAIL, $availfile) || exit(0); # It is ok for avail file not to exist |
|
561 |
while (<AVAIL>) { |
# ---------------------------------------------------------------------------- |
562 |
|
# ---------------------------------- create hash table $branch{file -> branch} |
563 |
|
# ---------------------------------------------------------------------------- |
564 |
|
|
565 |
|
# Here's a typical Entries file: |
566 |
|
# |
567 |
|
# /checkoutlist/1.4/Wed Feb 4 23:51:23 2004// |
568 |
|
# /cvsacl/1.3/Tue Feb 24 23:05:43 2004// |
569 |
|
# ... |
570 |
|
# /verifymsg/1.1/Fri Mar 16 19:56:24 2001// |
571 |
|
# D/backup//// |
572 |
|
# D/temp//// |
573 |
|
|
574 |
|
open(ENTRIES, $entries) || die("Cannot open $entries.\n"); |
575 |
|
print("$$ File / Branch\n") if $debug; |
576 |
|
my $i = 0; |
577 |
|
while(<ENTRIES>) { |
578 |
chop; |
chop; |
579 |
next if /^\s*\#/; |
next if /^\s*$/; # Skip blank lines |
580 |
next if /^\s*$/; |
$i = $i + 1; |
581 |
($flagstr, $u, $m) = split(/[\s,]*\|[\s,]*/, $_); |
if (m| |
582 |
|
/ # 1st slash |
583 |
# Skip anything not starting with "avail" or "unavail" and complain. |
([\w.-]*) # file name -> $1 |
584 |
(print "Bad avail line: $_\n"), next |
/ # 2nd slash |
585 |
if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/); |
.* # revision number |
586 |
|
/ # 3rd slash |
587 |
# Set which bit we are playing with. ('0' is OK == Available). |
.* # date and time |
588 |
$flag = (($& eq "avail") ? 0 : 1); |
/ # 4th slash |
589 |
|
.* # keyword |
590 |
# If we find a "universal off" flag (i.e. a simple "unavail") remember it |
/ # 5th slash |
591 |
$universal_off = 1 if ($flag && !$u && !$m); |
T? # 'T' constant |
592 |
|
(\w*) # branch -> #2 |
593 |
# $myname considered "in user list" if actually in list or is NULL |
|x) { |
594 |
$in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u))); |
$branch{$repository . '/' . $1} = ($2) ? $2 : "HEAD"; |
595 |
print "$$ \$myname($myname) in user list: $_\n" if $debug && $in_user; |
print "$$ CVS Entry $i: $1/$2\n" if $debug; |
596 |
|
} |
597 |
# Module matches if it is a NULL module list in the avail line. If module |
} |
598 |
# list is not null, we check every argument combination. |
close(ENTRIES); |
599 |
if (!($in_repo = !$m)) { |
|
600 |
@tmp = split(/[\s,]+/,$m); |
# ---------------------------------------------------------------------------- |
601 |
for $j (@tmp) { |
# ------------------------------------- evaluate each active line from 'cvsacl' |
602 |
# If the repos from avail is a parent(or equal) dir of $repos, OK |
# ---------------------------------------------------------------------------- |
603 |
$in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//); |
open (CVSACL, $cvsaclfile) || exit(0); # It is ok for cvsacl file not to exist |
604 |
} |
while (<CVSACL>) { |
605 |
if (!$in_repo) { |
chop; |
606 |
$in_repo = 1; |
next if /^\s*\#/; # skip comments |
607 |
for $j (@ARGV) { |
next if /^\s*$/; # skip blank lines |
608 |
last if !($in_repo = grep ($_ eq $j, @tmp)); |
# --------------------------------------------- parse current 'cvsacl' line |
609 |
|
print("$$ ==========\n$$ Processing \'cvsacl\' line: $_.\n") if $debug; |
610 |
|
($cvsacl_flag, $cvsacl_userIds, $cvsacl_modules, $cvsacl_branches) = split(/[\s,]*\|[\s,]*/, $_); |
611 |
|
|
612 |
|
# ------------------------------ Validate 'allow' or 'deny' line prefix |
613 |
|
if ($cvsacl_flag !~ /^allow/ && $cvsacl_flag !~ /^deny/) { |
614 |
|
print ("Bad cvsacl line: $_\n") if $debug; |
615 |
|
$log_text = sprintf "Bad cvsacl line: %s", $_; |
616 |
|
write_restrictlog_record($log_text); |
617 |
|
next; |
618 |
|
} |
619 |
|
|
620 |
|
# -------------------------------------------------- init loop match flags |
621 |
|
$user_match = 0; |
622 |
|
%repository_matches = (); |
623 |
|
|
624 |
|
# ------------------------------------------------------------------------ |
625 |
|
# ---------------------------------------------------------- user matching |
626 |
|
# ------------------------------------------------------------------------ |
627 |
|
# $user_name considered "in user list" if actually in list or is NULL |
628 |
|
$user_match = (!$cvsacl_userIds || grep ($_ eq $user_name, split(/[\s,]+/,$cvsacl_userIds))); |
629 |
|
print "$$ \$user_name: $user_name \$user_match match flag is: $user_match.\n" if $debug; |
630 |
|
if (!$user_match) { |
631 |
|
next; # no match, skip to next 'cvsacl' line |
632 |
|
} |
633 |
|
|
634 |
|
# ------------------------------------------------------------------------ |
635 |
|
# ---------------------------------------------------- repository matching |
636 |
|
# ------------------------------------------------------------------------ |
637 |
|
if (!$cvsacl_modules) { # blank module list = all modules |
638 |
|
if (!$cvsacl_branches) { # blank branch list = all branches |
639 |
|
print("$$ Adding all modules to \%repository_matches; null " . |
640 |
|
"\$cvsacl_modules and \$cvsacl_branches.\n") if $debug; |
641 |
|
for $commit_object (@ARGV) { |
642 |
|
$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules]; |
643 |
|
print("$$ \$repository_matches{$commit_object} = " . |
644 |
|
"[$branch{$commit_object}, $cvsacl_modules].\n") if $debug; |
645 |
|
} |
646 |
|
} |
647 |
|
else { # need to check for repository match |
648 |
|
@branch_list = split (/[\s,]+/,$cvsacl_branches); |
649 |
|
print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug; |
650 |
|
for $commit_object (@ARGV) { |
651 |
|
if (grep($branch{$commit_object}, @branch_list)) { |
652 |
|
$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules]; |
653 |
|
print("$$ \$repository_matches{$commit_object} = " . |
654 |
|
"[$branch{$commit_object}, $cvsacl_modules].\n") if $debug; |
655 |
|
} |
656 |
} |
} |
657 |
} |
} |
658 |
} |
} |
659 |
print "$$ \$repos($repos) in repository list: $_\n" if $debug && $in_repo; |
else { |
660 |
|
# ----------------------------------- check every argument combination |
661 |
|
# parse 'cvsacl' modules to array |
662 |
|
my @module_list = split(/[\s,]+/,$cvsacl_modules); |
663 |
|
# ------------- Check all modules in list for either file or directory |
664 |
|
my $fileType = ""; |
665 |
|
if (($fileType = checkFileness(@module_list)) eq "") { |
666 |
|
next; # skip bad file types |
667 |
|
} |
668 |
|
# ---------- Check each combination of 'cvsacl' modules vs. @ARGV files |
669 |
|
print("$$ Checking matches for \@module_list: ", join("\, ",@module_list), ".\n") if $debug; |
670 |
|
# loop thru all command-line commit objects |
671 |
|
for $commit_object (@ARGV) { |
672 |
|
# loop thru all modules on 'cvsacl' line |
673 |
|
for $cvsacl_module (@module_list) { |
674 |
|
print("$$ Is \'cvsacl\': $cvsacl_modules pattern in: \@ARGV " . |
675 |
|
"\$commit_object: $commit_object?\n") if $debug; |
676 |
|
# Do match of beginning of $commit_object |
677 |
|
checkModuleMatch($fileType, $commit_object, $cvsacl_module); |
678 |
|
} # end for commit objects |
679 |
|
} # end for cvsacl modules |
680 |
|
} # end if |
681 |
|
|
682 |
|
print("$$ Matches for: \%repository_matches: ", join("\, ", (keys %repository_matches)), ".\n") if $debug; |
683 |
|
|
684 |
|
# ------------------------------------------------------------------------ |
685 |
|
# ----------------------------------------------------- setting exit value |
686 |
|
# ------------------------------------------------------------------------ |
687 |
|
if ($user_match && %repository_matches) { |
688 |
|
print("$$ An \"$cvsacl_flag\" match on User(s): $cvsacl_userIds; Module(s):" . |
689 |
|
" $cvsacl_modules; Branch(es): $cvsacl_branches.\n") if $debug; |
690 |
|
if ($cvsacl_flag eq "deny") { |
691 |
|
# Add all matches to the hash of restricted modules |
692 |
|
foreach $commitFile (keys %repository_matches) { |
693 |
|
print("$$ Adding \%repository_matches entry: $commitFile.\n") if $debug; |
694 |
|
$restricted_entries{$commitFile} = $repository_matches{$commitFile}[0]; |
695 |
|
} |
696 |
|
} |
697 |
|
else { |
698 |
|
# Remove all matches from the restricted modules hash |
699 |
|
foreach $commitFile (keys %repository_matches) { |
700 |
|
print("$$ Removing \%repository_matches entry: $commitFile.\n") if $debug; |
701 |
|
delete $restricted_entries{$commitFile}; |
702 |
|
} |
703 |
|
} |
704 |
|
} |
705 |
|
print "$$ ==== End of processing for \'cvsacl\' line: $_.\n" if $debug; |
706 |
|
} |
707 |
|
close(CVSACL); |
708 |
|
|
709 |
$exit_val = $flag if ($in_user && $in_repo); |
# ---------------------------------------------------------------------------- |
710 |
print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = $flag\n" if $debug; |
# --------------------------------------- determine final 'commit' disposition |
711 |
|
# ---------------------------------------------------------------------------- |
712 |
|
if (%restricted_entries) { # any restricted entries? |
713 |
|
$exit_val = 1; # don't commit |
714 |
|
print("**** Access denied: Insufficient authority for user: '$user_name\' " . |
715 |
|
"to commit to \'$repository\'.\n**** Contact CVS Administrators if " . |
716 |
|
"you require update access to these directories or files.\n"); |
717 |
|
print("**** file(s)/dir(s) restricted were:\n\t", join("\n\t",keys %restricted_entries), "\n"); |
718 |
|
printOptionalRestrictionMessage(); |
719 |
|
write_restrictlog(); |
720 |
} |
} |
721 |
close(AVAIL); |
elsif (!$exit_val && $debug) { |
722 |
|
print "**** Access allowed: Sufficient authority for commit.\n"; |
723 |
|
} |
724 |
|
|
725 |
print "$$ ==== \$exit_val = $exit_val\n" if $debug; |
print "$$ ==== \$exit_val = $exit_val\n" if $debug; |
|
print "**** Access denied: Insufficient Karma ($myname|$repos)\n" if $exit_val; |
|
|
print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n" |
|
|
if $universal_off && !$exit_val; |
|
726 |
exit($exit_val); |
exit($exit_val); |
727 |
|
|
728 |
|
# ---------------------------------------------------------------------------- |
729 |
|
# -------------------------------------------------------------- end of "main" |
730 |
|
# ---------------------------------------------------------------------------- |
731 |
|
|
732 |
|
|
733 |
|
# ---------------------------------------------------------------------------- |
734 |
|
# -------------------------------------------------------- process script args |
735 |
|
# ---------------------------------------------------------------------------- |
736 |
|
sub processArgs { |
737 |
|
|
738 |
|
# This subroutine is passed a reference to @ARGV. |
739 |
|
|
740 |
|
# If @ARGV contains a "-u" entry, use that as the effective userId. In this |
741 |
|
# case, the userId is the client-side userId that has been passed to this |
742 |
|
# script by the commit_prep script. (This is why the commit_prep script must |
743 |
|
# be placed *before* the cvs_acls script in the commitinfo admin file.) |
744 |
|
|
745 |
|
# Otherwise, pull the userId from the server-side environment. |
746 |
|
|
747 |
|
my $userId = ""; |
748 |
|
my ($argv) = shift; # pick up ref to @ARGV |
749 |
|
my @argvClone = (); # immutable copy for foreach loop |
750 |
|
for ($i=0; $i<(scalar @{$argv}); $i++) { |
751 |
|
$argvClone[$i]=$argv->[$i]; |
752 |
|
} |
753 |
|
|
754 |
|
print("$$ \@_ to processArgs is: @_.\n") if $debug; |
755 |
|
|
756 |
|
# Parse command line arguments (file list is seen as one arg) |
757 |
|
foreach $arg (@argvClone) { |
758 |
|
print("$$ \$arg for processArgs loop is: $arg.\n") if $debug; |
759 |
|
# Set $debug flag? |
760 |
|
if ($arg eq '-d') { |
761 |
|
shift @ARGV; |
762 |
|
$debug = 1; |
763 |
|
print("$$ \$debug flag set on.\n") if $debug; |
764 |
|
print STDERR "Debug turned on...\n"; |
765 |
|
} |
766 |
|
# Passing in a client-side userId? |
767 |
|
elsif ($arg eq '-u') { |
768 |
|
shift @ARGV; |
769 |
|
$userId = shift @ARGV; |
770 |
|
print("$$ client-side \$userId set to: $userId.\n") if $debug; |
771 |
|
} |
772 |
|
# An override for the default restrictlog file? |
773 |
|
elsif ($arg eq '-f') { |
774 |
|
shift @ARGV; |
775 |
|
$restrictlog = shift @ARGV; |
776 |
|
} |
777 |
|
else { |
778 |
|
next; |
779 |
|
} |
780 |
|
} |
781 |
|
|
782 |
|
# No client-side userId passed? then get from server env |
783 |
|
if (!$userId) { |
784 |
|
$userId = $ENV{"USER"} if !($userId = $ENV{"LOGNAME"}); |
785 |
|
print("$$ server-side \$userId set to: $userId.\n") if $debug; |
786 |
|
} |
787 |
|
|
788 |
|
print("$$ processArgs returning \$userId: $userId.\n") if $debug; |
789 |
|
return $userId; |
790 |
|
|
791 |
|
} |
792 |
|
|
793 |
|
|
794 |
|
# ---------------------------------------------------------------------------- |
795 |
|
# --------------------- Check all modules in list for either file or directory |
796 |
|
# ---------------------------------------------------------------------------- |
797 |
|
sub checkFileness { |
798 |
|
|
799 |
|
# Module patterns on the 'cvsacl' record can be files or directories. |
800 |
|
# If it's a directory, we pattern-match the directory name from 'cvsacl' |
801 |
|
# against the left side of the committed filename to see if the file is in |
802 |
|
# that hierarchy. By contrast, files use an explicit match. If the entries |
803 |
|
# are neither files nor directories, then the cvsacl file has been set up |
804 |
|
# incorrectly; we return a "" and the caller skips that line as invalid. |
805 |
|
# |
806 |
|
# This function determines whether the entries on the 'cvsacl' record are all |
807 |
|
# directories or all files; it cannot be a mixture. This restriction put in |
808 |
|
# to simplify the logic (without taking away much functionality). |
809 |
|
|
810 |
|
my @module_list = @_; |
811 |
|
print("$$ Checking \"fileness\" or \"dir-ness\" for \@module_list entries.\n") if $debug; |
812 |
|
print("$$ Entries are: ", join("\, ",@module_list), ".\n") if $debug; |
813 |
|
my $filetype = ""; |
814 |
|
for $cvsacl_module (@module_list) { |
815 |
|
my $reposDirName = $cvsroot . '/' . $cvsacl_module; |
816 |
|
my $reposFileName = $reposDirName . "\,v"; |
817 |
|
print("$$ In checkFileness: \$reposDirName: $reposDirName; \$reposFileName: $reposFileName.\n") if $debug; |
818 |
|
if (((-d $reposDirName) && ($filetype eq "file")) || ((-f $reposFileName) && ($filetype eq "dir"))) { |
819 |
|
print("Can\'t mix files and directories on single \'cvsacl\' file record; skipping entry.\n"); |
820 |
|
print(" Please contact a CVS administrator.\n"); |
821 |
|
$filetype = ""; |
822 |
|
last; |
823 |
|
} |
824 |
|
elsif (-d $reposDirName) { |
825 |
|
$filetype = "dir"; |
826 |
|
print("$$ $reposDirName is a directory.\n") if $debug; |
827 |
|
} |
828 |
|
elsif (-f $reposFileName) { |
829 |
|
$filetype = "file"; |
830 |
|
print("$$ $reposFileName is a regular file.\n") if $debug; |
831 |
|
} |
832 |
|
else { |
833 |
|
print("***** Item to commit was neither a regular file nor a directory.\n"); |
834 |
|
print("***** Current \'cvsacl\' line ignored.\n"); |
835 |
|
print("***** Possible problem with \'cvsacl\' admin file. Please contact a CVS administrator.\n"); |
836 |
|
$filetype = ""; |
837 |
|
$text = sprintf("Module entry on cvsacl line: %s is not a valid file or directory.\n", $cvsacl_module); |
838 |
|
write_restrictlog_record($text); |
839 |
|
last; |
840 |
|
} # end if |
841 |
|
} # end for |
842 |
|
|
843 |
|
print("$$ checkFileness will return \$filetype: $filetype.\n") if $debug; |
844 |
|
return $filetype; |
845 |
|
} |
846 |
|
|
847 |
|
|
848 |
|
# ---------------------------------------------------------------------------- |
849 |
|
# ----------------------------------------------------- check for module match |
850 |
|
# ---------------------------------------------------------------------------- |
851 |
|
sub checkModuleMatch { |
852 |
|
|
853 |
|
# This subroutine checks for a match between the directory or file pattern |
854 |
|
# specified in the 'cvsacl' file (i.e., $cvsacl_modules) versus the commit file |
855 |
|
# objects passed into the script via @ARGV (i.e., $commit_object). |
856 |
|
|
857 |
|
# The directory pattern only has to match the beginning portion of the commit |
858 |
|
# file's name for a match since all files under that directory are considered |
859 |
|
# a match. File patterns must exactly match. |
860 |
|
|
861 |
|
# Since (theoretically, if not normally in practice) a working directory can |
862 |
|
# contain a mixture of files from different branches, this routine checks to |
863 |
|
# see if there is also a match on branch before considering the file |
864 |
|
# comparison a match. |
865 |
|
|
866 |
|
my $match_flag = ""; |
867 |
|
|
868 |
|
print("$$ \@_ in checkModuleMatch is: @_.\n") if $debug; |
869 |
|
my ($type,$commit_object,$cvsacl_module) = @_; |
870 |
|
|
871 |
|
if ($type eq "file") { # Do exact file match of $commit_object |
872 |
|
if ($commit_object eq $cvsacl_module) { |
873 |
|
$match_flag = "file"; |
874 |
|
} # Do dir match at beginning of $commit_object |
875 |
|
} |
876 |
|
elsif ($commit_object =~ /^$cvsacl_module\//) { |
877 |
|
$match_flag = "dir"; |
878 |
|
} |
879 |
|
|
880 |
|
if ($match_flag) { |
881 |
|
print("$$ \$repository: $repository matches \$commit_object: $commit_object.\n") if $debug; |
882 |
|
if (!$cvsacl_branches) { # empty branch pattern matches all |
883 |
|
print("$$ blank \'cvsacl\' branch matches all commit files.\n") if $debug; |
884 |
|
$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module]; |
885 |
|
print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module].\n") if $debug; |
886 |
|
} |
887 |
|
else { # otherwise check branch hash table |
888 |
|
@branch_list = split (/[\s,]+/,$cvsacl_branches); |
889 |
|
print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug; |
890 |
|
if (grep(/$branch{$commit_object}/, @branch_list)) { |
891 |
|
$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module]; |
892 |
|
print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, " . |
893 |
|
"$cvsacl_module].\n") if $debug; |
894 |
|
} |
895 |
|
} |
896 |
|
} |
897 |
|
|
898 |
|
} |
899 |
|
|
900 |
|
# ---------------------------------------------------------------------------- |
901 |
|
# ------------------------------------------------------- check for file match |
902 |
|
# ---------------------------------------------------------------------------- |
903 |
|
sub printOptionalRestrictionMessage { |
904 |
|
|
905 |
|
# This subroutine optionally prints site-specific file restriction information |
906 |
|
# whenever a restriction condition is met. If the file 'restrict_msg' does |
907 |
|
# not exist, the routine immediately exits. If there is a 'restrict_msg' file |
908 |
|
# then all the contents are printed at the end of the standard restriction |
909 |
|
# message. |
910 |
|
|
911 |
|
# As seen from examining the definition of $restrictfile, the default filename |
912 |
|
# is: $CVSROOT/CVSROOT/restrict_msg. |
913 |
|
|
914 |
|
open (RESTRICT, $restrictfile) || return; # It is ok for cvsacl file not to exist |
915 |
|
while (<RESTRICT>) { |
916 |
|
chop; |
917 |
|
# print out each line |
918 |
|
print("**** $_\n"); |
919 |
|
} |
920 |
|
|
921 |
|
} |
922 |
|
|
923 |
|
# ---------------------------------------------------------------------------- |
924 |
|
# ---------------------------------------------------------- write log message |
925 |
|
# ---------------------------------------------------------------------------- |
926 |
|
sub write_restrictlog { |
927 |
|
|
928 |
|
# This subroutine iterates through the list of restricted entries and logs |
929 |
|
# each one to the error logfile. |
930 |
|
|
931 |
|
# write each line in @text out separately |
932 |
|
foreach $commitfile (keys %restricted_entries) { |
933 |
|
$log_text = sprintf "Commit attempt by: %s for: %s on branch: %s", |
934 |
|
$user_name, $commitfile, $branch{$commitfile}; |
935 |
|
write_restrictlog_record($log_text); |
936 |
|
} |
937 |
|
|
938 |
|
} |
939 |
|
|
940 |
|
# ---------------------------------------------------------------------------- |
941 |
|
# ---------------------------------------------------------- write log message |
942 |
|
# ---------------------------------------------------------------------------- |
943 |
|
sub write_restrictlog_record { |
944 |
|
|
945 |
|
# This subroutine receives a scalar string and writes it out to the |
946 |
|
# $restrictlog file as a separate line. Each line is prepended with the date |
947 |
|
# and time in the format: "2004/01/30 12:00:00 ". |
948 |
|
|
949 |
|
$text = shift; |
950 |
|
|
951 |
|
# return quietly if there is a problem opening the log file. |
952 |
|
open(FILE, ">>$restrictlog") || return; |
953 |
|
|
954 |
|
(@time) = localtime(); |
955 |
|
|
956 |
|
# write each line in @text out separately |
957 |
|
$log_record = sprintf "%04d/%02d/%02d %02d:%02d:%02d %s.\n", |
958 |
|
$time[5]+1900, $time[4]+1, $time[3], $time[2], $time[1], $time[0], $text; |
959 |
|
print FILE $log_record; |
960 |
|
print("$$ restrict_log record being written: $log_record to $restrictlog.\n") if $debug; |
961 |
|
|
962 |
|
close(FILE); |
963 |
|
} |