[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 <logfile>]</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
> <peter.connolly@cnet.com>
> +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:<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.
> + [<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
> <logfilename>]</pre>
> +<pre> where "-d" turns on debug trace
> + "-u $USER" passes the client-side userId to cvs_acls
> + "-f <logfilename"> 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}->{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</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);
> +}
>