bug-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

RE: Re: disabling branch commit


From: Peter Connolly
Subject: RE: Re: disabling branch commit
Date: Mon, 13 Sep 2004 12:55:00 -0700

Mark,

The changes seem completely reasonable.  I've gotten used to the idea of
replacing the old script.  Bombs away!

Thanks,
pc 

> -----Original Message-----
> From: mdb@juniper.net [mailto:mdb@juniper.net] On Behalf Of 
> Mark D. Baushke
> Sent: Monday, September 13, 2004 10:17 AM
> To: Peter Connolly
> Cc: bug-cvs@gnu.org
> Subject: Re: disabling branch commit 
> 
> Hi Peter,
> 
> Before I do the final commit, be advised that I have made a few minor
> changes to your script to rename it as cvs_acls from cvs_acls2 and to
> reference the most recent documentation URL.
> 
> Please review this and let me know if it is reasonable to proceed.
> 
>       -- Mark
> 
> Index: ChangeLog
> ===================================================================
> RCS file: /cvs/ccvs/ChangeLog,v
> retrieving revision 1.1048
> diff -u -p -r1.1048 ChangeLog
> --- ChangeLog 9 Sep 2004 17:30:12 -0000       1.1048
> +++ ChangeLog 13 Sep 2004 17:15:16 -0000
> @@ -1,3 +1,8 @@
> +2004-09-13  Mark D. Baushke  <mdb@cvshome.org>
> +
> +     * NEWS: Note change to contrib/cvs_acls and addition of
> +     contrib/cvs_acls.html
> +
>  2004-09-09  Conrad T. Pino  <Conrad@Pino.com>
>  
>       * cvsnt.dep: Regenerated for "cvsnt.dsp" changes made 
> 2004-09-08.
> Index: NEWS
> ===================================================================
> RCS file: /cvs/ccvs/NEWS,v
> retrieving revision 1.269
> diff -u -p -r1.269 NEWS
> --- NEWS      7 Sep 2004 19:16:37 -0000       1.269
> +++ NEWS      13 Sep 2004 17:15:16 -0000
> @@ -65,6 +65,10 @@ NEW FEATURES
>    using the new ImportNewFilesToVendorBranchOnly=yes option in
>    CVSROOT/config.
>  
> +* contrib/cvs_acls.in has been revised. Users of the old version will
> +  want to upgrade to use the new format. See the documentation in
> +  contrib/cvs_acls.html for more information.
> +
>  BUG FIXES
>  
>  * Thanks to a report from Chris Bohn <cbohn@rrinc.com>, 
> links from J.C. Hamlin
> Index: contrib/ChangeLog
> ===================================================================
> RCS file: /cvs/ccvs/contrib/ChangeLog,v
> retrieving revision 1.135
> diff -u -p -r1.135 ChangeLog
> --- contrib/ChangeLog 7 Sep 2004 05:15:05 -0000       1.135
> +++ contrib/ChangeLog 13 Sep 2004 17:15:16 -0000
> @@ -1,3 +1,12 @@
> +2004-09-13  Mark D. Baushke  <mdb@cvshome.org>
> +
> +     * cvs_acls.in: New version from
> +     "Peter Connolly" <Peter.Connolly@cnet.com>.
> +     * cvs_acls.html: New file from
> +     "Peter Connolly" <Peter.Connolly@cnet.com>.
> +     * Makefile.am (EXTRA_DIST): Add cvs_acls2.html
> +     (Close ccvs Issue #170.)
> +     
>  2004-09-06  Mark D. Baushke  <mdb@cvshome.org>
>  
>       * log_accum.in (UseNewFmtStrings, new_directory,
> Index: contrib/Makefile.am
> ===================================================================
> RCS file: /cvs/ccvs/contrib/Makefile.am,v
> retrieving revision 1.15
> diff -u -p -r1.15 Makefile.am
> --- contrib/Makefile.am       17 Jul 2004 02:41:31 -0000      1.15
> +++ contrib/Makefile.am       13 Sep 2004 17:15:16 -0000
> @@ -3,6 +3,7 @@
>  # Do not use this makefile directly, but only from `../Makefile'.
>  # Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
>  #               1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
> +#               2004
>  #               Free Software Foundation, Inc.
>  
>  # This program is free software; you can redistribute it 
> and/or modify
> @@ -55,6 +56,7 @@ EXTRA_DIST = \
>       cvs2vendor.sh \
>       sandbox_status.sh \
>       cvshelp.man \
> +     cvs_acls.html \
>       debug_check_log.sh \
>       descend.sh \
>       descend.man \
> Index: contrib/Makefile.in
> ===================================================================
> RCS file: /cvs/ccvs/contrib/Makefile.in,v
> retrieving revision 1.75
> diff -u -p -r1.75 Makefile.in
> --- contrib/Makefile.in       17 Jul 2004 02:41:31 -0000      1.75
> +++ contrib/Makefile.in       13 Sep 2004 17:15:16 -0000
> @@ -1,7 +1,7 @@
>  # Makefile.in generated by automake 1.7.9 from Makefile.am.
>  # @configure_input@
>  
> -# Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 
> 2002, 2003
> +# Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 
> 2002, 2003, 2004
>  # Free Software Foundation, Inc.
>  # This Makefile.in is free software; the Free Software Foundation
>  # gives unlimited permission to copy and/or distribute it,
> @@ -17,7 +17,8 @@
>  # Makefile for GNU CVS contributed sources.
>  # Do not use this makefile directly, but only from `../Makefile'.
>  # Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
> -#               1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
> +#               1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
> +#               2004
>  #               Free Software Foundation, Inc.
>  
>  # This program is free software; you can redistribute it 
> and/or modify
> @@ -219,6 +220,7 @@ EXTRA_DIST = \
>       $(contrib_MANS) \
>       cvs2vendor.sh \
>       sandbox_status.sh \
> +     cvs_acls.html \
>       cvshelp.man \
>       debug_check_log.sh \
>       descend.sh \
> Index: contrib/README
> ===================================================================
> RCS file: /cvs/ccvs/contrib/README,v
> retrieving revision 1.18
> diff -u -p -r1.18 README
> --- contrib/README    21 Nov 2002 19:04:38 -0000      1.18
> +++ contrib/README    13 Sep 2004 17:15:16 -0000
> @@ -57,9 +57,10 @@ An attempt at a table of Contents for th
>                       Contributed by Greg A. Woods <woods@planix.com>.
>  
>       cvs_acls        A perl script that implements Access 
> Control Lists
> -                     by using the "commitinfo" hook provided with the
> +     cvs_acls.html   by using the "commitinfo" hook provided with the
>                       "cvs commit" command.
>                       Contributed by David G. Grubbs <dgg@ksr.com>.
> +                     Enhanced by Peter Connolly 
> <Peter.Connolly@cnet.com>.
>  
>       cvscheck        Identifies files added, changed, or removed in a
>       cvscheck.man    checked out CVS tree; also notices 
> unknown files.
> Index: contrib/cvs_acls.html
> ===================================================================
> RCS file: contrib/cvs_acls.html
> diff -N contrib/cvs_acls.html
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ contrib/cvs_acls.html     13 Sep 2004 17:15:16 -0000
> @@ -0,0 +1,427 @@
> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
> +<html xmlns="http://www.w3.org/1999/xhtml";><head>
> +
> +<title>cvs_acls</title><link rev="made" 
> href="mailto:root@localhost";></head>
> +
> +<body style="background-color: white;">
> +
> +<p><a name="__index__"></a></p>
> +<!-- INDEX BEGIN -->
> +
> +<ul>
> +
> +     <li><a href="#name">Name</a></li>
> +     <li><a href="#synopsis">Synopsis</a></li>
> +     <li><a href="#licensing">Licensing</a></li>
> +     <li><a href="#description">Description</a></li>
> +     <li><a href="#enhancements">Enhancements</a></li>
> +     <ul>
> +
> +             <li><a href="#fixed_bugs">Fixed Bugs</a></li>
> +             <li><a href="#enhancements">Enhancements</a></li>
> +             <li><a href="#todos">ToDoS</a></li>
> +     </ul>
> +
> +     <li><a href="#version_information">Version Information</a></li>
> +     <li><a href="#installation">Installation</a></li>
> +     <li><a href="#format_of_the_cvsacl_file">Format of the 
> cvsacl file</a></li>
> +     <li><a href="#program_logic">Program Logic</a></li>
> +     <ul>
> +
> +             <li><a href="#pseudocode">Pseudocode</a></li>
> +             <li><a href="#sanity_check">Sanity Check</a></li>
> +     </ul>
> +
> +</ul>
> +<!-- INDEX END -->
> +
> +<hr>
> +<p>
> +</p>
> +<h1><a name="name">Name</a></h1>
> +<p>cvs_acls - Access Control List for CVS</p>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="synopsis">Synopsis</a></h1>
> +<p>In 'commitinfo':</p>
> +<pre>  repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls 
> [-d][-u $USER][-f &lt;logfile&gt;]</pre>
> +<p>where:</p>
> +<pre>  -d  turns on debug information
> +  -u  passes the client-side userId to the cvs_acls script
> +  -f  specifies an alternate filename for the restrict_log file</pre>
> +<p>In 'cvsacl':</p>
> +<pre>  {allow.*,deny.*} [|user,user,... [|repos,repos,... 
> [|branch,branch,...]]]</pre>
> +<p>where:</p>
> +<pre>  allow|deny - allow: commits are allowed; deny: prohibited
> +  user          - userId to be allowed or restricted
> +  repos         - file or directory to be allowed or restricted
> +  branch        - branch to be allowed or restricted</pre>
> +<p>See below for examples.</p>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="licensing">Licensing</a></h1>
> +<p>cvs_acls - provides access control list functionality for CVS
> +</p>
> +<pre>
> +Copyright (c) 2004 by Peter Connolly 
> &lt;peter.connolly@cnet.com&gt;  
> +All rights reserved.</pre>
> +<p>This program is free software; you can redistribute it 
> and/or modify  
> +it under the terms of the GNU General Public License as 
> published by  
> +the Free Software Foundation; either version 2 of the License, or  
> +(at your option) any later version.</p>
> +<p>This program is distributed in the hope that it will be useful,  
> +but WITHOUT ANY WARRANTY; without even the implied warranty of  
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
> +GNU General Public License for more details.</p>
> +<p>You should have received a copy of the GNU General Public 
> License  
> +along with this program; if not, write to the Free Software  
> +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  
> 02111-1307  USA</p>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="description">Description</a></h1>
> +<p>This script--cvs_acls--is invoked once for each directory 
> within a 
> +``cvs commit''. The set of files being committed for that 
> directory as 
> +well as the directory itself, are passed to this script.  
> This script 
> +checks its 'cvsacl' file to see if any of the files being committed 
> +are on the 'cvsacl' file's restricted list.  If any of the files are
> +restricted, then the cvs_acls script passes back an exit code of 1
> +which disallows the commits for that directory.</p>
> +<p>Messages are returned to the committer indicating the <a 
> href="#item_file"><code>file(s)</code></a> that 
> +he/she are not allowed to committ.  Additionally, a site-specific 
> +set of messages (e.g., contact information) can be included in these 
> +messages.</p>
> +<p>When a commit is prohibited, log messages are written to 
> a restrict_log
> +file in $CVSROOT/CVSROOT.  This default file can be redirected to 
> +another destination.</p>
> +<p>The script is triggered from the 'commitinfo' file in 
> $CVSROOT/CVSROOT/.</p>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="enhancements">Enhancements</a></h1>
> +<p>This section lists the bug fixes and enhancements added 
> to cvs_acls
> +that make up the current cvs_acls.</p>
> +<p>
> +</p>
> +<h2><a name="fixed_bugs">Fixed Bugs</a></h2>
> +<p>This version attempts to get rid the following bugs from the
> +original version of cvs_acls:</p>
> +<ul>
> +<li><strong><a name="item_files">Multiple entries on an 
> 'cvsacl' line will be matched individually, 
> +instead of requiring that all commit files *exactly* match all 
> +'cvsacl' entries. Commiting a file not in the 'cvsacl' list would
> +allow *all* files (including a restricted file) to be 
> committed.</a></strong><br>
> +</li>
> +[IMO, this basically made the original script unuseable for our 
> +situation since any arbitrary combination of committed files could 
> +avoid matching the 'cvsacl's entries.]
> +<p></p>
> +<li><strong><a 
> name="item_handle_specific_filename_restrictions._cvs_acls_">H
> andle specific filename restrictions. cvs_acls didn't restrict
> +individual files specified in 'cvsacl'.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_correctly_handle_multiple,_specific_filename_res">C
> orrectly handle multiple, specific filename 
> restrictions</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_prohibit_mix_of_dirs_and_files_on_a_single_'cvsa">P
> rohibit mix of dirs and files on a single 'cvsacl' line
> +[To simplify the logic and because this would be normal 
> usage.]</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_correctly_handle_a_mixture_of_branch_restrictions_"
> >Correctly handle a mixture of branch restrictions within one work
> +directory</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_$cvsroot_existence_is_checked_too_late">$CVSROOT 
> existence is checked too late</a></strong><br>
> +</li>
> +<li><strong><a name="item_option">Correctly handle the 
> CVSROOT=:local:/... option (useful for 
> +interactive testing)</a></strong><br>
> +</li>
> +<li><strong><a name="item_logic">Replacing shoddy 
> ``$universal_off'' logic 
> +(Thanks to Karl-Konig Konigsson for pointing this 
> out.)</a></strong><br>
> +</li>
> +</ul>
> +<p>
> +</p>
> +<h2><a name="enhancements">Enhancements</a></h2>
> +<ul>
> +<li><strong><a 
> name="item_checks_modules_in_the_'cvsacl'_file_for_valid_">Che
> cks modules in the 'cvsacl' file for valid files and 
> directories</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_accurately_report_restricted_entries_and_their_mat"
> >Accurately report restricted entries and their matching 
> patterns</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_simplified_and_commented_overly_complex_perl_regex"
> >Simplified and commented overly complex PERL REGEXPs for readability 
> +and maintainability</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_skip_the_rest_of_processing_if_a_mismatch_on_porti"
> >Skip the rest of processing if a mismatch on portion of the 
> 'cvsacl' line</a></strong><br>
> +</li>
> +<li><strong><a name="item_file">Get rid of opaque ``karma'' 
> messages in favor of user-friendly messages
> +that describe which user, <code>file(s)</code> and 
> <code>branch(es)</code> were disallowed.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_add_optional_'restrict_msg'_file_for_additiona">Add
>  optional 'restrict_msg' file for additional, site-specific 
> +restriction messages.</a></strong><br>
> +</li>
> +<li><strong><a name="item_userid">Take a ``-u'' parameter 
> for $USER from commit_prep so that the script
> +can do restrictions based on the client-side userId rather than the
> +server-side userId (usually 'cvs').</a></strong><br>
> +</li>
> +(See discussion below on ``Admin Setup'' for more on this point.)
> +<p></p>
> +<li><strong><a 
> name="item_added_a_lot_more_debug_trace">Added a lot more 
> debug trace</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_tested_these_restrictions_with_concurrent_use_of_p"
> >Tested these restrictions with concurrent use of pserver and SSH
> +access to model our transition from pserver to ext 
> access.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_added_logging_of_restricted_commit_attempts._res">A
> dded logging of restricted commit attempts.
> +Restricted commits can be sent to a default file:
> +$CVSROOT/CVSROOT/restrictlog or to one passed to the script
> +via the -f command parameter.</a></strong><br>
> +</li>
> +</ul>
> +<p>
> +</p>
> +<h2><a name="todos">ToDoS</a></h2>
> +<ul>
> +<li><strong><a 
> name="item_need_to_deal_with_pserver/ssh_transition_with_co">N
> eed to deal with pserver/SSH transition with conflicting 
> umasks?</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_use_a_cpan_module_to_handle_command_parameters.">Us
> e a CPAN module to handle command parameters.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_use_a_cpan_module_to_clone_data_structures.">Use a 
> CPAN module to clone data structures.</a></strong><br>
> +</li>
> +</ul>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="version_information">Version Information</a></h1>
> +<p>This is not offered as a fix to the original 'cvs_acls' 
> script since it 
> +differs substantially in goals and methods from the original 
> and there 
> +are probably a significant number of people out there that 
> still require 
> +the original version's functionality.</p>
> +<p>The 'cvsacl' file flags of 'allow' and 'deny' were intentionally 
> +changed to 'allow' and 'deny' because there are enough differences 
> +between the original script's behavior and this one's that 
> we wanted to
> +make sure that users will rethink their 'cvsacl' file formats before
> +plugging in this newer script.</p>
> +<p>Please note that there has been very limited 
> cross-platform testing of 
> +this script!!! (We did not have the time or resources to do 
> exhaustive
> +cross-platform testing.)</p>
> +<p>It was developed and tested under Red Hat Linux 9.0 using 
> PERL 5.8.0.
> +Additionally, it was built and tested under Red Hat Linux 7.3 using 
> +PERL 5.6.1.</p>
> +<p>$Id$</p>
> +<p>This Version is based on the 1.11.13 version of cvs_acls
> +<a 
> href="mailto:peter.connolly@cnet.com";>peter.connolly@cnet.com<
> /a> (Peter Connolly)</p>
> +<pre>  Access control lists for CVS.  dgg@ksr.com (David G. Grubbs)
> +  Branch specific controls added by voisine@bytemobile.com 
> (Aaron Voisine)</pre>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="installation">Installation</a></h1>
> +<p>To use this program, do the following four things:</p>
> +<p>0. Install PERL, version 5.6.1 or 5.8.0.</p>
> +<p>1. Admin Setup:</p>
> +<pre>   There are two choices here.</pre>
> +<pre>   a) The first option is to use the $ENV{"USER"}, 
> server-side userId
> +      (from the third column of your pserver 'passwd' file) 
> as the basis for 
> +      your restrictions.  In this case, you will (at a 
> minimum) want to set
> +      up a new "cvsadmin" userId and group on the pserver machine.  
> +      CVS administrators will then set up their 'passwd' 
> file entries to
> +      run either as "cvs" (for regular users) or as 
> "cvsadmin" (for power 
> +      users).  Correspondingly, your 'cvsacl' file will only 
> list 'cvs'
> +      and 'cvsadmin' as the userIds in the second column.</pre>
> +<pre>      Commentary: A potential weakness of this is that 
> the xinetd 
> +      cvspserver process will need to run as 'root' in order 
> to switch 
> +      between the 'cvs' and the 'cvsadmin' userIds.  Some 
> sysadmins don't
> +      like situations like this and may want to chroot the process.
> +      Talk to them about this point...</pre>
> +<pre>   b) The second option is to use the client-side 
> userId as the basis for
> +      your restrictions.  In this case, all the xinetd 
> cvspserver processes 
> +      can run as userId 'cvs' and no 'root' userId is 
> required.  If you have
> +      a 'passwd' file that lists 'cvs' as the effective 
> run-time userId for
> +      all your users, then no changes to this file are 
> needed.  Your 'cvsacl'
> +      file will use the individual, client-side userIds in 
> its 2nd column.</pre>
> +<pre>      As long as the userIds in pserver's 'passwd' file 
> match those userIds 
> +      that your Linux server know about, this approach is 
> ideal if you are 
> +      planning to move from pserver to SSH access at some 
> later point in time.
> +      Just by switching the CVSROOT var from 
> CVSROOT=:pserver:&lt;userId&gt;... to 
> +      CVSROOT=:ext:&lt;userId&gt;..., users can switch over 
> to SSH access without
> +      any other administrative changes.  When all users have 
> switched over to
> +      SSH, the inherently insecure xinetd cvspserver process 
> can be disabled.
> +      [<a 
> href="https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_2.ht
> ml#SEC32">https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_
> 2.html#SEC32</a>]</pre>
> +<pre>      :TODO: The only potential glitch with the SSH 
> approach is the possibility 
> +      that each user can have differing umasks that might 
> interfere with one 
> +      another, especially during a transition from pserver 
> to SSH.  As noted
> +      in the ToDo section, this needs a good strategy and 
> set of tests for that 
> +      yet...</pre>
> +<p>2. Put two lines, as the *only* non-comment lines, in 
> your commitinfo file:</p>
> +<pre>   ALL $CVSROOT/CVSROOT/commit_prep 
> +   ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f 
> &lt;logfilename&gt;]</pre>
> +<pre>   where "-d" turns on debug trace
> +         "-u $USER" passes the client-side userId to cvs_acls 
> +         "-f &lt;logfilename"&gt; overrides the default 
> filename used to log
> +                            restricted commit attempts.</pre>
> +<pre>   (These are handled in the processArgs() subroutine.)</pre>
> +<p>If you are using client-side userIds to restrict access to your 
> +repository, make sure that they are in this order since the 
> commit_prep 
> +script is required in order to pass the $USER parameter.</p>
> +<p>A final note about the repository matching pattern.  The 
> example above
> +uses ``ALL'' but note that this means that the cvs_acls 
> script will run
> +for each and every commit in your repository.  Obviously, in a large
> +repository this adds up to a lot of overhead that may not be 
> necesary. 
> +A better strategy is to use a repository pattern that is 
> more specific 
> +to the areas that you wish to secure.</p>
> +<p>3. Install this file as $CVSROOT/CVSROOT/cvs_acls and 
> make it executable.</p>
> +<p>4. Create a file named CVSROOT/cvsacl and optionally add it to
> +   CVSROOT/checkoutlist and check it in.  See the CVS manual's
> +   administrative files section about checkoutlist.  Typically:</p>
> +<pre>   $ cvs checkout CVSROOT
> +   $ cd CVSROOT
> +   [ create the cvsacl file, include 'commitinfo' line ]
> +   [ add cvsacl to checkoutlist ]
> +   $ cvs add cvsacl
> +   $ cvs commit -m 'Added cvsacl for use with cvs_acls.' 
> cvsacl checkoutlist</pre>
> +<p>Note: The format of the 'cvsacl' file is described in 
> detail immediately 
> +below but here is an important set up point:</p>
> +<pre>   Make sure to include a line like the following:</pre>
> +<pre>     deny||CVSROOT/commitinfo CVSROOT/cvsacl
> +     allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl</pre>
> +<pre>   that restricts access to commitinfo and cvsacl since 
> this would be one of
> +   the easiest "end runs" around this ACL approach. 
> ('commitinfo' has the 
> +   line that executes the cvs_acls script and, of course, all the 
> +   restrictions are in 'cvsacl'.)</pre>
> +<p>5. (Optional) Create a 'restrict_msg' file in the 
> $CVSROOT/CVSROOT directory.
> +   Whenever there is a restricted file or dir message, 
> cvs_acls will look 
> +   for this file and, if it exists, print its contents as 
> part of the 
> +   commit-denial message.  This gives you a chance to print 
> any site-specific
> +   information (e.g., who to call, what procedures to look 
> up,...) whenever
> +   a commit is denied.</p>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="format_of_the_cvsacl_file">Format of the cvsacl 
> file</a></h1>
> +<p>The 'cvsacl' file determines whether you may commit 
> files.  It contains lines
> +read from top to bottom, keeping track of whether a given 
> user, repository
> +and branch combination is ``allowed'' or ``denied.''  The 
> script will assume 
> +``allowed'' on all repository paths until 'allow' and 'deny' 
> rules change 
> +that default.</p>
> +<p>The normal pattern is to specify an 'deny' rule to turn off
> +access to ALL users, then follow it with a matching 'allow' 
> rule that will 
> +turn on access for a select set of users.  In the case of 
> multiple rules for
> +the same user, repository and branch, the last one takes 
> precedence.</p>
> +<p>Blank lines and lines with only comments are ignored.  
> Any other lines not 
> +beginning with ``allow'' or ``deny'' are logged to the 
> restrict_log file.</p>
> +<p>Lines beginning with ``allow'' or ``deny'' are assumed to 
> be '|'-separated
> +triples: (All spaces and tabs are ignored in a line.)</p>
> +<pre>  {allow.*,deny.*} [|user,user,... [|repos,repos,... 
> [|branch,branch,...]]]</pre>
> +<pre>   1. String starting with "allow" or "deny".
> +   2. Optional, comma-separated list of usernames.
> +   3. Optional, comma-separated list of repository pathnames.
> +      These are pathnames relative to $CVSROOT.  They can be 
> directories or
> +      filenames.  A directory name allows or restricts 
> access to all files and
> +      directories below it. One line can have either 
> directories or filenames
> +      but not both.
> +   4. Optional, comma-separated list of branch tags.
> +      If not specified, all branches are assumed. Use HEAD 
> to reference the
> +      main branch.</pre>
> +<p>Example:  (Note: No in-line comments.)</p>
> +<pre>   # ----- Make whole repository unavailable.
> +   deny</pre>
> +<pre>   # ----- Except for user "dgg".
> +   allow|dgg</pre>
> +<pre>   # ----- Except when "fred" or "john" commit to the 
> +   #       module whose repository is "bin/ls"
> +   allow|fred, john|bin/ls</pre>
> +<pre>   # ----- Except when "ed" commits to the "stable" 
> +   #       branch of the "bin/ls" repository
> +   allow|ed|/bin/ls|stable</pre>
> +<p>
> +</p>
> +<hr>
> +<h1><a name="program_logic">Program Logic</a></h1>
> +<p>CVS passes to @ARGV an absolute directory pathname (the repository
> +appended to your $CVSROOT variable), followed by a list of filenames
> +within that directory that are to be committed.</p>
> +<p>The script walks through the 'cvsacl' file looking for matches on 
> +the username, repository and branch.</p>
> +<p>A username match is simply the user's name appearing in the second
> +column of the cvsacl line in a space-or-comma separate list. If
> +blank, then any user will match.</p>
> +<p>A repository match:</p>
> +<ul>
> +<li><strong><a 
> name="item_each_entry_in_the_modules_section_of_the_current_%"
> >Each entry in the modules section of the current 'cvsacl' line is 
> +examined to see if it is a dir or a file. The line must have 
> +either files or dirs, but not both. (To simplify the 
> logic.)</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_if_neither,_then_assume_the_'cvsacl'_file_wa">If 
> neither, then assume the 'cvsacl' file was set up in error and
> +skip that 'allow' line.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_if_a_dir,_then_each_dir_pattern_is_matched_separ">I
> f a dir, then each dir pattern is matched separately against the 
> +beginning of each of the committed files in @ARGV.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_if_a_file,_then_each_file_pattern_is_matched_exa">I
> f a file, then each file pattern is matched exactly against each
> +of the files to be committed in @ARGV.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_repository_and_branch_must_both_match_together._">R
> epository and branch must BOTH match together. This is to cover
> +the use case where a user has multiple branches checked out in
> +a single work directory. Commit files can be from different
> +branches.</a></strong><br>
> +</li>
> +A branch match is either:
> +<ul>
> +<li><strong><a 
> name="item_when_no_branches_are_listed_in_the_fourth_column%2"
> >When no branches are listed in the fourth column. (``Match 
> any.'')</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_all_elements_from_the_fourth_column_are_matched_ag"
> >All elements from the fourth column are matched against each of 
> +the tag names for $ARGV[1..$#ARGV] found in the %branches 
> file.</a></strong><br>
> +</li>
> +</ul>
> +<li><strong><a 
> name="item_'allow'_match_remove_that_match_from_the_tally">'al
> low' match remove that match from the tally map.</a></strong><br>
> +</li>
> +<li><strong><a name="item_restricted">Restricted ('deny') 
> matches are saved in the %repository_matches 
> +table.</a></strong><br>
> +</li>
> +<li><strong><a 
> name="item_if_there_is_a_match_on_user,_repository_and_bran">I
> f there is a match on user, repository and branch:</a></strong><br>
> +</li>
> +<pre>  If repository, branch and user match
> +    if 'deny'
> +      add %repository_matches entries to %restricted_entries
> +    else if 'allow'
> +      remove %repository_matches entries from 
> %restricted_entries</pre>
> +<li><strong><a 
> name="item_at_the_end_of_all_the_'cvsacl'_line_checks,_">At 
> the end of all the 'cvsacl' line checks, check to see if there
> +are any entries in the %restricted_entries.  If so, then deny the
> +commit.</a></strong><br>
> +</li>
> +</ul>
> +<p>
> +</p>
> +<h2><a name="pseudocode">Pseudocode</a></h2>
> +<pre>     read CVS/Entries file and create 
> branch{file}-&gt;{branch} hash table
> +   + for each 'allow' and 'deny' line in the 'cvsacl' file:
> +   |   user match?   
> +   |     - Yes: set $user_match       = 1;
> +   |   repository and branch match?
> +   |     - Yes: add to %repository_matches;
> +   |   did user, repository match?
> +   |     - Yes: if 'deny' then 
> +   |                add %repository_matches -&gt; %restricted_entries
> +   |            if 'allow'   then 
> +   |                remove %repository_matches &lt;- 
> %restricted_entries
> +   + end for loop
> +     any saved restrictions?
> +       no:  exit, 
> +            set exit code allowing commits and exit
> +       yes: report restrictions, 
> +            set exit code prohibiting commits and exit</pre>
> +<p>
> +</p>
> +<h2><a name="sanity_check">Sanity Check</a></h2>
> +<pre>  1) file allow trumps a dir deny
> +     deny||java/lib
> +     allow||java/lib/README
> +  2) dir allow can undo a file deny
> +     deny||java/lib/README
> +     allow||java/lib
> +  3) file deny trumps a dir allow
> +     allow||java/lib
> +     deny||java/lib/README
> +  4) dir deny trumps a file allow
> +     allow||java/lib/README
> +     deny||java/lib
> +  ... so last match always takes precedence</pre>
> +
> +</body></html>
> Index: contrib/cvs_acls.in
> ===================================================================
> RCS file: /cvs/ccvs/contrib/cvs_acls.in,v
> retrieving revision 1.4
> diff -u -p -r1.4 cvs_acls.in
> --- contrib/cvs_acls.in       16 Dec 2002 15:30:43 -0000      1.4
> +++ contrib/cvs_acls.in       13 Sep 2004 17:15:16 -0000
> @@ -1,193 +1,933 @@
>  #! @PERL@
>  # -*-Perl-*-
> -#
> -# Access control lists for CVS.  dgg@ksr.com (David G. Grubbs)
> -# Branch specific controls added by voisine@bytemobile.com 
> (Aaron Voisine)
> -#
> -# CVS "commitinfo" for matching repository names, running 
> the program it finds
> -# on the same line.  More information is available in the 
> CVS man pages.
> -#
> -# ==== INSTALLATION:
> -#
> -# To use this program as I intended, do the following four things:
> -#
> -# 0. Install PERL.  :-)
> -#
> -# 1. Put one line, as the *only* non-comment line, in your 
> commitinfo file:
> -#
> -#    DEFAULT         /usr/local/bin/cvs_acls
> -#
> -# 2. Install this file as /usr/local/bin/cvs_acls and make 
> it executable.
> -#
> -# 3. Create a file named CVSROOT/avail and optionally add it to
> -#    CVSROOT/checkoutlist and check it in.  See the CVS manual's
> -#    administrative files section about checkoutlist.  Typically:
> -#
> -#    $ cvs checkout CVSROOT
> -#    $ cd CVSROOT
> -#    [ create the avail file ]
> -#    [ add avail to checkoutlist ]
> -#    $ cvs add avail
> -#    $ cvs commit -m 'Added avail for use with cvs_acls.' 
> avail checkoutlist
> -#
> -# ==== FORMAT OF THE avail FILE:
> -#
> -# The avail file determines whether you may commit files.  
> It contains lines
> -# read from top to bottom, keeping track of a single "bit".  
> The "bit"
> -# defaults to "on".  It can be turned "off" by "unavail" 
> lines and "on" by
> -# "avail" lines.  ==> Last one counts.
> -#
> -# Any line not beginning with "avail" or "unavail" is ignored.
> -#
> -# Lines beginning with "avail" or "unavail" are assumed to 
> be '|'-separated
> -# triples: (All spaces and tabs are ignored in a line.)
> -#
> -# {avail.*,unavail.*} [|user,user,... [|repos,repos,... 
> [|branch,branch,...]]]
> -#
> -#    1. String starting with "avail" or "unavail".
> -#    2. Optional, comma-separated list of usernames.
> -#    3. Optional, comma-separated list of repository pathnames.
> -#    These are pathnames relative to $CVSROOT.  They can be 
> directories or
> -#    filenames.  A directory name allows access to all files and
> -#    directories below it.
> -#    4. Optional, comma-separated list of branch tags.
> -#    If not specified, all branches are assumed. Use HEAD to 
> reference the
> -#    main branch.
> -#
> -# Example:  (Text from the ';;' rightward may not appear in 
> the file.)
> -#
> -#    unavail                 ;; Make whole repository unavailable.
> -#    avail|dgg               ;; Except for user "dgg".
> -#    avail|fred, john|bin/ls ;; Except when "fred" or "john" 
> commit to
> -#                            ;; the module whose repository 
> is "bin/ls"
> -#    avail|ed|/bin/ls|stable ;; Except when "ed" commits to 
> the "stable"
> -#                            ;; branch of the "bin/ls" repository 
> -#
> -# PROGRAM LOGIC:
> -#
> -#    CVS passes to @ARGV an absolute directory pathname (the 
> repository
> -#    appended to your $CVSROOT variable), followed by a list 
> of filenames
> -#    within that directory.
> -#
> -#    We walk through the avail file looking for a line that 
> matches the
> -#    username, repository and branch.
> -#
> -#    A username match is simply the user's name appearing in 
> the second
> -#    column of the avail line in a space-or-comma separate list.
> -#
> -#    A repository match is either:
> -#            - One element of the third column matches 
> $ARGV[0], or some
> -#              parent directory of $ARGV[0].
> -#            - Otherwise *all* file arguments 
> ($ARGV[1..$#ARGV]) must be
> -#              in the file list in one avail line.
> -#        - In other words, using directory names in the 
> third column of
> -#          the avail file allows committing of any file (or group of
> -#          files in a single commit) in the tree below that 
> directory.
> -#        - If individual file names are used in the third column of
> -#          the avail file, then files must be committed 
> individually or
> -#          all files specified in a single commit must all appear in
> -#          third column of a single avail line.
> -#
> -#    A branch match is either:
> -#            - When no branches are listed in the fourth column.
> -#            - One element from the fourth column matches 
> each of the tag
> -#              names for $ARGV[1..$#ARGV] found in the 
> CVS/Entries file.
> -#            - HEAD specified in the fourth column will 
> match if there
> -#              is no tag listed in the CVS/Entries file.
> -#
>  
> -$debug = 0;
> +=head1 Name
> +
> +cvs_acls2 - Access Control List for CVS, Version 2
> +
> +=head1 Synopsis
> +
> +In 'commitinfo':
> +
> +  repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls2 
> [-d][-u $USER][-f <logfile>]
> +
> +where:
> +
> +  -d  turns on debug information
> +  -u  passes the client-side userId to the cvs_acls2 script
> +  -f  specifies an alternate filename for the restrict_log file
> +
> +In 'cvsacl':
> +
> +  {allow.*,deny.*} [|user,user,... [|repos,repos,... 
> [|branch,branch,...]]]
> +
> +where:
> +
> +  allow|deny - allow: commits are allowed; deny: prohibited
> +  user          - userId to be allowed or restricted
> +  repos         - file or directory to be allowed or restricted
> +  branch        - branch to be allowed or restricted
> +
> +See below for examples.
> +
> +=head1 Licensing
> +
> +cvs_acls2 - provides access control list functionality for CVS
> +  
> +Copyright (c) 2004 by Peter Connolly <peter.connolly@cnet.com>  
> +All rights reserved.
> +
> +This program is free software; you can redistribute it 
> and/or modify  
> +it under the terms of the GNU General Public License as 
> published by  
> +the Free Software Foundation; either version 2 of the License, or  
> +(at your option) any later version. 
> +
> +This program is distributed in the hope that it will be useful,  
> +but WITHOUT ANY WARRANTY; without even the implied warranty of  
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
> +GNU General Public License for more details.  
> +
> +You should have received a copy of the GNU General Public License  
> +along with this program; if not, write to the Free Software  
> +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  
> 02111-1307  USA
> +
> +=head1 Description
> +
> +This script--cvs_acls2--is invoked once for each directory within a 
> +"cvs commit". The set of files being committed for that directory as 
> +well as the directory itself, are passed to this script.  
> This script 
> +checks its 'cvsacl' file to see if any of the files being committed 
> +are on the 'cvsacl' file's restricted list.  If any of the files are
> +restricted, then the cvs_acls2 script passes back an exit code of 1
> +which disallows the commits for that directory.  
> +
> +Messages are returned to the committer indicating the file(s) that 
> +he/she are not allowed to committ.  Additionally, a site-specific 
> +set of messages (e.g., contact information) can be included in these 
> +messages.
> +
> +When a commit is prohibited, log messages are written to a 
> restrict_log
> +file in $CVSROOT/CVSROOT.  This default file can be redirected to 
> +another destination.
> +
> +The script is triggered from the 'commitinfo' file in 
> $CVSROOT/CVSROOT/.
> +
> +=head1 Enhancements
> +
> +This section lists the bug fixes and enhancements added to cvs_acls
> +that make up the current cvs_acls2.
> +
> +=head2 Fixed Bugs
> +
> +This version attempts to get rid the following bugs from the
> +original version of cvs_acls:
> +
> +=over 2
> +
> +=item *
> +Multiple entries on an 'cvsacl' line will be matched individually, 
> +instead of requiring that all commit files *exactly* match all 
> +'cvsacl' entries. Commiting a file not in the 'cvsacl' list would
> +allow *all* files (including a restricted file) to be committed.
> +
> +[IMO, this basically made the original script unuseable for our 
> +situation since any arbitrary combination of committed files could 
> +avoid matching the 'cvsacl's entries.]
> +
> +=item *
> +Handle specific filename restrictions. cvs_acls didn't restrict
> +individual files specified in 'cvsacl'.
> +
> +=item *
> +Correctly handle multiple, specific filename restrictions
> +
> +=item *
> +Prohibit mix of dirs and files on a single 'cvsacl' line
> +[To simplify the logic and because this would be normal usage.]
> +
> +=item *
> +Correctly handle a mixture of branch restrictions within one work
> +directory
> +
> +=item *
> +$CVSROOT existence is checked too late
> +
> +=item *
> +Correctly handle the CVSROOT=:local:/... option (useful for 
> +interactive testing)
> +
> +=item *
> +Replacing shoddy "$universal_off" logic 
> +(Thanks to Karl-Konig Konigsson for pointing this out.)
> +
> +=back
> +
> +=head2 Enhancements
> +
> +=over 2
> +
> +=item *
> +Checks modules in the 'cvsacl' file for valid files and directories
> +
> +=item *
> +Accurately report restricted entries and their matching patterns
> +
> +=item *
> +Simplified and commented overly complex PERL REGEXPs for readability 
> +and maintainability
> +
> +=item *
> +Skip the rest of processing if a mismatch on portion of the 
> 'cvsacl' line
> +
> +=item *
> +Get rid of opaque "karma" messages in favor of user-friendly messages
> +that describe which user, file(s) and branch(es) were disallowed.
> +
> +=item *
> +Add optional 'restrict_msg' file for additional, site-specific 
> +restriction messages.
> +
> +=item *
> +Take a "-u" parameter for $USER from commit_prep so that the script
> +can do restrictions based on the client-side userId rather than the
> +server-side userId (usually 'cvs').
> +
> +(See discussion below on "Admin Setup" for more on this point.)
> +
> +=item *
> +Added a lot more debug trace 
> +
> +=item *
> +Tested these restrictions with concurrent use of pserver and SSH
> +access to model our transition from pserver to ext access.
> +
> +=item *
> +Added logging of restricted commit attempts.
> +Restricted commits can be sent to a default file:
> +$CVSROOT/CVSROOT/restrictlog or to one passed to the script
> +via the -f command parameter.
> +
> +=back
> +
> +=head2 ToDoS 
> +
> +=over 2
> +
> +=item *
> +Need to deal with pserver/SSH transition with conflicting umasks?
> +
> +=item *
> +Use a CPAN module to handle command parameters.
> +
> +=item *
> +Use a CPAN module to clone data structures.
> +
> +=back
> +
> +=head1 Version Information
> +
> +This is not offered as a fix to the original 'cvs_acls' 
> script since it 
> +differs substantially in goals and methods from the original 
> and there 
> +are probably a significant number of people out there that 
> still require 
> +the original version's functionality.
> +
> +The 'cvsacl' file flags of 'allow' and 'deny' were intentionally 
> +changed to 'allow' and 'deny' because there are enough differences 
> +between the original script's behavior and this one's that 
> we wanted to
> +make sure that users will rethink their 'cvsacl' file formats before
> +plugging in this newer script.
> +
> +Please note that there has been very limited cross-platform 
> testing of 
> +this script!!! (We did not have the time or resources to do 
> exhaustive
> +cross-platform testing.)
> +
> +It was developed and tested under Red Hat Linux 9.0 using PERL 5.8.0.
> +Additionally, it was built and tested under Red Hat Linux 7.3 using 
> +PERL 5.6.1.
> +
> +$Id: cvs_acls2,v 1.31 2004/03/17 06:01:13 peterc Exp $
> +
> +Version 2 is based on the 1.11.13 version of cvs_acls
> +peter.connolly@cnet.com (Peter Connolly) 
> +
> +  Access control lists for CVS.  dgg@ksr.com (David G. Grubbs)
> +  Branch specific controls added by voisine@bytemobile.com 
> (Aaron Voisine)
> +
> +=head1 Installation
> +
> +To use this program, do the following four things:
> +
> +0. Install PERL, version 5.6.1 or 5.8.0.
> +
> +1. Admin Setup:
> +
> +   There are two choices here. 
> +
> +   a) The first option is to use the $ENV{"USER"}, server-side userId
> +      (from the third column of your pserver 'passwd' file) 
> as the basis for 
> +      your restrictions.  In this case, you will (at a 
> minimum) want to set
> +      up a new "cvsadmin" userId and group on the pserver machine.  
> +      CVS administrators will then set up their 'passwd' 
> file entries to
> +      run either as "cvs" (for regular users) or as 
> "cvsadmin" (for power 
> +      users).  Correspondingly, your 'cvsacl' file will only 
> list 'cvs'
> +      and 'cvsadmin' as the userIds in the second column.
> +
> +      Commentary: A potential weakness of this is that the xinetd 
> +      cvspserver process will need to run as 'root' in order 
> to switch 
> +      between the 'cvs' and the 'cvsadmin' userIds.  Some 
> sysadmins don't
> +      like situations like this and may want to chroot the process.
> +      Talk to them about this point...
> +
> +   b) The second option is to use the client-side userId as 
> the basis for
> +      your restrictions.  In this case, all the xinetd 
> cvspserver processes 
> +      can run as userId 'cvs' and no 'root' userId is 
> required.  If you have
> +      a 'passwd' file that lists 'cvs' as the effective 
> run-time userId for
> +      all your users, then no changes to this file are 
> needed.  Your 'cvsacl'
> +      file will use the individual, client-side userIds in 
> its 2nd column.
> +
> +      As long as the userIds in pserver's 'passwd' file 
> match those userIds 
> +      that your Linux server know about, this approach is 
> ideal if you are 
> +      planning to move from pserver to SSH access at some 
> later point in time.
> +      Just by switching the CVSROOT var from 
> CVSROOT=:pserver:<userId>... to 
> +      CVSROOT=:ext:<userId>..., users can switch over to SSH 
> access without
> +      any other administrative changes.  When all users have 
> switched over to
> +      SSH, the inherently insecure xinetd cvspserver process 
> can be disabled.
> +      
> [https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_2.html#SEC32]
> +
> +      :TODO: The only potential glitch with the SSH approach 
> is the possibility 
> +      that each user can have differing umasks that might 
> interfere with one 
> +      another, especially during a transition from pserver 
> to SSH.  As noted
> +      in the ToDo section, this needs a good strategy and 
> set of tests for that 
> +      yet...
> +
> +2. Put two lines, as the *only* non-comment lines, in your 
> commitinfo file:
> +
> +   ALL $CVSROOT/CVSROOT/commit_prep 
> +   ALL $CVSROOT/CVSROOT/cvs_acls2 [-d][-u $USER ][-f <logfilename>]
> +
> +   where "-d" turns on debug trace
> +         "-u $USER" passes the client-side userId to cvs_acls2 
> +         "-f <logfilename"> overrides the default filename 
> used to log
> +                            restricted commit attempts.
> +
> +   (These are handled in the processArgs() subroutine.)
> +
> +If you are using client-side userIds to restrict access to your 
> +repository, make sure that they are in this order since the 
> commit_prep 
> +script is required in order to pass the $USER parameter.
> +
> +A final note about the repository matching pattern.  The 
> example above
> +uses "ALL" but note that this means that the cvs_acls2 
> script will run
> +for each and every commit in your repository.  Obviously, in a large
> +repository this adds up to a lot of overhead that may not be 
> necesary. 
> +A better strategy is to use a repository pattern that is 
> more specific 
> +to the areas that you wish to secure.
> +
> +3. Install this file as $CVSROOT/CVSROOT/cvs_acls2 and make 
> it executable.
> +
> +4. Create a file named CVSROOT/cvsacl and optionally add it to
> +   CVSROOT/checkoutlist and check it in.  See the CVS manual's
> +   administrative files section about checkoutlist.  Typically:
> +
> +   $ cvs checkout CVSROOT
> +   $ cd CVSROOT
> +   [ create the cvsacl file, include 'commitinfo' line ]
> +   [ add cvsacl to checkoutlist ]
> +   $ cvs add cvsacl
> +   $ cvs commit -m 'Added cvsacl for use with cvs_acls2.' 
> cvsacl checkoutlist
> +
> +Note: The format of the 'cvsacl' file is described in detail 
> immediately 
> +below but here is an important set up point:
> +
> +   Make sure to include a line like the following:
> +
> +     deny||CVSROOT/commitinfo CVSROOT/cvsacl
> +     allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl
> +
> +   that restricts access to commitinfo and cvsacl since this 
> would be one of
> +   the easiest "end runs" around this ACL approach. 
> ('commitinfo' has the 
> +   line that executes the cvs_acls2 script and, of course, all the 
> +   restrictions are in 'cvsacl'.)
> +
> +5. (Optional) Create a 'restrict_msg' file in the 
> $CVSROOT/CVSROOT directory.
> +   Whenever there is a restricted file or dir message, 
> cvs_acls2 will look 
> +   for this file and, if it exists, print its contents as 
> part of the 
> +   commit-denial message.  This gives you a chance to print 
> any site-specific
> +   information (e.g., who to call, what procedures to look 
> up,...) whenever
> +   a commit is denied.
> +
> +=head1 Format of the cvsacl file
> +
> +The 'cvsacl' file determines whether you may commit files.  
> It contains lines
> +read from top to bottom, keeping track of whether a given 
> user, repository
> +and branch combination is "allowed" or "denied."  The script 
> will assume 
> +"allowed" on all repository paths until 'allow' and 'deny' 
> rules change 
> +that default.  
> +
> +The normal pattern is to specify an 'deny' rule to turn off
> +access to ALL users, then follow it with a matching 'allow' 
> rule that will 
> +turn on access for a select set of users.  In the case of 
> multiple rules for
> +the same user, repository and branch, the last one takes precedence.
> +
> +Blank lines and lines with only comments are ignored.  Any 
> other lines not 
> +beginning with "allow" or "deny" are logged to the restrict_log file.
> +
> +Lines beginning with "allow" or "deny" are assumed to be 
> '|'-separated
> +triples: (All spaces and tabs are ignored in a line.)
> +
> +  {allow.*,deny.*} [|user,user,... [|repos,repos,... 
> [|branch,branch,...]]]
> +
> +   1. String starting with "allow" or "deny".
> +   2. Optional, comma-separated list of usernames.
> +   3. Optional, comma-separated list of repository pathnames.
> +      These are pathnames relative to $CVSROOT.  They can be 
> directories or
> +      filenames.  A directory name allows or restricts 
> access to all files and
> +      directories below it. One line can have either 
> directories or filenames
> +      but not both.
> +   4. Optional, comma-separated list of branch tags.
> +      If not specified, all branches are assumed. Use HEAD 
> to reference the
> +      main branch.
> +
> +Example:  (Note: No in-line comments.)
> +
> +   # ----- Make whole repository unavailable.
> +   deny
> +
> +   # ----- Except for user "dgg".
> +   allow|dgg
> +
> +   # ----- Except when "fred" or "john" commit to the 
> +   #       module whose repository is "bin/ls"
> +   allow|fred, john|bin/ls
> +
> +   # ----- Except when "ed" commits to the "stable" 
> +   #       branch of the "bin/ls" repository
> +   allow|ed|/bin/ls|stable
> +
> +=head1 Program Logic
> +
> +CVS passes to @ARGV an absolute directory pathname (the repository
> +appended to your $CVSROOT variable), followed by a list of filenames
> +within that directory that are to be committed.
> +
> +The script walks through the 'cvsacl' file looking for matches on 
> +the username, repository and branch.
> +
> +A username match is simply the user's name appearing in the second
> +column of the cvsacl line in a space-or-comma separate list. If
> +blank, then any user will match.
> +
> +A repository match:
> +
> +=over 2
> +
> +=item *
> +Each entry in the modules section of the current 'cvsacl' line is 
> +examined to see if it is a dir or a file. The line must have 
> +either files or dirs, but not both. (To simplify the logic.)
> +
> +=item *
> +If neither, then assume the 'cvsacl' file was set up in error and
> +skip that 'allow' line.
> +
> +=item *
> +If a dir, then each dir pattern is matched separately against the 
> +beginning of each of the committed files in @ARGV. 
> +
> +=item *
> +If a file, then each file pattern is matched exactly against each
> +of the files to be committed in @ARGV.
> +
> +=item *
> +Repository and branch must BOTH match together. This is to cover
> +the use case where a user has multiple branches checked out in
> +a single work directory. Commit files can be from different
> +branches.
> +
> +A branch match is either:
> +
> +=over 4
> +
> +=item *
> +When no branches are listed in the fourth column. ("Match any.")
> +
> +=item *
> +All elements from the fourth column are matched against each of 
> +the tag names for $ARGV[1..$#ARGV] found in the %branches file.
> +
> +=back
> +
> +=item *
> +'allow' match remove that match from the tally map.
> +
> +=item *
> +Restricted ('deny') matches are saved in the %repository_matches 
> +table.
> +
> +=item *
> +If there is a match on user, repository and branch:
> +
> +  If repository, branch and user match
> +    if 'deny'
> +      add %repository_matches entries to %restricted_entries
> +    else if 'allow'
> +      remove %repository_matches entries from %restricted_entries
> +
> +=item *
> +At the end of all the 'cvsacl' line checks, check to see if there
> +are any entries in the %restricted_entries.  If so, then deny the
> +commit.
> +
> +=back
> +
> +=head2 Pseudocode
> +
> +     read CVS/Entries file and create branch{file}->{branch} 
> hash table
> +   + for each 'allow' and 'deny' line in the 'cvsacl' file:
> +   |   user match?   
> +   |     - Yes: set $user_match       = 1;
> +   |   repository and branch match?
> +   |     - Yes: add to %repository_matches;
> +   |   did user, repository match?
> +   |     - Yes: if 'deny' then 
> +   |                add %repository_matches -> %restricted_entries
> +   |            if 'allow'   then 
> +   |                remove %repository_matches <- %restricted_entries
> +   + end for loop
> +     any saved restrictions?
> +       no:  exit, 
> +            set exit code allowing commits and exit
> +       yes: report restrictions, 
> +            set exit code prohibiting commits and exit
> +
> +=head2 Sanity Check
> +
> +  1) file allow trumps a dir deny
> +     deny||java/lib
> +     allow||java/lib/README
> +  2) dir allow can undo a file deny
> +     deny||java/lib/README
> +     allow||java/lib
> +  3) file deny trumps a dir allow
> +     allow||java/lib
> +     deny||java/lib/README
> +  4) dir deny trumps a file allow
> +     allow||java/lib/README
> +     deny||java/lib
> +  ... so last match always takes precedence
> +
> +=cut
> +
> +$debug = 0;                     # Set to 1 for debug messages
> +
> +%repository_matches = ();       # hash of match file and 
> pattern from 'cvsacl'
> +                                # repository_matches --> 
> [branch, matching-pattern]
> +                                # (Used during module/branch 
> matching loop)
> +
> +%restricted_entries = ();       # hash table of restricted 
> commit files (from @ARGV)
> +                                # restricted_entries --> branch
> +                                # (If user/module/branch all 
> match on an 'deny'
> +                                #  line, then entries added 
> to this map.)
> +
> +%branch;                        # hash table of key: commit 
> file; value: branch
> +                                # Built from 
> ".../CVS/Entries" file of directory 
> +                                # currently being examined
> +
> +# 
> --------------------------------------------------------------
> -- get CVSROOT
>  $cvsroot = $ENV{'CVSROOT'};
> -$availfile = $cvsroot . "/CVSROOT/avail";
> -$entries = "CVS/Entries";
> -$myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"});
> +die "Must set CVSROOT\n" if !$cvsroot;
> +if ($cvsroot =~ /:([\/\w]*)$/) { # Filter ":pserver:", 
> ":local:"-type prefixes
> +    $cvsroot = $1; 
> +}
>  
> +# 
> ------------------------------------------------------------- 
> set file paths
> +$entries = "CVS/Entries";                                # 
> client-side file???
> +$cvsaclfile = $cvsroot . "/CVSROOT/cvsacl";
> +$restrictfile = $cvsroot . "/CVSROOT/restrict_msg";
> +$restrictlog = $cvsroot . "/CVSROOT/restrict_log";
> +
> +# 
> --------------------------------------------------------------
> - process args
> +$user_name = processArgs(\@ARGV);
> +
> +print("$$ \@ARGV after processArgs is: @ARGV.\n") if $debug;
> +print("$$ ========== Begin $PROGRAM_NAME for \"$ARGV[0]\" 
> repository. ========== \n") if $debug;
> +
> +# 
> --------------------------------------------------------------
> - filter @ARGV
>  eval "print STDERR \$die='Unknown parameter $1\n' if 
> !defined \$$1; \$$1=\$';"
>      while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
> -exit 255 if $die;            # process any variable=value switches
> +exit 255 if $die;                     # process any 
> variable=value switches
>  
> -die "Must set CVSROOT\n" if !$cvsroot;
> -($repos = shift) =~ s:^$cvsroot/::;
> -grep($_ = $repos . '/' . $_, @ARGV);
> +print("$$ \@ARGV after shift processing contains:",join("\, 
> ",@ARGV),".\n") if $debug;
> +
> +# 
> --------------------------------------------------------------
> -- get cvsroot
> +($repository = shift) =~ s:^$cvsroot/::;
> +grep($_ = $repository . '/' . $_, @ARGV);
>  
> -print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== 
> ",@ARGV),"\n" if $debug;
> +print("$$ \$cvsroot is: $cvsroot.\n") if $debug;
> +print "$$ Repos: $repository\n","$$ ==== ",join("\n$$ ==== 
> ",@ARGV),"\n" if $debug;
>  
> -$exit_val = 0;                               # Good Exit value
> +$exit_val = 0;                                # presume good 
> exit value for commit
>  
> -$universal_off = 0;
> +# 
> --------------------------------------------------------------
> --------------
> +# ---------------------------------- create hash table 
> $branch{file -> branch}
> +# 
> --------------------------------------------------------------
> --------------
>  
> -my %branch;
> -my $f;
> +# Here's a typical Entries file:
> +#
> +#   /checkoutlist/1.4/Wed Feb  4 23:51:23 2004//
> +#   /cvsacl/1.3/Tue Feb 24 23:05:43 2004//
> +#   ...
> +#   /verifymsg/1.1/Fri Mar 16 19:56:24 2001//
> +#   D/backup////
> +#   D/temp////
>  
>  open(ENTRIES, $entries) || die("Cannot open $entries.\n");
> +print("$$ File / Branch\n") if $debug;
> +my $i = 0;
>  while(<ENTRIES>) {
>      chop;
> -    next if /^\s*$/;
> -    if(m|^[^/]*/([^/]*)/(?:[^/]*/)*[^/]?([^/]*)$|) {
> -     $branch{$repos . '/' . $1} = ($2) ? $2 : "HEAD"; 
> -     print "$$ $1/$2\n" if $debug;
> +    next if /^\s*$/;                    # Skip blank lines
> +    $i = $i + 1;
> +    if (m|
> +          /                             # 1st slash
> +          ([\w.]*)                      # file name -> $1
> +          /                             # 2nd slash
> +          .*                            # revision number
> +          /                             # 3rd slash
> +          .*                            # date and time
> +          /                             # 4th slash
> +          .*                            # keyword
> +          /                             # 5th slash
> +          T?                            # 'T' constant
> +          (\w*)                         # branch    -> #2
> +           |x) {
> +     $branch{$repository . '/' . $1} = ($2) ? $2 : "HEAD"; 
> +     print "$$ CVS Entry $i: $1/$2\n" if $debug;
>      }
>  }
>  close(ENTRIES);
>  
> -open (AVAIL, $availfile) || exit(0); # It is ok for avail 
> file not to exist
> -while (<AVAIL>) {
> +# 
> --------------------------------------------------------------
> --------------
> +# ------------------------------------- evaluate each active 
> line from 'cvsacl'
> +# 
> --------------------------------------------------------------
> --------------
> +open (CVSACL, $cvsaclfile) || exit(0);       # It is ok for 
> cvsacl file not to exist
> +while (<CVSACL>) {
>      chop;
> -    next if /^\s*\#/;
> -    next if /^\s*$/;
> -    ($flagstr, $u, $m, $b) = split(/[\s,]*\|[\s,]*/, $_);
> -
> -    # Skip anything not starting with "avail" or "unavail" 
> and complain.
> -    (print "Bad avail line: $_\n"), next
> -     if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/);
> -
> -    # Set which bit we are playing with. ('0' is OK == Available).
> -    $flag = (($& eq "avail") ? 0 : 1);
> -
> -    # If we find a "universal off" flag (i.e. a simple 
> "unavail") remember it
> -    $universal_off = 1 if ($flag && !$u && !$m && !$b);
> -
> -    # $myname considered "in user list" if actually in list 
> or is NULL
> -    $in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u)));
> -    print "$$ \$myname($myname) in user list: $_\n" if 
> $debug && $in_user;
> -
> -    # Module matches if it is a NULL module list in the 
> avail line.  If module
> -    # list is not null, we check every argument combination.
> -    if (!($in_repo = !$m)) {
> -     my @tmp = split(/[\s,]+/,$m);
> -     for $j (@tmp) {
> -         # If the repos from avail is a parent(or equal) dir 
> of $repos, OK
> -         $in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//);
> -     }
> -     if (!$in_repo) {
> -         $in_repo = 1;
> -         for $j (@ARGV) {
> -             last if !($in_repo = grep ($_ eq $j, @tmp));
> -         }
> -     }
> +    next if /^\s*\#/;                               # skip comments
> +    next if /^\s*$/;                                # skip 
> blank lines
> +    # --------------------------------------------- parse 
> current 'cvsacl' line
> +    print("$$ ==========\n$$ Processing \'cvsacl\' line: 
> $_.\n") if $debug;
> +    ($cvsacl_flag, $cvsacl_userIds, $cvsacl_modules, 
> $cvsacl_branches) = split(/[\s,]*\|[\s,]*/, $_);
> +
> +    # ------------------------------ Validate 'allow' or 
> 'deny' line prefix
> +    if ($cvsacl_flag !~ /^allow/ && $cvsacl_flag !~ /^deny/) {
> +     print ("Bad cvsacl line: $_\n") if $debug;
> +     $log_text = sprintf "Bad cvsacl line: %s", $_; 
> +     write_restrictlog_record($log_text);
> +        next;
>      }
> -    print "$$ \$repos($repos) in repository list: $_\n" if 
> $debug && $in_repo;
>  
> -    # Branch matches if it is in the branch list in the 
> avail line, the branch
> -    # list is NULL, or there is no branch and HEAD is in the 
> branch list.
> -    if(!($in_branch = !$b)) {
> -     @bls = split (/[\s,]+/,$b);
> +    # -------------------------------------------------- 
> init loop match flags
> +    $user_match = 0;
> +    %repository_matches = ();
> +
> +    # 
> --------------------------------------------------------------
> ----------
> +    # 
> ---------------------------------------------------------- 
> user matching
> +    # 
> --------------------------------------------------------------
> ----------
> +    # $user_name considered "in user list" if actually in 
> list or is NULL
> +    $user_match = (!$cvsacl_userIds || grep ($_ eq 
> $user_name, split(/[\s,]+/,$cvsacl_userIds)));
> +    print "$$ \$user_name: $user_name \$user_match match 
> flag is: $user_match.\n" if $debug;
> +    if (!$user_match) {
> +     next;                            # no match, skip to 
> next 'cvsacl' line
> +    }
>  
> -     for $j (@ARGV) {
> -        $f = $j;
> -        last if !($in_branch = grep($_ eq $branch{$j}, @bls)); 
> +    # 
> --------------------------------------------------------------
> ----------
> +    # ---------------------------------------------------- 
> repository matching
> +    # 
> --------------------------------------------------------------
> ----------
> +    if (!$cvsacl_modules) {                  # blank module 
> list = all modules
> +     if (!$cvsacl_branches) {            # blank branch list 
> = all branches
> +         print("$$ Adding all modules to 
> \%repository_matches; null " . 
> +                  "\$cvsacl_modules and 
> \$cvsacl_branches.\n") if $debug;
> +         for $commit_object (@ARGV) {
> +             $repository_matches{$commit_object} = 
> [$branch{$commit_object}, $cvsacl_modules];
> +                print("$$ \$repository_matches{$commit_object} = " .
> +                      "[$branch{$commit_object}, 
> $cvsacl_modules].\n") if $debug;
> +         }
> +     }
> +     else {                            # need to check for 
> repository match
> +         @branch_list = split (/[\s,]+/,$cvsacl_branches);
> +         print("$$ Branches from \'cvsacl\' record: ", 
> join(", ",@branch_list),".\n") if $debug;
> +         for $commit_object (@ARGV) {
> +             if (grep($branch{$commit_object}, @branch_list)) {
> +                 $repository_matches{$commit_object} = 
> [$branch{$commit_object}, $cvsacl_modules];
> +                 print("$$ \$repository_matches{$commit_object} = " .
> +                          "[$branch{$commit_object}, 
> $cvsacl_modules].\n") if $debug;
> +             }
> +         }
> +     }
> +    }
> +    else {
> +     # ----------------------------------- check every 
> argument combination
> +     # parse 'cvsacl' modules to array
> +     my @module_list = split(/[\s,]+/,$cvsacl_modules);
> +        # ------------- Check all modules in list for either 
> file or directory
> +     my $fileType = "";
> +     if (($fileType = checkFileness(@module_list)) eq "") {
> +         next;                                        # skip 
> bad file types
> +     }
> +     # ---------- Check each combination of 'cvsacl' modules 
> vs. @ARGV files
> +     print("$$ Checking matches for \@module_list: ", 
> join("\, ",@module_list), ".\n") if $debug;
> +     # loop thru all command-line commit objects
> +        for $commit_object (@ARGV) {              
> +         # loop thru all modules on 'cvsacl' line
> +            for $cvsacl_module (@module_list) { 
> +             print("$$ Is \'cvsacl\': $cvsacl_modules 
> pattern in: \@ARGV " . 
> +                      "\$commit_object: $commit_object?\n") 
> if $debug;
> +             # Do match of beginning of $commit_object
> +             checkModuleMatch($fileType, $commit_object, 
> $cvsacl_module);
> +         } # end for commit objects
> +     } # end for cvsacl modules
> +    } # end if
> +
> +    print("$$ Matches for: \%repository_matches: ", join("\, 
> ", (keys %repository_matches)), ".\n") if $debug;
> +
> +    # 
> --------------------------------------------------------------
> ----------
> +    # ----------------------------------------------------- 
> setting exit value
> +    # 
> --------------------------------------------------------------
> ----------
> +    if ($user_match && %repository_matches) {
> +        print("$$ An \"$cvsacl_flag\" match on User(s): 
> $cvsacl_userIds; Module(s):" .
> +              " $cvsacl_modules; Branch(es): 
> $cvsacl_branches.\n") if $debug;
> +     if ($cvsacl_flag eq "deny") {
> +         # Add all matches to the hash of restricted modules
> +         foreach $commitFile (keys %repository_matches) {
> +             print("$$ Adding \%repository_matches entry: 
> $commitFile.\n") if $debug;
> +             $restricted_entries{$commitFile} = 
> $repository_matches{$commitFile}[0];
> +         }
> +     }
> +     else {
> +         # Remove all matches from the restricted modules hash
> +         foreach $commitFile (keys %repository_matches) {
> +             print("$$ Removing \%repository_matches entry: 
> $commitFile.\n") if $debug;
> +             delete $restricted_entries{$commitFile};
> +         }
>       }
>      }
> -    print "$$ \$branch($branch{$f}) in branch list: $_\n"
> -     if $debug && $in_branch;
> +    print "$$ ==== End of processing for \'cvsacl\' line: 
> $_.\n" if $debug;
> +}
> +close(CVSACL);
>  
> -    $exit_val = $flag if ($in_user && $in_repo && $in_branch);
> -    print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = 
> $flag\n" if $debug;
> +# 
> --------------------------------------------------------------
> --------------
> +# --------------------------------------- determine final 
> 'commit' disposition
> +# 
> --------------------------------------------------------------
> -------------- 
> +if (%restricted_entries) {                           # any 
> restricted entries?
> +    $exit_val = 1;                                           
>    # don't commit
> +    print("**** Access denied: Insufficient authority for 
> user: '$user_name\' " .
> +          "to commit to \'$repository\'.\n**** Contact CVS 
> Administrators if " .
> +          "you require update access to these directories or 
> files.\n");
> +    print("**** file(s)/dir(s) restricted were:\n\t", 
> join("\n\t",keys %restricted_entries), "\n");
> +    printOptionalRestrictionMessage();
> +    write_restrictlog();
> +}
> +elsif (!$exit_val && $debug) {
> +    print "**** Access allowed: Sufficient authority for commit.\n";
>  }
> -close(AVAIL);
> +
>  print "$$ ==== \$exit_val = $exit_val\n" if $debug;
> -print "**** Access denied: Insufficient Karma 
> ($myname|$repos|$branch{$f})\n"
> -     if $exit_val;
> -print "**** Access allowed: Personal Karma exceeds 
> Environmental Karma.\n"
> -     if $universal_off && !$exit_val;
>  exit($exit_val);
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# 
> --------------------------------------------------------------
>  end of "main"
> +# 
> --------------------------------------------------------------
> --------------
> +
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# -------------------------------------------------------- 
> process script args
> +# 
> --------------------------------------------------------------
> --------------
> +sub processArgs {
> +
> +# This subroutine is passed a reference to @ARGV. 
> +
> +# If @ARGV contains a "-u" entry, use that as the effective 
> userId.  In this 
> +# case, the userId is the client-side userId that has been 
> passed to this 
> +# script by the commit_prep script.  (This is why the 
> commit_prep script must 
> +# be placed *before* the cvs_acls2 script in the commitinfo 
> admin file.)
> +
> +# Otherwise, pull the userId from the server-side environment.
> +
> +    my $userId = "";
> +    my ($argv) = shift;             # pick up ref to @ARGV
> +    my @argvClone = ();             # immutable copy for foreach loop
> +    for ($i=0; $i<(scalar @{$argv}); $i++) {
> +     $argvClone[$i]=$argv->[$i]; 
> +    }
> +
> +    print("$$ \@_ to processArgs is: @_.\n") if $debug;
> +
> +    # Parse command line arguments (file list is seen as one arg)
> +    foreach $arg (@argvClone) {
> +     print("$$ \$arg for processArgs loop is: $arg.\n") if $debug;
> +     # Set $debug flag?
> +     if ($arg eq '-d') {
> +         shift @ARGV;
> +         $debug = 1;
> +         print("$$ \$debug flag set on.\n") if $debug;
> +         print STDERR "Debug turned on...\n";
> +     }
> +     # Passing in a client-side userId?
> +     elsif ($arg eq '-u') {
> +         shift @ARGV;
> +         $userId = shift @ARGV;
> +         print("$$ client-side \$userId set to: $userId.\n") 
> if $debug;
> +     } 
> +     # An override for the default restrictlog file?
> +     elsif ($arg eq '-f') {
> +         shift @ARGV;
> +         $restrictlog = shift @ARGV;
> +     } 
> +     else {
> +         next;
> +     }
> +    }
> +
> +    # No client-side userId passed? then get from server env
> +    if (!$userId) {
> +        $userId = $ENV{"USER"} if !($userId = $ENV{"LOGNAME"});
> +         print("$$ server-side \$userId set to: $userId.\n") 
> if $debug;
> +    }
> +
> +    print("$$ processArgs returning \$userId: $userId.\n") if $debug;
> +    return $userId;
> +
> +}
> +
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# --------------------- Check all modules in list for either 
> file or directory
> +# 
> --------------------------------------------------------------
> --------------
> +sub checkFileness {
> +
> +# Module patterns on the 'cvsacl' record can be files or 
> directories. 
> +# If it's a directory, we pattern-match the directory name 
> from 'cvsacl' 
> +# against the left side of the committed filename to see if 
> the file is in 
> +# that hierarchy.  By contrast, files use an explicit match. 
>  If the entries
> +# are neither files nor directories, then the cvsacl file 
> has been set up
> +# incorrectly; we return a "" and the caller skips that line 
> as invalid.
> +#
> +# This function determines whether the entries on the 
> 'cvsacl' record are all
> +# directories or all files; it cannot be a mixture.  This 
> restriction put in
> +# to simplify the logic (without taking away much functionality).
> +
> +    my @module_list = @_;
> +    print("$$ Checking \"fileness\" or \"dir-ness\" for 
> \@module_list entries.\n") if $debug;
> +    print("$$     Entries are: ", join("\, ",@module_list), 
> ".\n") if $debug;
> +    my $filetype = "";
> +    for $cvsacl_module (@module_list) {
> +        my $reposDirName = $cvsroot . '/' . $cvsacl_module;
> +        my $reposFileName = $reposDirName . "\,v";
> +        print("$$ In checkFileness: \$reposDirName: 
> $reposDirName; \$reposFileName: $reposFileName.\n") if $debug;
> +        if (((-d $reposDirName) && ($filetype eq "file")) || 
> ((-f $reposFileName) && ($filetype eq "dir"))) {
> +            print("Can\'t mix files and directories on 
> single \'cvsacl\' file record; skipping entry.\n");
> +         print("    Please contact a CVS administrator.\n");
> +         $filetype = "";
> +         last;
> +        }
> +        elsif (-d $reposDirName) { 
> +            $filetype = "dir";
> +         print("$$ $reposDirName is a directory.\n") if $debug;
> +     }
> +        elsif (-f $reposFileName) {
> +            $filetype = "file";
> +         print("$$ $reposFileName is a regular file.\n") if $debug;
> +     }
> +        else {
> +            print("***** Item to commit was neither a 
> regular file nor a directory.\n");
> +         print("***** Current \'cvsacl\' line ignored.\n");
> +         print("***** Possible problem with \'cvsacl\' admin 
> file. Please contact a CVS administrator.\n");
> +         $filetype = "";
> +         $text = sprintf("Module entry on cvsacl line: %s is 
> not a valid file or directory.\n", $cvsacl_module);
> +         write_restrictlog_record($text);
> +         last;
> +        } # end if
> +    } # end for
> +
> +    print("$$ checkFileness will return \$filetype: 
> $filetype.\n") if $debug;
> +    return $filetype;
> +}
> +
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# ----------------------------------------------------- 
> check for module match
> +# 
> --------------------------------------------------------------
> --------------
> +sub checkModuleMatch {
> +
> +# This subroutine checks for a match between the directory 
> or file pattern 
> +# specified in the 'cvsacl' file (i.e., $cvsacl_modules) 
> versus the commit file 
> +# objects passed into the script via @ARGV (i.e., $commit_object). 
> +
> +# The directory pattern only has to match the beginning 
> portion of the commit 
> +# file's name for a match since all files under that 
> directory are considered 
> +# a match. File patterns must exactly match.
> +
> +# Since (theoretically, if not normally in practice) a 
> working directory can
> +# contain a mixture of files from different branches, this 
> routine checks to 
> +# see if there is also a match on branch before considering the file 
> +# comparison a match.
> +
> +    my $match_flag = "";
> +
> +    print("$$ \@_ in checkModuleMatch is: @_.\n") if $debug;
> +    my ($type,$commit_object,$cvsacl_module) = @_;
> +
> +    if ($type eq "file") {             # Do exact file match 
> of $commit_object
> +     if ($commit_object eq $cvsacl_module) {
> +         $match_flag = "file";
> +     }                        # Do dir match at beginning of 
> $commit_object
> +    }
> +    elsif ($commit_object =~ /^$cvsacl_module\//) {
> +        $match_flag = "dir";
> +    }
> +
> +    if ($match_flag) {
> +     print("$$ \$repository: $repository matches 
> \$commit_object: $commit_object.\n") if $debug;
> +     if (!$cvsacl_branches) {             # empty branch 
> pattern matches all
> +         print("$$ blank \'cvsacl\' branch matches all 
> commit files.\n") if $debug;
> +         $repository_matches{$commit_object} = 
> [$branch{$commit_object}, $cvsacl_module];
> +         print("$$ \$repository_matches{$commit_object} = 
> [$branch{$commit_object}, $cvsacl_module].\n") if $debug;
> +     }
> +     else {                             # otherwise check 
> branch hash table
> +         @branch_list = split (/[\s,]+/,$cvsacl_branches);
> +         print("$$ Branches from \'cvsacl\' record: ", 
> join(", ",@branch_list),".\n") if $debug;
> +         if (grep(/$branch{$commit_object}/, @branch_list)) {
> +             $repository_matches{$commit_object} = 
> [$branch{$commit_object}, $cvsacl_module];
> +             print("$$ \$repository_matches{$commit_object} 
> = [$branch{$commit_object}, " .
> +                      "$cvsacl_module].\n") if $debug;
> +         }
> +     }
> +    }
> +
> +}
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# ------------------------------------------------------- 
> check for file match
> +# 
> --------------------------------------------------------------
> --------------
> +sub printOptionalRestrictionMessage {
> +
> +# This subroutine optionally prints site-specific file 
> restriction information
> +# whenever a restriction condition is met.  If the file 
> 'restrict_msg' does 
> +# not exist, the routine immediately exits.  If there is a 
> 'restrict_msg' file
> +# then all the contents are printed at the end of the 
> standard restriction 
> +# message.
> +
> +# As seen from examining the definition of $restrictfile, 
> the default filename
> +# is: $CVSROOT/CVSROOT/restrict_msg.
> +
> +    open (RESTRICT, $restrictfile) || return;        # It is 
> ok for cvsacl file not to exist
> +    while (<RESTRICT>) {
> +     chop;
> +     # print out each line
> +     print("**** $_\n");
> +    }
> +
> +}
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# ---------------------------------------------------------- 
> write log message
> +# 
> --------------------------------------------------------------
> --------------
> +sub write_restrictlog {
> +
> +# This subroutine iterates through the list of restricted 
> entries and logs 
> +# each one to the error logfile.
> +
> +    # write each line in @text out separately
> +    foreach $commitfile (keys %restricted_entries) {
> +     $log_text = sprintf "Commit attempt by: %s for: %s on 
> branch: %s", 
> +                         $user_name, $commitfile, 
> $branch{$commitfile};
> +     write_restrictlog_record($log_text);
> +    }
> +
> +}
> +
> +# 
> --------------------------------------------------------------
> --------------
> +# ---------------------------------------------------------- 
> write log message
> +# 
> --------------------------------------------------------------
> --------------
> +sub write_restrictlog_record {
> +
> +# This subroutine receives a scalar string and writes it out to the 
> +# $restrictlog file as a separate line. Each line is 
> prepended with the date 
> +# and time in the format: "2004/01/30 12:00:00 ".
> +
> +    $text = shift;
> +
> +    # return quietly if there is a problem opening the log file.
> +    open(FILE, ">>$restrictlog") || return;
> +
> +    (@time) = localtime();
> +
> +    # write each line in @text out separately
> +    $log_record = sprintf "%04d/%02d/%02d %02d:%02d:%02d %s.\n", 
> +                      $time[5]+1900, $time[4]+1, $time[3], 
> $time[2], $time[1], $time[0], $text;
> +    print FILE $log_record;
> +    print("$$ restrict_log record being written: $log_record 
> to $restrictlog.\n") if $debug;
> +    
> +    close(FILE);
> +}
> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]