6 cvs_acls - Access Control List for CVS
12 repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER][-f <logfile>]
16 -d turns on debug information
17 -u passes the client-side userId to the cvs_acls script
18 -f specifies an alternate filename for the restrict_log file
22 {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
26 allow|deny - allow: commits are allowed; deny: prohibited
27 user - userId to be allowed or restricted
28 repos - file or directory to be allowed or restricted
29 branch - branch to be allowed or restricted
31 See below for examples.
35 cvs_acls - provides access control list functionality for CVS
37 Copyright (c) 2004 by Peter Connolly <peter.connolly@cnet.com>
40 This program is free software; you can redistribute it and/or modify
41 it under the terms of the GNU General Public License as published by
42 the Free Software Foundation; either version 2 of the License, or
43 (at your option) any later version.
45 This program is distributed in the hope that it will be useful,
46 but WITHOUT ANY WARRANTY; without even the implied warranty of
47 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 GNU General Public License for more details.
50 You should have received a copy of the GNU General Public License
51 along with this program; if not, write to the Free Software
52 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
56 This script--cvs_acls--is invoked once for each directory within a
57 "cvs commit". The set of files being committed for that directory as
58 well as the directory itself, are passed to this script. This script
59 checks its 'cvsacl' file to see if any of the files being committed
60 are on the 'cvsacl' file's restricted list. If any of the files are
61 restricted, then the cvs_acls script passes back an exit code of 1
62 which disallows the commits for that directory.
64 Messages are returned to the committer indicating the file(s) that
65 he/she are not allowed to committ. Additionally, a site-specific
66 set of messages (e.g., contact information) can be included in these
69 When a commit is prohibited, log messages are written to a restrict_log
70 file in $CVSROOT/CVSROOT. This default file can be redirected to
73 The script is triggered from the 'commitinfo' file in $CVSROOT/CVSROOT/.
77 This section lists the bug fixes and enhancements added to cvs_acls
78 that make up the current cvs_acls.
82 This version attempts to get rid the following bugs from the
83 original version of cvs_acls:
88 Multiple entries on an 'cvsacl' line will be matched individually,
89 instead of requiring that all commit files *exactly* match all
90 'cvsacl' entries. Commiting a file not in the 'cvsacl' list would
91 allow *all* files (including a restricted file) to be committed.
93 [IMO, this basically made the original script unuseable for our
94 situation since any arbitrary combination of committed files could
95 avoid matching the 'cvsacl's entries.]
98 Handle specific filename restrictions. cvs_acls didn't restrict
99 individual files specified in 'cvsacl'.
102 Correctly handle multiple, specific filename restrictions
105 Prohibit mix of dirs and files on a single 'cvsacl' line
106 [To simplify the logic and because this would be normal usage.]
109 Correctly handle a mixture of branch restrictions within one work
113 $CVSROOT existence is checked too late
116 Correctly handle the CVSROOT=:local:/... option (useful for
120 Replacing shoddy "$universal_off" logic
121 (Thanks to Karl-Konig Konigsson for pointing this out.)
130 Checks modules in the 'cvsacl' file for valid files and directories
133 Accurately report restricted entries and their matching patterns
136 Simplified and commented overly complex PERL REGEXPs for readability
140 Skip the rest of processing if a mismatch on portion of the 'cvsacl' line
143 Get rid of opaque "karma" messages in favor of user-friendly messages
144 that describe which user, file(s) and branch(es) were disallowed.
147 Add optional 'restrict_msg' file for additional, site-specific
148 restriction messages.
151 Take a "-u" parameter for $USER from commit_prep so that the script
152 can do restrictions based on the client-side userId rather than the
153 server-side userId (usually 'cvs').
155 (See discussion below on "Admin Setup" for more on this point.)
158 Added a lot more debug trace
161 Tested these restrictions with concurrent use of pserver and SSH
162 access to model our transition from pserver to ext access.
165 Added logging of restricted commit attempts.
166 Restricted commits can be sent to a default file:
167 $CVSROOT/CVSROOT/restrictlog or to one passed to the script
168 via the -f command parameter.
177 Need to deal with pserver/SSH transition with conflicting umasks?
180 Use a CPAN module to handle command parameters.
183 Use a CPAN module to clone data structures.
187 =head1 Version Information
189 This is not offered as a fix to the original 'cvs_acls' script since it
190 differs substantially in goals and methods from the original and there
191 are probably a significant number of people out there that still require
192 the original version's functionality.
194 The 'cvsacl' file flags of 'allow' and 'deny' were intentionally
195 changed to 'allow' and 'deny' because there are enough differences
196 between the original script's behavior and this one's that we wanted to
197 make sure that users will rethink their 'cvsacl' file formats before
198 plugging in this newer script.
200 Please note that there has been very limited cross-platform testing of
201 this script!!! (We did not have the time or resources to do exhaustive
202 cross-platform testing.)
204 It was developed and tested under Red Hat Linux 9.0 using PERL 5.8.0.
205 Additionally, it was built and tested under Red Hat Linux 7.3 using
208 $Id: cvs_acls.in,v 1.5 2004/09/13 20:20:58 mdb Exp $
210 This version is based on the 1.11.13 version of cvs_acls
211 peter.connolly@cnet.com (Peter Connolly)
213 Access control lists for CVS. dgg@ksr.com (David G. Grubbs)
214 Branch specific controls added by voisine@bytemobile.com (Aaron Voisine)
218 To use this program, do the following four things:
220 0. Install PERL, version 5.6.1 or 5.8.0.
224 There are two choices here.
226 a) The first option is to use the $ENV{"USER"}, server-side userId
227 (from the third column of your pserver 'passwd' file) as the basis for
228 your restrictions. In this case, you will (at a minimum) want to set
229 up a new "cvsadmin" userId and group on the pserver machine.
230 CVS administrators will then set up their 'passwd' file entries to
231 run either as "cvs" (for regular users) or as "cvsadmin" (for power
232 users). Correspondingly, your 'cvsacl' file will only list 'cvs'
233 and 'cvsadmin' as the userIds in the second column.
235 Commentary: A potential weakness of this is that the xinetd
236 cvspserver process will need to run as 'root' in order to switch
237 between the 'cvs' and the 'cvsadmin' userIds. Some sysadmins don't
238 like situations like this and may want to chroot the process.
239 Talk to them about this point...
241 b) The second option is to use the client-side userId as the basis for
242 your restrictions. In this case, all the xinetd cvspserver processes
243 can run as userId 'cvs' and no 'root' userId is required. If you have
244 a 'passwd' file that lists 'cvs' as the effective run-time userId for
245 all your users, then no changes to this file are needed. Your 'cvsacl'
246 file will use the individual, client-side userIds in its 2nd column.
248 As long as the userIds in pserver's 'passwd' file match those userIds
249 that your Linux server know about, this approach is ideal if you are
250 planning to move from pserver to SSH access at some later point in time.
251 Just by switching the CVSROOT var from CVSROOT=:pserver:<userId>... to
252 CVSROOT=:ext:<userId>..., users can switch over to SSH access without
253 any other administrative changes. When all users have switched over to
254 SSH, the inherently insecure xinetd cvspserver process can be disabled.
255 [https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_2.html#SEC32]
257 :TODO: The only potential glitch with the SSH approach is the possibility
258 that each user can have differing umasks that might interfere with one
259 another, especially during a transition from pserver to SSH. As noted
260 in the ToDo section, this needs a good strategy and set of tests for that
263 2. Put two lines, as the *only* non-comment lines, in your commitinfo file:
265 ALL $CVSROOT/CVSROOT/commit_prep
266 ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f <logfilename>]
268 where "-d" turns on debug trace
269 "-u $USER" passes the client-side userId to cvs_acls
270 "-f <logfilename"> overrides the default filename used to log
271 restricted commit attempts.
273 (These are handled in the processArgs() subroutine.)
275 If you are using client-side userIds to restrict access to your
276 repository, make sure that they are in this order since the commit_prep
277 script is required in order to pass the $USER parameter.
279 A final note about the repository matching pattern. The example above
280 uses "ALL" but note that this means that the cvs_acls script will run
281 for each and every commit in your repository. Obviously, in a large
282 repository this adds up to a lot of overhead that may not be necesary.
283 A better strategy is to use a repository pattern that is more specific
284 to the areas that you wish to secure.
286 3. Install this file as $CVSROOT/CVSROOT/cvs_acls and make it executable.
288 4. Create a file named CVSROOT/cvsacl and optionally add it to
289 CVSROOT/checkoutlist and check it in. See the CVS manual's
290 administrative files section about checkoutlist. Typically:
292 $ cvs checkout CVSROOT
294 [ create the cvsacl file, include 'commitinfo' line ]
295 [ add cvsacl to checkoutlist ]
297 $ cvs commit -m 'Added cvsacl for use with cvs_acls.' cvsacl checkoutlist
299 Note: The format of the 'cvsacl' file is described in detail immediately
300 below but here is an important set up point:
302 Make sure to include a line like the following:
304 deny||CVSROOT/commitinfo CVSROOT/cvsacl
305 allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl
307 that restricts access to commitinfo and cvsacl since this would be one of
308 the easiest "end runs" around this ACL approach. ('commitinfo' has the
309 line that executes the cvs_acls script and, of course, all the
310 restrictions are in 'cvsacl'.)
312 5. (Optional) Create a 'restrict_msg' file in the $CVSROOT/CVSROOT directory.
313 Whenever there is a restricted file or dir message, cvs_acls will look
314 for this file and, if it exists, print its contents as part of the
315 commit-denial message. This gives you a chance to print any site-specific
316 information (e.g., who to call, what procedures to look up,...) whenever
319 =head1 Format of the cvsacl file
321 The 'cvsacl' file determines whether you may commit files. It contains lines
322 read from top to bottom, keeping track of whether a given user, repository
323 and branch combination is "allowed" or "denied." The script will assume
324 "allowed" on all repository paths until 'allow' and 'deny' rules change
327 The normal pattern is to specify an 'deny' rule to turn off
328 access to ALL users, then follow it with a matching 'allow' rule that will
329 turn on access for a select set of users. In the case of multiple rules for
330 the same user, repository and branch, the last one takes precedence.
332 Blank lines and lines with only comments are ignored. Any other lines not
333 beginning with "allow" or "deny" are logged to the restrict_log file.
335 Lines beginning with "allow" or "deny" are assumed to be '|'-separated
336 triples: (All spaces and tabs are ignored in a line.)
338 {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
340 1. String starting with "allow" or "deny".
341 2. Optional, comma-separated list of usernames.
342 3. Optional, comma-separated list of repository pathnames.
343 These are pathnames relative to $CVSROOT. They can be directories or
344 filenames. A directory name allows or restricts access to all files and
345 directories below it. One line can have either directories or filenames
347 4. Optional, comma-separated list of branch tags.
348 If not specified, all branches are assumed. Use HEAD to reference the
351 Example: (Note: No in-line comments.)
353 # ----- Make whole repository unavailable.
356 # ----- Except for user "dgg".
359 # ----- Except when "fred" or "john" commit to the
360 # module whose repository is "bin/ls"
361 allow|fred, john|bin/ls
363 # ----- Except when "ed" commits to the "stable"
364 # branch of the "bin/ls" repository
365 allow|ed|/bin/ls|stable
369 CVS passes to @ARGV an absolute directory pathname (the repository
370 appended to your $CVSROOT variable), followed by a list of filenames
371 within that directory that are to be committed.
373 The script walks through the 'cvsacl' file looking for matches on
374 the username, repository and branch.
376 A username match is simply the user's name appearing in the second
377 column of the cvsacl line in a space-or-comma separate list. If
378 blank, then any user will match.
385 Each entry in the modules section of the current 'cvsacl' line is
386 examined to see if it is a dir or a file. The line must have
387 either files or dirs, but not both. (To simplify the logic.)
390 If neither, then assume the 'cvsacl' file was set up in error and
391 skip that 'allow' line.
394 If a dir, then each dir pattern is matched separately against the
395 beginning of each of the committed files in @ARGV.
398 If a file, then each file pattern is matched exactly against each
399 of the files to be committed in @ARGV.
402 Repository and branch must BOTH match together. This is to cover
403 the use case where a user has multiple branches checked out in
404 a single work directory. Commit files can be from different
407 A branch match is either:
412 When no branches are listed in the fourth column. ("Match any.")
415 All elements from the fourth column are matched against each of
416 the tag names for $ARGV[1..$#ARGV] found in the %branches file.
421 'allow' match remove that match from the tally map.
424 Restricted ('deny') matches are saved in the %repository_matches
428 If there is a match on user, repository and branch:
430 If repository, branch and user match
432 add %repository_matches entries to %restricted_entries
434 remove %repository_matches entries from %restricted_entries
437 At the end of all the 'cvsacl' line checks, check to see if there
438 are any entries in the %restricted_entries. If so, then deny the
445 read CVS/Entries file and create branch{file}->{branch} hash table
446 + for each 'allow' and 'deny' line in the 'cvsacl' file:
448 | - Yes: set $user_match = 1;
449 | repository and branch match?
450 | - Yes: add to %repository_matches;
451 | did user, repository match?
452 | - Yes: if 'deny' then
453 | add %repository_matches -> %restricted_entries
455 | remove %repository_matches <- %restricted_entries
457 any saved restrictions?
459 set exit code allowing commits and exit
460 yes: report restrictions,
461 set exit code prohibiting commits and exit
465 1) file allow trumps a dir deny
467 allow||java/lib/README
468 2) dir allow can undo a file deny
469 deny||java/lib/README
471 3) file deny trumps a dir allow
473 deny||java/lib/README
474 4) dir deny trumps a file allow
475 allow||java/lib/README
477 ... so last match always takes precedence
481 $debug = 0; # Set to 1 for debug messages
483 %repository_matches = (); # hash of match file and pattern from 'cvsacl'
484 # repository_matches --> [branch, matching-pattern]
485 # (Used during module/branch matching loop)
487 %restricted_entries = (); # hash table of restricted commit files (from @ARGV)
488 # restricted_entries --> branch
489 # (If user/module/branch all match on an 'deny'
490 # line, then entries added to this map.)
492 %branch; # hash table of key: commit file; value: branch
493 # Built from ".../CVS/Entries" file of directory
494 # currently being examined
496 # ---------------------------------------------------------------- get CVSROOT
497 $cvsroot = $ENV{'CVSROOT'};
498 die "Must set CVSROOT\n" if !$cvsroot;
499 if ($cvsroot =~ /:([\/\w]*)$/) { # Filter ":pserver:", ":local:"-type prefixes
503 # ------------------------------------------------------------- set file paths
504 $entries = "CVS/Entries"; # client-side file???
505 $cvsaclfile = $cvsroot . "/CVSROOT/cvsacl";
506 $restrictfile = $cvsroot . "/CVSROOT/restrict_msg";
507 $restrictlog = $cvsroot . "/CVSROOT/restrict_log";
509 # --------------------------------------------------------------- process args
510 $user_name = processArgs(\@ARGV);
512 print("$$ \@ARGV after processArgs is: @ARGV.\n") if $debug;
513 print("$$ ========== Begin $PROGRAM_NAME for \"$ARGV[0]\" repository. ========== \n") if $debug;
515 # --------------------------------------------------------------- filter @ARGV
516 eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
517 while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
518 exit 255 if $die; # process any variable=value switches
520 print("$$ \@ARGV after shift processing contains:",join("\, ",@ARGV),".\n") if $debug;
522 # ---------------------------------------------------------------- get cvsroot
523 ($repository = shift) =~ s:^$cvsroot/::;
524 grep($_ = $repository . '/' . $_, @ARGV);
526 print("$$ \$cvsroot is: $cvsroot.\n") if $debug;
527 print "$$ Repos: $repository\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;
529 $exit_val = 0; # presume good exit value for commit
531 # ----------------------------------------------------------------------------
532 # ---------------------------------- create hash table $branch{file -> branch}
533 # ----------------------------------------------------------------------------
535 # Here's a typical Entries file:
537 # /checkoutlist/1.4/Wed Feb 4 23:51:23 2004//
538 # /cvsacl/1.3/Tue Feb 24 23:05:43 2004//
540 # /verifymsg/1.1/Fri Mar 16 19:56:24 2001//
544 open(ENTRIES, $entries) || die("Cannot open $entries.\n");
545 print("$$ File / Branch\n") if $debug;
549 next if /^\s*$/; # Skip blank lines
553 ([\w.]*) # file name -> $1
564 $branch{$repository . '/' . $1} = ($2) ? $2 : "HEAD";
565 print "$$ CVS Entry $i: $1/$2\n" if $debug;
570 # ----------------------------------------------------------------------------
571 # ------------------------------------- evaluate each active line from 'cvsacl'
572 # ----------------------------------------------------------------------------
573 open (CVSACL, $cvsaclfile) || exit(0); # It is ok for cvsacl file not to exist
576 next if /^\s*\#/; # skip comments
577 next if /^\s*$/; # skip blank lines
578 # --------------------------------------------- parse current 'cvsacl' line
579 print("$$ ==========\n$$ Processing \'cvsacl\' line: $_.\n") if $debug;
580 ($cvsacl_flag, $cvsacl_userIds, $cvsacl_modules, $cvsacl_branches) = split(/[\s,]*\|[\s,]*/, $_);
582 # ------------------------------ Validate 'allow' or 'deny' line prefix
583 if ($cvsacl_flag !~ /^allow/ && $cvsacl_flag !~ /^deny/) {
584 print ("Bad cvsacl line: $_\n") if $debug;
585 $log_text = sprintf "Bad cvsacl line: %s", $_;
586 write_restrictlog_record($log_text);
590 # -------------------------------------------------- init loop match flags
592 %repository_matches = ();
594 # ------------------------------------------------------------------------
595 # ---------------------------------------------------------- user matching
596 # ------------------------------------------------------------------------
597 # $user_name considered "in user list" if actually in list or is NULL
598 $user_match = (!$cvsacl_userIds || grep ($_ eq $user_name, split(/[\s,]+/,$cvsacl_userIds)));
599 print "$$ \$user_name: $user_name \$user_match match flag is: $user_match.\n" if $debug;
601 next; # no match, skip to next 'cvsacl' line
604 # ------------------------------------------------------------------------
605 # ---------------------------------------------------- repository matching
606 # ------------------------------------------------------------------------
607 if (!$cvsacl_modules) { # blank module list = all modules
608 if (!$cvsacl_branches) { # blank branch list = all branches
609 print("$$ Adding all modules to \%repository_matches; null " .
610 "\$cvsacl_modules and \$cvsacl_branches.\n") if $debug;
611 for $commit_object (@ARGV) {
612 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules];
613 print("$$ \$repository_matches{$commit_object} = " .
614 "[$branch{$commit_object}, $cvsacl_modules].\n") if $debug;
617 else { # need to check for repository match
618 @branch_list = split (/[\s,]+/,$cvsacl_branches);
619 print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug;
620 for $commit_object (@ARGV) {
621 if (grep($branch{$commit_object}, @branch_list)) {
622 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules];
623 print("$$ \$repository_matches{$commit_object} = " .
624 "[$branch{$commit_object}, $cvsacl_modules].\n") if $debug;
630 # ----------------------------------- check every argument combination
631 # parse 'cvsacl' modules to array
632 my @module_list = split(/[\s,]+/,$cvsacl_modules);
633 # ------------- Check all modules in list for either file or directory
635 if (($fileType = checkFileness(@module_list)) eq "") {
636 next; # skip bad file types
638 # ---------- Check each combination of 'cvsacl' modules vs. @ARGV files
639 print("$$ Checking matches for \@module_list: ", join("\, ",@module_list), ".\n") if $debug;
640 # loop thru all command-line commit objects
641 for $commit_object (@ARGV) {
642 # loop thru all modules on 'cvsacl' line
643 for $cvsacl_module (@module_list) {
644 print("$$ Is \'cvsacl\': $cvsacl_modules pattern in: \@ARGV " .
645 "\$commit_object: $commit_object?\n") if $debug;
646 # Do match of beginning of $commit_object
647 checkModuleMatch($fileType, $commit_object, $cvsacl_module);
648 } # end for commit objects
649 } # end for cvsacl modules
652 print("$$ Matches for: \%repository_matches: ", join("\, ", (keys %repository_matches)), ".\n") if $debug;
654 # ------------------------------------------------------------------------
655 # ----------------------------------------------------- setting exit value
656 # ------------------------------------------------------------------------
657 if ($user_match && %repository_matches) {
658 print("$$ An \"$cvsacl_flag\" match on User(s): $cvsacl_userIds; Module(s):" .
659 " $cvsacl_modules; Branch(es): $cvsacl_branches.\n") if $debug;
660 if ($cvsacl_flag eq "deny") {
661 # Add all matches to the hash of restricted modules
662 foreach $commitFile (keys %repository_matches) {
663 print("$$ Adding \%repository_matches entry: $commitFile.\n") if $debug;
664 $restricted_entries{$commitFile} = $repository_matches{$commitFile}[0];
668 # Remove all matches from the restricted modules hash
669 foreach $commitFile (keys %repository_matches) {
670 print("$$ Removing \%repository_matches entry: $commitFile.\n") if $debug;
671 delete $restricted_entries{$commitFile};
675 print "$$ ==== End of processing for \'cvsacl\' line: $_.\n" if $debug;
679 # ----------------------------------------------------------------------------
680 # --------------------------------------- determine final 'commit' disposition
681 # ----------------------------------------------------------------------------
682 if (%restricted_entries) { # any restricted entries?
683 $exit_val = 1; # don't commit
684 print("**** Access denied: Insufficient authority for user: '$user_name\' " .
685 "to commit to \'$repository\'.\n**** Contact CVS Administrators if " .
686 "you require update access to these directories or files.\n");
687 print("**** file(s)/dir(s) restricted were:\n\t", join("\n\t",keys %restricted_entries), "\n");
688 printOptionalRestrictionMessage();
691 elsif (!$exit_val && $debug) {
692 print "**** Access allowed: Sufficient authority for commit.\n";
695 print "$$ ==== \$exit_val = $exit_val\n" if $debug;
698 # ----------------------------------------------------------------------------
699 # -------------------------------------------------------------- end of "main"
700 # ----------------------------------------------------------------------------
703 # ----------------------------------------------------------------------------
704 # -------------------------------------------------------- process script args
705 # ----------------------------------------------------------------------------
708 # This subroutine is passed a reference to @ARGV.
710 # If @ARGV contains a "-u" entry, use that as the effective userId. In this
711 # case, the userId is the client-side userId that has been passed to this
712 # script by the commit_prep script. (This is why the commit_prep script must
713 # be placed *before* the cvs_acls script in the commitinfo admin file.)
715 # Otherwise, pull the userId from the server-side environment.
718 my ($argv) = shift; # pick up ref to @ARGV
719 my @argvClone = (); # immutable copy for foreach loop
720 for ($i=0; $i<(scalar @{$argv}); $i++) {
721 $argvClone[$i]=$argv->[$i];
724 print("$$ \@_ to processArgs is: @_.\n") if $debug;
726 # Parse command line arguments (file list is seen as one arg)
727 foreach $arg (@argvClone) {
728 print("$$ \$arg for processArgs loop is: $arg.\n") if $debug;
733 print("$$ \$debug flag set on.\n") if $debug;
734 print STDERR "Debug turned on...\n";
736 # Passing in a client-side userId?
737 elsif ($arg eq '-u') {
739 $userId = shift @ARGV;
740 print("$$ client-side \$userId set to: $userId.\n") if $debug;
742 # An override for the default restrictlog file?
743 elsif ($arg eq '-f') {
745 $restrictlog = shift @ARGV;
752 # No client-side userId passed? then get from server env
754 $userId = $ENV{"USER"} if !($userId = $ENV{"LOGNAME"});
755 print("$$ server-side \$userId set to: $userId.\n") if $debug;
758 print("$$ processArgs returning \$userId: $userId.\n") if $debug;
764 # ----------------------------------------------------------------------------
765 # --------------------- Check all modules in list for either file or directory
766 # ----------------------------------------------------------------------------
769 # Module patterns on the 'cvsacl' record can be files or directories.
770 # If it's a directory, we pattern-match the directory name from 'cvsacl'
771 # against the left side of the committed filename to see if the file is in
772 # that hierarchy. By contrast, files use an explicit match. If the entries
773 # are neither files nor directories, then the cvsacl file has been set up
774 # incorrectly; we return a "" and the caller skips that line as invalid.
776 # This function determines whether the entries on the 'cvsacl' record are all
777 # directories or all files; it cannot be a mixture. This restriction put in
778 # to simplify the logic (without taking away much functionality).
780 my @module_list = @_;
781 print("$$ Checking \"fileness\" or \"dir-ness\" for \@module_list entries.\n") if $debug;
782 print("$$ Entries are: ", join("\, ",@module_list), ".\n") if $debug;
784 for $cvsacl_module (@module_list) {
785 my $reposDirName = $cvsroot . '/' . $cvsacl_module;
786 my $reposFileName = $reposDirName . "\,v";
787 print("$$ In checkFileness: \$reposDirName: $reposDirName; \$reposFileName: $reposFileName.\n") if $debug;
788 if (((-d $reposDirName) && ($filetype eq "file")) || ((-f $reposFileName) && ($filetype eq "dir"))) {
789 print("Can\'t mix files and directories on single \'cvsacl\' file record; skipping entry.\n");
790 print(" Please contact a CVS administrator.\n");
794 elsif (-d $reposDirName) {
796 print("$$ $reposDirName is a directory.\n") if $debug;
798 elsif (-f $reposFileName) {
800 print("$$ $reposFileName is a regular file.\n") if $debug;
803 print("***** Item to commit was neither a regular file nor a directory.\n");
804 print("***** Current \'cvsacl\' line ignored.\n");
805 print("***** Possible problem with \'cvsacl\' admin file. Please contact a CVS administrator.\n");
807 $text = sprintf("Module entry on cvsacl line: %s is not a valid file or directory.\n", $cvsacl_module);
808 write_restrictlog_record($text);
813 print("$$ checkFileness will return \$filetype: $filetype.\n") if $debug;
818 # ----------------------------------------------------------------------------
819 # ----------------------------------------------------- check for module match
820 # ----------------------------------------------------------------------------
821 sub checkModuleMatch {
823 # This subroutine checks for a match between the directory or file pattern
824 # specified in the 'cvsacl' file (i.e., $cvsacl_modules) versus the commit file
825 # objects passed into the script via @ARGV (i.e., $commit_object).
827 # The directory pattern only has to match the beginning portion of the commit
828 # file's name for a match since all files under that directory are considered
829 # a match. File patterns must exactly match.
831 # Since (theoretically, if not normally in practice) a working directory can
832 # contain a mixture of files from different branches, this routine checks to
833 # see if there is also a match on branch before considering the file
834 # comparison a match.
838 print("$$ \@_ in checkModuleMatch is: @_.\n") if $debug;
839 my ($type,$commit_object,$cvsacl_module) = @_;
841 if ($type eq "file") { # Do exact file match of $commit_object
842 if ($commit_object eq $cvsacl_module) {
843 $match_flag = "file";
844 } # Do dir match at beginning of $commit_object
846 elsif ($commit_object =~ /^$cvsacl_module\//) {
851 print("$$ \$repository: $repository matches \$commit_object: $commit_object.\n") if $debug;
852 if (!$cvsacl_branches) { # empty branch pattern matches all
853 print("$$ blank \'cvsacl\' branch matches all commit files.\n") if $debug;
854 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module];
855 print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module].\n") if $debug;
857 else { # otherwise check branch hash table
858 @branch_list = split (/[\s,]+/,$cvsacl_branches);
859 print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug;
860 if (grep(/$branch{$commit_object}/, @branch_list)) {
861 $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module];
862 print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, " .
863 "$cvsacl_module].\n") if $debug;
870 # ----------------------------------------------------------------------------
871 # ------------------------------------------------------- check for file match
872 # ----------------------------------------------------------------------------
873 sub printOptionalRestrictionMessage {
875 # This subroutine optionally prints site-specific file restriction information
876 # whenever a restriction condition is met. If the file 'restrict_msg' does
877 # not exist, the routine immediately exits. If there is a 'restrict_msg' file
878 # then all the contents are printed at the end of the standard restriction
881 # As seen from examining the definition of $restrictfile, the default filename
882 # is: $CVSROOT/CVSROOT/restrict_msg.
884 open (RESTRICT, $restrictfile) || return; # It is ok for cvsacl file not to exist
887 # print out each line
893 # ----------------------------------------------------------------------------
894 # ---------------------------------------------------------- write log message
895 # ----------------------------------------------------------------------------
896 sub write_restrictlog {
898 # This subroutine iterates through the list of restricted entries and logs
899 # each one to the error logfile.
901 # write each line in @text out separately
902 foreach $commitfile (keys %restricted_entries) {
903 $log_text = sprintf "Commit attempt by: %s for: %s on branch: %s",
904 $user_name, $commitfile, $branch{$commitfile};
905 write_restrictlog_record($log_text);
910 # ----------------------------------------------------------------------------
911 # ---------------------------------------------------------- write log message
912 # ----------------------------------------------------------------------------
913 sub write_restrictlog_record {
915 # This subroutine receives a scalar string and writes it out to the
916 # $restrictlog file as a separate line. Each line is prepended with the date
917 # and time in the format: "2004/01/30 12:00:00 ".
921 # return quietly if there is a problem opening the log file.
922 open(FILE, ">>$restrictlog") || return;
924 (@time) = localtime();
926 # write each line in @text out separately
927 $log_record = sprintf "%04d/%02d/%02d %02d:%02d:%02d %s.\n",
928 $time[5]+1900, $time[4]+1, $time[3], $time[2], $time[1], $time[0], $text;
929 print FILE $log_record;
930 print("$$ restrict_log record being written: $log_record to $restrictlog.\n") if $debug;