[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: disabling branch commit
From: |
Mark D. Baushke |
Subject: |
Re: disabling branch commit |
Date: |
Mon, 13 Sep 2004 10:16:55 -0700 |
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_">Handle 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">Correctly handle
multiple, specific filename restrictions</a></strong><br>
+</li>
+<li><strong><a
name="item_prohibit_mix_of_dirs_and_files_on_a_single_'cvsa">Prohibit 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_">Checks 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">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.</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">Need 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.">Use
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.html#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">If
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">If
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._">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></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">'allow' 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">If
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);
+}
- Re: disabling branch commit,
Mark D. Baushke <=