4 # Copyright (C) 1994-2005 The Free Software Foundation, Inc.
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)
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 ###############################################################################
17 ###############################################################################
18 ###############################################################################
20 # THIS SCRIPT IS PROBABLY BROKEN. REMOVING THE -T SWITCH ON THE #! LINE ABOVE
21 # 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.
27 # For more on general Perl security and taint-checking, please try running the
28 # `perldoc perlsec' command.
30 ###############################################################################
31 ###############################################################################
32 ###############################################################################
36 cvs_acls - Access Control List for CVS
42 repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER][-f <logfile>]
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
52 {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
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
61 See below for examples.
65 cvs_acls - provides access control list functionality for CVS
67 Copyright (c) 2004 by Peter Connolly <peter.connolly@cnet.com>
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.
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.
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
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.
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
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
103 The script is triggered from the 'commitinfo' file in $CVSROOT/CVSROOT/.
107 This section lists the bug fixes and enhancements added to cvs_acls
108 that make up the current cvs_acls.
112 This version attempts to get rid the following bugs from the
113 original version of cvs_acls:
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.
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.]
128 Handle specific filename restrictions. cvs_acls didn't restrict
129 individual files specified in 'cvsacl'.
132 Correctly handle multiple, specific filename restrictions
135 Prohibit mix of dirs and files on a single 'cvsacl' line
136 [To simplify the logic and because this would be normal usage.]
139 Correctly handle a mixture of branch restrictions within one work
143 $CVSROOT existence is checked too late
146 Correctly handle the CVSROOT=:local:/... option (useful for
150 Replacing shoddy "$universal_off" logic
151 (Thanks to Karl-Konig Konigsson for pointing this out.)
160 Checks modules in the 'cvsacl' file for valid files and directories
163 Accurately report restricted entries and their matching patterns
166 Simplified and commented overly complex PERL REGEXPs for readability
170 Skip the rest of processing if a mismatch on portion of the 'cvsacl' line
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.
177 Add optional 'restrict_msg' file for additional, site-specific
178 restriction messages.
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').
185 (See discussion below on "Admin Setup" for more on this point.)
188 Added a lot more debug trace
191 Tested these restrictions with concurrent use of pserver and SSH
192 access to model our transition from pserver to ext access.
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.
207 Need to deal with pserver/SSH transition with conflicting umasks?
210 Use a CPAN module to handle command parameters.
213 Use a CPAN module to clone data structures.
217 =head1 Version Information
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.
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.
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.)
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
238 $Id: cvs_acls.in,v 1.1.1.1 2009/04/07 22:10:10 christos Exp $
240 This version is based on the 1.11.13 version of cvs_acls
241 peter.connolly@cnet.com (Peter Connolly)
243 Access control lists for CVS. dgg@ksr.com (David G. Grubbs)
244 Branch specific controls added by voisine@bytemobile.com (Aaron Voisine)
248 To use this program, do the following four things:
250 0. Install PERL, version 5.6.1 or 5.8.0.
254 There are two choices here.
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.
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...
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.
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]
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
293 2. Put two lines, as the *only* non-comment lines, in your commitinfo file:
295 ALL $CVSROOT/CVSROOT/commit_prep
296 ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f <logfilename>]
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.
303 (These are handled in the processArgs() subroutine.)
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.
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.
316 3. Install this file as $CVSROOT/CVSROOT/cvs_acls and make it executable.
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:
322 $ cvs checkout CVSROOT
324 [ create the cvsacl file, include 'commitinfo' line ]
325 [ add cvsacl to checkoutlist ]
327 $ cvs commit -m 'Added cvsacl for use with cvs_acls.' cvsacl checkoutlist
329 Note: The format of the 'cvsacl' file is described in detail immediately
330 below but here is an important set up point:
332 Make sure to include a line like the following:
334 deny||CVSROOT/commitinfo CVSROOT/cvsacl
335 allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl
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'.)
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
349 =head1 Format of the cvsacl file
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
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.
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.
365 Lines beginning with "allow" or "deny" are assumed to be '|'-separated
366 triples: (All spaces and tabs are ignored in a line.)
368 {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
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
377 4. Optional, comma-separated list of branch tags.
378 If not specified, all branches are assumed. Use HEAD to reference the
381 Example: (Note: No in-line comments.)
383 # ----- Make whole repository unavailable.
386 # ----- Except for user "dgg".
389 # ----- Except when "fred" or "john" commit to the
390 # module whose repository is "bin/ls"
391 allow|fred, john|bin/ls
393 # ----- Except when "ed" commits to the "stable"
394 # branch of the "bin/ls" repository
395 allow|ed|/bin/ls|stable
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.
403 The script walks through the 'cvsacl' file looking for matches on
404 the username, repository and branch.
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.
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.)
420 If neither, then assume the 'cvsacl' file was set up in error and
421 skip that 'allow' line.
424 If a dir, then each dir pattern is matched separately against the
425 beginning of each of the committed files in @ARGV.
428 If a file, then each file pattern is matched exactly against each
429 of the files to be committed in @ARGV.
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
437 A branch match is either:
442 When no branches are listed in the fourth column. ("Match any.")
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.
451 'allow' match remove that match from the tally map.
454 Restricted ('deny') matches are saved in the %repository_matches
458 If there is a match on user, repository and branch:
460 If repository, branch and user match
462 add %repository_matches entries to %restricted_entries
464 remove %repository_matches entries from %restricted_entries
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
475 read CVS/Entries file and create branch{file}->{branch} hash table
476 + for each 'allow' and 'deny' line in the 'cvsacl' file:
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
485 | remove %repository_matches <- %restricted_entries
487 any saved restrictions?
489 set exit code allowing commits and exit
490 yes: report restrictions,
491 set exit code prohibiting commits and exit
495 1) file allow trumps a dir deny
497 allow||java/lib/README
498 2) dir allow can undo a file deny
499 deny||java/lib/README
501 3) file deny trumps a dir allow
503 deny||java/lib/README
504 4) dir deny trumps a file allow
505 allow||java/lib/README
507 ... so last match always takes precedence
511 $debug = 0; # Set to 1 for debug messages
513 %repository_matches = (); # hash of match file and pattern from 'cvsacl'
514 # repository_matches --> [branch, matching-pattern]
515 # (Used during module/branch matching loop)
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.)
522 %branch; # hash table of key: commit file; value: branch
523 # Built from ".../CVS/Entries" file of directory
524 # currently being examined
526 # ---------------------------------------------------------------- get CVSROOT
527 $cvsroot = $ENV{'CVSROOT'};
528 die "Must set CVSROOT\n" if !$cvsroot;
529 if ($cvsroot =~ /:([\/\w]*)$/) { # Filter ":pserver:", ":local:"-type prefixes
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";
539 # --------------------------------------------------------------- process args
540 $user_name = processArgs(\@ARGV);
542 print("$$ \@ARGV after processArgs is: @ARGV.\n") if $debug;
543 print("$$ ========== Begin $PROGRAM_NAME for \"$ARGV[0]\" repository. ========== \n") if $debug;
545 # --------------------------------------------------------------- filter @ARGV
546 eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
547 while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
548 exit 255 if $die; # process any variable=value switches
550 print("$$ \@ARGV after shift processing contains:",join("\, ",@ARGV),".\n") if $debug;
552 # ---------------------------------------------------------------- get cvsroot
553 ($repository = shift) =~ s:^$cvsroot/::;
554 grep($_ = $repository . '/' . $_, @ARGV);
556 print("$$ \$cvsroot is: $cvsroot.\n") if $debug;
557 print "$$ Repos: $repository\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;
559 $exit_val = 0; # presume good exit value for commit
561 # ----------------------------------------------------------------------------
562 # ---------------------------------- create hash table $branch{file -> branch}
563 # ----------------------------------------------------------------------------
565 # Here's a typical Entries file:
567 # /checkoutlist/1.4/Wed Feb 4 23:51:23 2004//
568 # /cvsacl/1.3/Tue Feb 24 23:05:43 2004//
570 # /verifymsg/1.1/Fri Mar 16 19:56:24 2001//
574 open(ENTRIES, $entries) || die("Cannot open $entries.\n");
575 print("$$ File / Branch\n") if $debug;
579 next if /^\s*$/; # Skip blank lines
583 ([\w.-]*) # file name -> $1
594 $branch{$repository . '/' . $1} = ($2) ? $2 : "HEAD";
595 print "$$ CVS Entry $i: $1/$2\n" if $debug;
600 # ----------------------------------------------------------------------------
601 # ------------------------------------- evaluate each active line from 'cvsacl'
602 # ----------------------------------------------------------------------------
603 open (CVSACL, $cvsaclfile) || exit(0); # It is ok for cvsacl file not to exist
606 next if /^\s*\#/; # skip comments
607 next if /^\s*$/; # skip blank lines
608 # --------------------------------------------- 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,]*/, $_);
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);
620 # -------------------------------------------------- init loop match flags
622 %repository_matches = ();
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;
631 next; # no match, skip to next 'cvsacl' line
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;
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;
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
665 if (($fileType = checkFileness(@module_list)) eq "") {
666 next; # skip bad file types
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
682 print("$$ Matches for: \%repository_matches: ", join("\, ", (keys %repository_matches)), ".\n") if $debug;
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];
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};
705 print "$$ ==== End of processing for \'cvsacl\' line: $_.\n" if $debug;
709 # ----------------------------------------------------------------------------
710 # --------------------------------------- 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();
721 elsif (!$exit_val && $debug) {
722 print "**** Access allowed: Sufficient authority for commit.\n";
725 print "$$ ==== \$exit_val = $exit_val\n" if $debug;
728 # ----------------------------------------------------------------------------
729 # -------------------------------------------------------------- end of "main"
730 # ----------------------------------------------------------------------------
733 # ----------------------------------------------------------------------------
734 # -------------------------------------------------------- process script args
735 # ----------------------------------------------------------------------------
738 # This subroutine is passed a reference to @ARGV.
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.)
745 # Otherwise, pull the userId from the server-side environment.
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];
754 print("$$ \@_ to processArgs is: @_.\n") if $debug;
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;
763 print("$$ \$debug flag set on.\n") if $debug;
764 print STDERR "Debug turned on...\n";
766 # Passing in a client-side userId?
767 elsif ($arg eq '-u') {
769 $userId = shift @ARGV;
770 print("$$ client-side \$userId set to: $userId.\n") if $debug;
772 # An override for the default restrictlog file?
773 elsif ($arg eq '-f') {
775 $restrictlog = shift @ARGV;
782 # No client-side userId passed? then get from server env
784 $userId = $ENV{"USER"} if !($userId = $ENV{"LOGNAME"});
785 print("$$ server-side \$userId set to: $userId.\n") if $debug;
788 print("$$ processArgs returning \$userId: $userId.\n") if $debug;
794 # ----------------------------------------------------------------------------
795 # --------------------- Check all modules in list for either file or directory
796 # ----------------------------------------------------------------------------
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.
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).
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;
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");
824 elsif (-d $reposDirName) {
826 print("$$ $reposDirName is a directory.\n") if $debug;
828 elsif (-f $reposFileName) {
830 print("$$ $reposFileName is a regular file.\n") if $debug;
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");
837 $text = sprintf("Module entry on cvsacl line: %s is not a valid file or directory.\n", $cvsacl_module);
838 write_restrictlog_record($text);
843 print("$$ checkFileness will return \$filetype: $filetype.\n") if $debug;
848 # ----------------------------------------------------------------------------
849 # ----------------------------------------------------- check for module match
850 # ----------------------------------------------------------------------------
851 sub checkModuleMatch {
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).
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.
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.
868 print("$$ \@_ in checkModuleMatch is: @_.\n") if $debug;
869 my ($type,$commit_object,$cvsacl_module) = @_;
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
876 elsif ($commit_object =~ /^$cvsacl_module\//) {
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;
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;
900 # ----------------------------------------------------------------------------
901 # ------------------------------------------------------- check for file match
902 # ----------------------------------------------------------------------------
903 sub printOptionalRestrictionMessage {
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
911 # As seen from examining the definition of $restrictfile, the default filename
912 # is: $CVSROOT/CVSROOT/restrict_msg.
914 open (RESTRICT, $restrictfile) || return; # It is ok for cvsacl file not to exist
917 # print out each line
923 # ----------------------------------------------------------------------------
924 # ---------------------------------------------------------- write log message
925 # ----------------------------------------------------------------------------
926 sub write_restrictlog {
928 # This subroutine iterates through the list of restricted entries and logs
929 # each one to the error logfile.
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);
940 # ----------------------------------------------------------------------------
941 # ---------------------------------------------------------- write log message
942 # ----------------------------------------------------------------------------
943 sub write_restrictlog_record {
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 ".
951 # return quietly if there is a problem opening the log file.
952 open(FILE, ">>$restrictlog") || return;
954 (@time) = localtime();
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;