4 ###############################################################################
5 ###############################################################################
6 ###############################################################################
8 # THIS SCRIPT IS PROBABLY BROKEN. REMOVING THE -T SWITCH ON THE #! LINE ABOVE
9 # WOULD FIX IT, BUT THIS IS INSECURE. WE RECOMMEND FIXING THE ERRORS WHICH THE
10 # -T SWITCH WILL CAUSE PERL TO REPORT BEFORE RUNNING THIS SCRIPT FROM A CVS
11 # SERVER TRIGGER. PLEASE SEND PATCHES CONTAINING THE CHANGES YOU FIND
12 # NECESSARY TO RUN THIS SCRIPT WITH THE TAINT-CHECKING ENABLED BACK TO THE
13 # <bug-cvs@gnu.org> MAILING LIST.
15 # For more on general Perl security and taint-checking, please try running the
16 # `perldoc perlsec' command.
18 ###############################################################################
19 ###############################################################################
20 ###############################################################################
24 cvs_acls - Access Control List for CVS
30 repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER][-f <logfile>]
34 -d turns on debug information
35 -u passes the client-side userId to the cvs_acls script
36 -f specifies an alternate filename for the restrict_log file
40 {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
44 allow|deny - allow: commits are allowed; deny: prohibited
45 user - userId to be allowed or restricted
46 repos - file or directory to be allowed or restricted
47 branch - branch to be allowed or restricted
49 See below for examples.
53 cvs_acls - provides access control list functionality for CVS
55 Copyright (c) 2004 by Peter Connolly <peter.connolly@cnet.com>
58 This program is free software; you can redistribute it and/or modify
59 it under the terms of the GNU General Public License as published by
60 the Free Software Foundation; either version 2 of the License, or
61 (at your option) any later version.
63 This program is distributed in the hope that it will be useful,
64 but WITHOUT ANY WARRANTY; without even the implied warranty of
65 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66 GNU General Public License for more details.
68 You should have received a copy of the GNU General Public License
69 along with this program; if not, write to the Free Software
70 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
74 This script--cvs_acls--is invoked once for each directory within a
75 "cvs commit". The set of files being committed for that directory as
76 well as the directory itself, are passed to this script. This script
77 checks its 'cvsacl' file to see if any of the files being committed
78 are on the 'cvsacl' file's restricted list. If any of the files are
79 restricted, then the cvs_acls script passes back an exit code of 1
80 which disallows the commits for that directory.
82 Messages are returned to the committer indicating the file(s) that
83 he/she are not allowed to committ. Additionally, a site-specific
84 set of messages (e.g., contact information) can be included in these
87 When a commit is prohibited, log messages are written to a restrict_log
88 file in $CVSROOT/CVSROOT. This default file can be redirected to
91 The script is triggered from the 'commitinfo' file in $CVSROOT/CVSROOT/.
95 This section lists the bug fixes and enhancements added to cvs_acls
96 that make up the current cvs_acls.
100 This version attempts to get rid the following bugs from the
101 original version of cvs_acls:
106 Multiple entries on an 'cvsacl' line will be matched individually,
107 instead of requiring that all commit files *exactly* match all
108 'cvsacl' entries. Commiting a file not in the 'cvsacl' list would
109 allow *all* files (including a restricted file) to be committed.
111 [IMO, this basically made the original script unuseable for our
112 situation since any arbitrary combination of committed files could
113 avoid matching the 'cvsacl's entries.]
116 Handle specific filename restrictions. cvs_acls didn't restrict
117 individual files specified in 'cvsacl'.
120 Correctly handle multiple, specific filename restrictions
123 Prohibit mix of dirs and files on a single 'cvsacl' line
124 [To simplify the logic and because this would be normal usage.]
127 Correctly handle a mixture of branch restrictions within one work
131 $CVSROOT existence is checked too late
134 Correctly handle the CVSROOT=:local:/... option (useful for
138 Replacing shoddy "$universal_off" logic
139 (Thanks to Karl-Konig Konigsson for pointing this out.)
148 Checks modules in the 'cvsacl' file for valid files and directories
151 Accurately report restricted entries and their matching patterns
154 Simplified and commented overly complex PERL REGEXPs for readability
158 Skip the rest of processing if a mismatch on portion of the 'cvsacl' line
161 Get rid of opaque "karma" messages in favor of user-friendly messages
162 that describe which user, file(s) and branch(es) were disallowed.
165 Add optional 'restrict_msg' file for additional, site-specific
166 restriction messages.
169 Take a "-u" parameter for $USER from commit_prep so that the script
170 can do restrictions based on the client-side userId rather than the
171 server-side userId (usually 'cvs').
173 (See discussion below on "Admin Setup" for more on this point.)
176 Added a lot more debug trace
179 Tested these restrictions with concurrent use of pserver and SSH
180 access to model our transition from pserver to ext access.
183 Added logging of restricted commit attempts.
184 Restricted commits can be sent to a default file:
185 $CVSROOT/CVSROOT/restrictlog or to one passed to the script
186 via the -f command parameter.
195 Need to deal with pserver/SSH transition with conflicting umasks?
198 Use a CPAN module to handle command parameters.
201 Use a CPAN module to clone data structures.
205 =head1 Version Information
207 This is not offered as a fix to the original 'cvs_acls' script since it
208 differs substantially in goals and methods from the original and there
209 are probably a significant number of people out there that still require
210 the original version's functionality.
212 The 'cvsacl' file flags of 'allow' and 'deny' were intentionally
213 changed to 'allow' and 'deny' because there are enough differences
214 between the original script's behavior and this one's that we wanted to
215 make sure that users will rethink their 'cvsacl' file formats before
216 plugging in this newer script.
218 Please note that there has been very limited cross-platform testing of
219 this script!!! (We did not have the time or resources to do exhaustive
220 cross-platform testing.)
222 It was developed and tested under Red Hat Linux 9.0 using PERL 5.8.0.
223 Additionally, it was built and tested under Red Hat Linux 7.3 using
226 $Id: cvs_acls.in,v 1.7 2005/04/14 15:45:00 dprice Exp $
228 This version is based on the 1.11.13 version of cvs_acls
229 peter.connolly@cnet.com (Peter Connolly)
231 Access control lists for CVS. dgg@ksr.com (David G. Grubbs)
232 Branch specific controls added by voisine@bytemobile.com (Aaron Voisine)
236 To use this program, do the following four things:
238 0. Install PERL, version 5.6.1 or 5.8.0.
242 There are two choices here.
244 a) The first option is to use the $ENV{"USER"}, server-side userId
245 (from the third column of your pserver 'passwd' file) as the basis for
246 your restrictions. In this case, you will (at a minimum) want to set
247 up a new "cvsadmin" userId and group on the pserver machine.
248 CVS administrators will then set up their 'passwd' file entries to
249 run either as "cvs" (for regular users) or as "cvsadmin" (for power
250 users). Correspondingly, your 'cvsacl' file will only list 'cvs'
251 and 'cvsadmin' as the userIds in the second column.
253 Commentary: A potential weakness of this is that the xinetd
254 cvspserver process will need to run as 'root' in order to switch
255 between the 'cvs' and the 'cvsadmin' userIds. Some sysadmins don't
256 like situations like this and may want to chroot the process.
257 Talk to them about this point...
259 b) The second option is to use the client-side userId as the basis for
260 your restrictions. In this case, all the xinetd cvspserver processes
261 can run as userId 'cvs' and no 'root' userId is required. If you have
262 a 'passwd' file that lists 'cvs' as the effective run-time userId for
263 all your users, then no changes to this file are needed. Your 'cvsacl'
264 file will use the individual, client-side userIds in its 2nd column.
266 As long as the userIds in pserver's 'passwd' file match those userIds
267 that your Linux server know about, this approach is ideal if you are
268 planning to move from pserver to SSH access at some later point in time.
269 Just by switching the CVSROOT var from CVSROOT=:pserver:<userId>... to
270 CVSROOT=:ext:<userId>..., users can switch over to SSH access without
271 any other administrative changes. When all users have switched over to
272 SSH, the inherently insecure xinetd cvspserver process can be disabled.
273 [https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_2.html#SEC32]
275 :TODO: The only potential glitch with the SSH approach is the possibility
276 that each user can have differing umasks that might interfere with one
277 another, especially during a transition from pserver to SSH. As noted
278 in the ToDo section, this needs a good strategy and set of tests for that
281 2. Put two lines, as the *only* non-comment lines, in your commitinfo file:
283 ALL $CVSROOT/CVSROOT/commit_prep
284 ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f <logfilename>]
286 where "-d" turns on debug trace
287 "-u $USER" passes the client-side userId to cvs_acls
288 "-f <logfilename"> overrides the default filename used to log
289 restricted commit attempts.
291 (These are handled in the processArgs() subroutine.)
293 If you are using client-side userIds to restrict access to your
294 repository, make sure that they are in this order since the commit_prep
295 script is required in order to pass the $USER parameter.
297 A final note about the repository matching pattern. The example above
298 uses "ALL" but note that this means that the cvs_acls script will run
299 for each and every commit in your repository. Obviously, in a large
300 repository this adds up to a lot of overhead that may not be necesary.
301 A better strategy is to use a repository pattern that is more specific
302 to the areas that you wish to secure.
304 3. Install this file as $CVSROOT/CVSROOT/cvs_acls and make it executable.
306 4. Create a file named CVSROOT/cvsacl and optionally add it to
307 CVSROOT/checkoutlist and check it in. See the CVS manual's
308 administrative files section about checkoutlist. Typically:
310 $ cvs checkout CVSROOT
312 [ create the cvsacl file, include 'commitinfo' line ]
313 [ add cvsacl to checkoutlist ]
315 $ cvs commit -m 'Added cvsacl for use with cvs_acls.' cvsacl checkoutlist
317 Note: The format of the 'cvsacl' file is described in detail immediately
318 below but here is an important set up point:
320 Make sure to include a line like the following:
322 deny||CVSROOT/commitinfo CVSROOT/cvsacl
323 allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl
325 that restricts access to commitinfo and cvsacl since this would be one of
326 the easiest "end runs" around this ACL approach. ('commitinfo' has the
327 line that executes the cvs_acls script and, of course, all the
328 restrictions are in 'cvsacl'.)
330 5. (Optional) Create a 'restrict_msg' file in the $CVSROOT/CVSROOT directory.
331 Whenever there is a restricted file or dir message, cvs_acls will look
332 for this file and, if it exists, print its contents as part of the
333 commit-denial message. This gives you a chance to print any site-specific
334 information (e.g., who to call, what procedures to look up,...) whenever
337 =head1 Format of the cvsacl file
339 The 'cvsacl' file determines whether you may commit files. It contains lines
340 read from top to bottom, keeping track of whether a given user, repository
341 and branch combination is "allowed" or "denied." The script will assume
342 "allowed" on all repository paths until 'allow' and 'deny' rules change
345 The normal pattern is to specify an 'deny' rule to turn off
346 access to ALL users, then follow it with a matching 'allow' rule that will
347 turn on access for a select set of users. In the case of multiple rules for
348 the same user, repository and branch, the last one takes precedence.
350 Blank lines and lines with only comments are ignored. Any other lines not
351 beginning with "allow" or "deny" are logged to the restrict_log file.
353 Lines beginning with "allow" or "deny" are assumed to be '|'-separated
354 triples: (All spaces and tabs are ignored in a line.)
356 {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
358 1. String starting with "allow" or "deny".
359 2. Optional, comma-separated list of usernames.
360 3. Optional, comma-separated list of repository pathnames.
361 These are pathnames relative to $CVSROOT. They can be directories or
362 filenames. A directory name allows or restricts access to all files and
363 directories below it. One line can have either directories or filenames
365 4. Optional, comma-separated list of branch tags.
366 If not specified, all branches are assumed. Use HEAD to reference the
369 Example: (Note: No in-line comments.)
371 # ----- Make whole repository unavailable.
374 # ----- Except for user "dgg".
377 # ----- Except when "fred" or "john" commit to the
378 # module whose repository is "bin/ls"
379 allow|fred, john|bin/ls
381 # ----- Except when "ed" commits to the "stable"
382 # branch of the "bin/ls" repository
383 allow|ed|/bin/ls|stable
387 CVS passes to @ARGV an absolute directory pathname (the repository
388 appended to your $CVSROOT variable), followed by a list of filenames
389 within that directory that are to be committed.
391 The script walks through the 'cvsacl' file looking for matches on
392 the username, repository and branch.
394 A username match is simply the user's name appearing in the second
395 column of the cvsacl line in a space-or-comma separate list. If
396 blank, then any user will match.
403 Each entry in the modules section of the current 'cvsacl' line is
404 examined to see if it is a dir or a file. The line must have
405 either files or dirs, but not both. (To simplify the logic.)
408 If neither, then assume the 'cvsacl' file was set up in error and
409 skip that 'allow' line.
412 If a dir, then each dir pattern is matched separately against the
413 beginning of each of the committed files in @ARGV.
416 If a file, then each file pattern is matched exactly against each
417 of the files to be committed in @ARGV.
420 Repository and branch must BOTH match together. This is to cover
421 the use case where a user has multiple branches checked out in
422 a single work directory. Commit files can be from different
425 A branch match is either:
430 When no branches are listed in the fourth column. ("Match any.")
433 All elements from the fourth column are matched against each of
434 the tag names for $ARGV[1..$#ARGV] found in the %branches file.
439 'allow' match remove that match from the tally map.
442 Restricted ('deny') matches are saved in the %repository_matches
446 If there is a match on user, repository and branch:
448 If repository, branch and user match
450 add %repository_matches entries to %restricted_entries
452 remove %repository_matches entries from %restricted_entries
455 At the end of all the 'cvsacl' line checks, check to see if there
456 are any entries in the %restricted_entries. If so, then deny the
463 read CVS/Entries file and create branch{file}->{branch} hash table
464 + for each 'allow' and 'deny' line in the 'cvsacl' file:
466 | - Yes: set $user_match = 1;
467 | repository and branch match?
468 | - Yes: add to %repository_matches;
469 | did user, repository match?
470 | - Yes: if 'deny' then
471 | add %repository_matches -> %restricted_entries
473 | remove %repository_matches <- %restricted_entries
475 any saved restrictions?
477 set exit code allowing commits and exit
478 yes: report restrictions,
479 set exit code prohibiting commits and exit
483 1) file allow trumps a dir deny
485 allow||java/lib/README
486 2) dir allow can undo a file deny
487 deny||java/lib/README
489 3) file deny trumps a dir allow
491 deny||java/lib/README
492 4) dir deny trumps a file allow
493 allow||java/lib/README
495 ... so last match always takes precedence
499 $debug = 0; # Set to 1 for debug messages
501 %repository_matches = (); # hash of match file and pattern from 'cvsacl'
502 # repository_matches --> [branch, matching-pattern]
503 # (Used during module/branch matching loop)
505 %restricted_entries = (); # hash table of restricted commit files (from @ARGV)
506 # restricted_entries --> branch
507 # (If user/module/branch all match on an 'deny'
508 # line, then entries added to this map.)
510 %branch; # hash table of key: commit file; value: branch
511 # Built from ".../CVS/Entries" file of directory
512 # currently being examined
514 # ---------------------------------------------------------------- get CVSROOT
515 $cvsroot = $ENV{'CVSROOT'};
516 die "Must set CVSROOT\n" if !$cvsroot;
517 if ($cvsroot =~ /:([\/\w]*)$/) { # Filter ":pserver:", ":local:"-type prefixes
521 # ------------------------------------------------------------- set file paths
522 $entries = "CVS/Entries"; # client-side file???
523 $cvsaclfile = $cvsroot . "/CVSROOT/cvsacl";
524 $restrictfile = $cvsroot . "/CVSROOT/restrict_msg";
525 $restrictlog = $cvsroot . "/CVSROOT/restrict_log";
527 # --------------------------------------------------------------- process args
528 $user_name = processArgs(\@ARGV);
530 print("$$ \@ARGV after processArgs is: @ARGV.\n") if $debug;
531 print("$$ ========== Begin $PROGRAM_NAME for \"$ARGV[0]\" repository. ========== \n") if $debug;
533 # --------------------------------------------------------------- filter @ARGV
534 eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
535 while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
536 exit 255 if $die; # process any variable=value switches
538 print("$$ \@ARGV after shift processing contains:",join("\, ",@ARGV),".\n") if $debug;
540 # ---------------------------------------------------------------- get cvsroot
541 ($repository = shift) =~ s:^$cvsroot/::;
542 grep($_ = $repository . '/' . $_, @ARGV);
544 print("$$ \$cvsroot is: $cvsroot.\n") if $debug;
545 print "$$ Repos: $repository\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;
547 $exit_val = 0; # presume good exit value for commit
549 # ----------------------------------------------------------------------------
550 # ---------------------------------- create hash table $branch{file -> branch}
551 # ----------------------------------------------------------------------------
553 # Here's a typical Entries file:
555 # /checkoutlist/1.4/Wed Feb 4 23:51:23 2004//
556 # /cvsacl/1.3/Tue Feb 24 23:05:43 2004//
558 # /verifymsg/1.1/Fri Mar 16 19:56:24 2001//
562 open(ENTRIES, $entries) || die("Cannot open $entries.\n");
563 print("$$ File / Branch\n") if $debug;
567 next if /^\s*$/; # Skip blank lines
571 ([\w.-]*) # file name -> $1
582 $branch{$repository . '/' . $1} = ($2) ? $2 : "HEAD";
583 print "$$ CVS Entry $i: $1/$2\n" if $debug;
588 # ----------------------------------------------------------------------------
589 # ------------------------------------- evaluate each active line from 'cvsacl'
590 # ----------------------------------------------------------------------------
591 open (CVSACL, $cvsaclfile) || exit(0); # It is ok for cvsacl file not to exist
594 next if /^\s*\#/; # skip comments
595 next if /^\s*$/; # skip blank lines
596 # --------------------------------------------- parse current 'cvsacl' line
597 print("$$ ==========\n$$ Processing \'cvsacl\' line: $_.\n") if $debug;
598 ($cvsacl_flag, $cvsacl_userIds, $cvsacl_modules, $cvsacl_branches) = split(/[\s,]*\|[\s,]*/, $_);
600 # ------------------------------ Validate 'allow' or 'deny' line prefix
601 if ($cvsacl_flag !~ /^allow/ && $cvsacl_flag !~ /^deny/) {
602 print ("Bad cvsacl line: $_\n") if $debug;
603 $log_text = sprintf "Bad cvsacl line: %s", $_;
604 write_restrictlog_record($log_text);
608 # -------------------------------------------------- init loop match flags
610 %repository_matches = ();
612 # ------------------------------------------------------------------------
613 # ---------------------------------------------------------- user matching
614 # ------------------------------------------------------------------------
615 # $user_name considered "in user list" if actually in list or is NULL
616 $user_match = (!$cvsacl_userIds || grep ($_ eq $user_name, split(/[\s,]+/,$cvsacl_userIds)));
617 print "$$ \$user_name: $user_name \$user_match match flag is: $user_match.\n" if $debug;
619 next; # no match, skip to next 'cvsacl' line
622 # ------------------------------------------------------------------------
623 # ---------------------------------------------------- repository matching
624 # ------------------------------------------------------------------------
625 if (!$cvsacl_modules) { # blank module list = all modules
626 if (!$cvsacl_branches) { # blank branch list = all branches
627 print("$$ Adding all modules to \%repository_matches; null " .
628 "\$cvsacl_modules and \$cvsacl_branches.\n") if $debug;
629 for $commit_object (@ARGV) {
630 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules];
631 print("$$ \$repository_matches{$commit_object} = " .
632 "[$branch{$commit_object}, $cvsacl_modules].\n") if $debug;
635 else { # need to check for repository match
636 @branch_list = split (/[\s,]+/,$cvsacl_branches);
637 print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug;
638 for $commit_object (@ARGV) {
639 if (grep($branch{$commit_object}, @branch_list)) {
640 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules];
641 print("$$ \$repository_matches{$commit_object} = " .
642 "[$branch{$commit_object}, $cvsacl_modules].\n") if $debug;
648 # ----------------------------------- check every argument combination
649 # parse 'cvsacl' modules to array
650 my @module_list = split(/[\s,]+/,$cvsacl_modules);
651 # ------------- Check all modules in list for either file or directory
653 if (($fileType = checkFileness(@module_list)) eq "") {
654 next; # skip bad file types
656 # ---------- Check each combination of 'cvsacl' modules vs. @ARGV files
657 print("$$ Checking matches for \@module_list: ", join("\, ",@module_list), ".\n") if $debug;
658 # loop thru all command-line commit objects
659 for $commit_object (@ARGV) {
660 # loop thru all modules on 'cvsacl' line
661 for $cvsacl_module (@module_list) {
662 print("$$ Is \'cvsacl\': $cvsacl_modules pattern in: \@ARGV " .
663 "\$commit_object: $commit_object?\n") if $debug;
664 # Do match of beginning of $commit_object
665 checkModuleMatch($fileType, $commit_object, $cvsacl_module);
666 } # end for commit objects
667 } # end for cvsacl modules
670 print("$$ Matches for: \%repository_matches: ", join("\, ", (keys %repository_matches)), ".\n") if $debug;
672 # ------------------------------------------------------------------------
673 # ----------------------------------------------------- setting exit value
674 # ------------------------------------------------------------------------
675 if ($user_match && %repository_matches) {
676 print("$$ An \"$cvsacl_flag\" match on User(s): $cvsacl_userIds; Module(s):" .
677 " $cvsacl_modules; Branch(es): $cvsacl_branches.\n") if $debug;
678 if ($cvsacl_flag eq "deny") {
679 # Add all matches to the hash of restricted modules
680 foreach $commitFile (keys %repository_matches) {
681 print("$$ Adding \%repository_matches entry: $commitFile.\n") if $debug;
682 $restricted_entries{$commitFile} = $repository_matches{$commitFile}[0];
686 # Remove all matches from the restricted modules hash
687 foreach $commitFile (keys %repository_matches) {
688 print("$$ Removing \%repository_matches entry: $commitFile.\n") if $debug;
689 delete $restricted_entries{$commitFile};
693 print "$$ ==== End of processing for \'cvsacl\' line: $_.\n" if $debug;
697 # ----------------------------------------------------------------------------
698 # --------------------------------------- determine final 'commit' disposition
699 # ----------------------------------------------------------------------------
700 if (%restricted_entries) { # any restricted entries?
701 $exit_val = 1; # don't commit
702 print("**** Access denied: Insufficient authority for user: '$user_name\' " .
703 "to commit to \'$repository\'.\n**** Contact CVS Administrators if " .
704 "you require update access to these directories or files.\n");
705 print("**** file(s)/dir(s) restricted were:\n\t", join("\n\t",keys %restricted_entries), "\n");
706 printOptionalRestrictionMessage();
709 elsif (!$exit_val && $debug) {
710 print "**** Access allowed: Sufficient authority for commit.\n";
713 print "$$ ==== \$exit_val = $exit_val\n" if $debug;
716 # ----------------------------------------------------------------------------
717 # -------------------------------------------------------------- end of "main"
718 # ----------------------------------------------------------------------------
721 # ----------------------------------------------------------------------------
722 # -------------------------------------------------------- process script args
723 # ----------------------------------------------------------------------------
726 # This subroutine is passed a reference to @ARGV.
728 # If @ARGV contains a "-u" entry, use that as the effective userId. In this
729 # case, the userId is the client-side userId that has been passed to this
730 # script by the commit_prep script. (This is why the commit_prep script must
731 # be placed *before* the cvs_acls script in the commitinfo admin file.)
733 # Otherwise, pull the userId from the server-side environment.
736 my ($argv) = shift; # pick up ref to @ARGV
737 my @argvClone = (); # immutable copy for foreach loop
738 for ($i=0; $i<(scalar @{$argv}); $i++) {
739 $argvClone[$i]=$argv->[$i];
742 print("$$ \@_ to processArgs is: @_.\n") if $debug;
744 # Parse command line arguments (file list is seen as one arg)
745 foreach $arg (@argvClone) {
746 print("$$ \$arg for processArgs loop is: $arg.\n") if $debug;
751 print("$$ \$debug flag set on.\n") if $debug;
752 print STDERR "Debug turned on...\n";
754 # Passing in a client-side userId?
755 elsif ($arg eq '-u') {
757 $userId = shift @ARGV;
758 print("$$ client-side \$userId set to: $userId.\n") if $debug;
760 # An override for the default restrictlog file?
761 elsif ($arg eq '-f') {
763 $restrictlog = shift @ARGV;
770 # No client-side userId passed? then get from server env
772 $userId = $ENV{"USER"} if !($userId = $ENV{"LOGNAME"});
773 print("$$ server-side \$userId set to: $userId.\n") if $debug;
776 print("$$ processArgs returning \$userId: $userId.\n") if $debug;
782 # ----------------------------------------------------------------------------
783 # --------------------- Check all modules in list for either file or directory
784 # ----------------------------------------------------------------------------
787 # Module patterns on the 'cvsacl' record can be files or directories.
788 # If it's a directory, we pattern-match the directory name from 'cvsacl'
789 # against the left side of the committed filename to see if the file is in
790 # that hierarchy. By contrast, files use an explicit match. If the entries
791 # are neither files nor directories, then the cvsacl file has been set up
792 # incorrectly; we return a "" and the caller skips that line as invalid.
794 # This function determines whether the entries on the 'cvsacl' record are all
795 # directories or all files; it cannot be a mixture. This restriction put in
796 # to simplify the logic (without taking away much functionality).
798 my @module_list = @_;
799 print("$$ Checking \"fileness\" or \"dir-ness\" for \@module_list entries.\n") if $debug;
800 print("$$ Entries are: ", join("\, ",@module_list), ".\n") if $debug;
802 for $cvsacl_module (@module_list) {
803 my $reposDirName = $cvsroot . '/' . $cvsacl_module;
804 my $reposFileName = $reposDirName . "\,v";
805 print("$$ In checkFileness: \$reposDirName: $reposDirName; \$reposFileName: $reposFileName.\n") if $debug;
806 if (((-d $reposDirName) && ($filetype eq "file")) || ((-f $reposFileName) && ($filetype eq "dir"))) {
807 print("Can\'t mix files and directories on single \'cvsacl\' file record; skipping entry.\n");
808 print(" Please contact a CVS administrator.\n");
812 elsif (-d $reposDirName) {
814 print("$$ $reposDirName is a directory.\n") if $debug;
816 elsif (-f $reposFileName) {
818 print("$$ $reposFileName is a regular file.\n") if $debug;
821 print("***** Item to commit was neither a regular file nor a directory.\n");
822 print("***** Current \'cvsacl\' line ignored.\n");
823 print("***** Possible problem with \'cvsacl\' admin file. Please contact a CVS administrator.\n");
825 $text = sprintf("Module entry on cvsacl line: %s is not a valid file or directory.\n", $cvsacl_module);
826 write_restrictlog_record($text);
831 print("$$ checkFileness will return \$filetype: $filetype.\n") if $debug;
836 # ----------------------------------------------------------------------------
837 # ----------------------------------------------------- check for module match
838 # ----------------------------------------------------------------------------
839 sub checkModuleMatch {
841 # This subroutine checks for a match between the directory or file pattern
842 # specified in the 'cvsacl' file (i.e., $cvsacl_modules) versus the commit file
843 # objects passed into the script via @ARGV (i.e., $commit_object).
845 # The directory pattern only has to match the beginning portion of the commit
846 # file's name for a match since all files under that directory are considered
847 # a match. File patterns must exactly match.
849 # Since (theoretically, if not normally in practice) a working directory can
850 # contain a mixture of files from different branches, this routine checks to
851 # see if there is also a match on branch before considering the file
852 # comparison a match.
856 print("$$ \@_ in checkModuleMatch is: @_.\n") if $debug;
857 my ($type,$commit_object,$cvsacl_module) = @_;
859 if ($type eq "file") { # Do exact file match of $commit_object
860 if ($commit_object eq $cvsacl_module) {
861 $match_flag = "file";
862 } # Do dir match at beginning of $commit_object
864 elsif ($commit_object =~ /^$cvsacl_module\//) {
869 print("$$ \$repository: $repository matches \$commit_object: $commit_object.\n") if $debug;
870 if (!$cvsacl_branches) { # empty branch pattern matches all
871 print("$$ blank \'cvsacl\' branch matches all commit files.\n") if $debug;
872 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module];
873 print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module].\n") if $debug;
875 else { # otherwise check branch hash table
876 @branch_list = split (/[\s,]+/,$cvsacl_branches);
877 print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug;
878 if (grep(/$branch{$commit_object}/, @branch_list)) {
879 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module];
880 print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, " .
881 "$cvsacl_module].\n") if $debug;
888 # ----------------------------------------------------------------------------
889 # ------------------------------------------------------- check for file match
890 # ----------------------------------------------------------------------------
891 sub printOptionalRestrictionMessage {
893 # This subroutine optionally prints site-specific file restriction information
894 # whenever a restriction condition is met. If the file 'restrict_msg' does
895 # not exist, the routine immediately exits. If there is a 'restrict_msg' file
896 # then all the contents are printed at the end of the standard restriction
899 # As seen from examining the definition of $restrictfile, the default filename
900 # is: $CVSROOT/CVSROOT/restrict_msg.
902 open (RESTRICT, $restrictfile) || return; # It is ok for cvsacl file not to exist
905 # print out each line
911 # ----------------------------------------------------------------------------
912 # ---------------------------------------------------------- write log message
913 # ----------------------------------------------------------------------------
914 sub write_restrictlog {
916 # This subroutine iterates through the list of restricted entries and logs
917 # each one to the error logfile.
919 # write each line in @text out separately
920 foreach $commitfile (keys %restricted_entries) {
921 $log_text = sprintf "Commit attempt by: %s for: %s on branch: %s",
922 $user_name, $commitfile, $branch{$commitfile};
923 write_restrictlog_record($log_text);
928 # ----------------------------------------------------------------------------
929 # ---------------------------------------------------------- write log message
930 # ----------------------------------------------------------------------------
931 sub write_restrictlog_record {
933 # This subroutine receives a scalar string and writes it out to the
934 # $restrictlog file as a separate line. Each line is prepended with the date
935 # and time in the format: "2004/01/30 12:00:00 ".
939 # return quietly if there is a problem opening the log file.
940 open(FILE, ">>$restrictlog") || return;
942 (@time) = localtime();
944 # write each line in @text out separately
945 $log_record = sprintf "%04d/%02d/%02d %02d:%02d:%02d %s.\n",
946 $time[5]+1900, $time[4]+1, $time[3], $time[2], $time[1], $time[0], $text;
947 print FILE $log_record;
948 print("$$ restrict_log record being written: $log_record to $restrictlog.\n") if $debug;