/[cvs]/ccvs/contrib/cvs_acls.pl
ViewVC logotype

Diff of /ccvs/contrib/cvs_acls.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.4 by dprice, Fri Jan 5 18:18:18 2001 UTC revision 1.5 by dprice, Thu Oct 6 20:25:12 2005 UTC
# Line 1  Line 1 
1  #! xPERL_PATHx  #! @PERL@ -T
2  # -*-Perl-*-  # -*-Perl-*-
3    
4    # Copyright (C) 1994-2005 The Free Software Foundation, Inc.
5    
6    # This program is free software; you can redistribute it and/or modify
7    # it under the terms of the GNU General Public License as published by
8    # the Free Software Foundation; either version 2, or (at your option)
9    # any later version.
10  #  #
11  # Access control lists for CVS.  dgg@ksr.com (David G. Grubbs)  # This program is distributed in the hope that it will be useful,
12  #  # but WITHOUT ANY WARRANTY; without even the implied warranty of
13  # CVS "commitinfo" for matching repository names, running the program it finds  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  # on the same line.  More information is available in the CVS man pages.  # GNU General Public License for more details.
15  #  
16  # ==== INSTALLATION:  ###############################################################################
17  #  ###############################################################################
18  # 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/CVSROOT/avail.  
 #  
 # ==== 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,...]]  
 #  
 #    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.  
 #  
 # 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"  
 #  
 # 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 both  
 #       the username and repository.  
19  #  #
20  #       A username match is simply the user's name appearing in the second  # THIS SCRIPT IS PROBABLY BROKEN.  REMOVING THE -T SWITCH ON THE #! LINE ABOVE
21  #       column of the avail line in a space-or-comma separate list.  # WOULD FIX IT, BUT THIS IS INSECURE.  WE RECOMMEND FIXING THE ERRORS WHICH THE
22    # -T SWITCH WILL CAUSE PERL TO REPORT BEFORE RUNNING THIS SCRIPT FROM A CVS
23    # SERVER TRIGGER.  PLEASE SEND PATCHES CONTAINING THE CHANGES YOU FIND
24    # NECESSARY TO RUN THIS SCRIPT WITH THE TAINT-CHECKING ENABLED BACK TO THE
25    # <@PACKAGE_BUGREPORT@> MAILING LIST.
26  #  #
27  #       A repository match is either:  # For more on general Perl security and taint-checking, please try running the
28  #               - One element of the third column matches $ARGV[0], or some  # `perldoc perlsec' command.
 #                 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.  
29  #  #
30    ###############################################################################
31    ###############################################################################
32    ###############################################################################
33    
34    =head1 Name
35    
36    cvs_acls - Access Control List for CVS
37    
38    =head1 Synopsis
39    
40    In 'commitinfo':
41    
42      repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER][-f <logfile>]
43    
44    where:
45    
46      -d  turns on debug information
47      -u  passes the client-side userId to the cvs_acls script
48      -f  specifies an alternate filename for the restrict_log file
49    
50    In 'cvsacl':
51    
52      {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
53    
54    where:
55    
56      allow|deny - allow: commits are allowed; deny: prohibited
57      user          - userId to be allowed or restricted
58      repos         - file or directory to be allowed or restricted
59      branch        - branch to be allowed or restricted
60    
61    See below for examples.
62    
63    =head1 Licensing
64    
65    cvs_acls - provides access control list functionality for CVS
66      
67    Copyright (c) 2004 by Peter Connolly <peter.connolly@cnet.com>  
68    All rights reserved.
69    
70    This program is free software; you can redistribute it and/or modify  
71    it under the terms of the GNU General Public License as published by  
72    the Free Software Foundation; either version 2 of the License, or  
73    (at your option) any later version.
74    
75    This program is distributed in the hope that it will be useful,  
76    but WITHOUT ANY WARRANTY; without even the implied warranty of  
77    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
78    GNU General Public License for more details.  
79    
80    You should have received a copy of the GNU General Public License  
81    along with this program; if not, write to the Free Software  
82    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
83    
84    =head1 Description
85    
86    This script--cvs_acls--is invoked once for each directory within a
87    "cvs commit". The set of files being committed for that directory as
88    well as the directory itself, are passed to this script.  This script
89    checks its 'cvsacl' file to see if any of the files being committed
90    are on the 'cvsacl' file's restricted list.  If any of the files are
91    restricted, then the cvs_acls script passes back an exit code of 1
92    which disallows the commits for that directory.  
93    
94    Messages are returned to the committer indicating the file(s) that
95    he/she are not allowed to committ.  Additionally, a site-specific
96    set of messages (e.g., contact information) can be included in these
97    messages.
98    
99    When a commit is prohibited, log messages are written to a restrict_log
100    file in $CVSROOT/CVSROOT.  This default file can be redirected to
101    another destination.
102    
103    The script is triggered from the 'commitinfo' file in $CVSROOT/CVSROOT/.
104    
105    =head1 Enhancements
106    
107    This section lists the bug fixes and enhancements added to cvs_acls
108    that make up the current cvs_acls.
109    
110    =head2 Fixed Bugs
111    
112    This version attempts to get rid the following bugs from the
113    original version of cvs_acls:
114    
115    =over 2
116    
117    =item *
118    Multiple entries on an 'cvsacl' line will be matched individually,
119    instead of requiring that all commit files *exactly* match all
120    'cvsacl' entries. Commiting a file not in the 'cvsacl' list would
121    allow *all* files (including a restricted file) to be committed.
122    
123    [IMO, this basically made the original script unuseable for our
124    situation since any arbitrary combination of committed files could
125    avoid matching the 'cvsacl's entries.]
126    
127    =item *
128    Handle specific filename restrictions. cvs_acls didn't restrict
129    individual files specified in 'cvsacl'.
130    
131    =item *
132    Correctly handle multiple, specific filename restrictions
133    
134    =item *
135    Prohibit mix of dirs and files on a single 'cvsacl' line
136    [To simplify the logic and because this would be normal usage.]
137    
138    =item *
139    Correctly handle a mixture of branch restrictions within one work
140    directory
141    
142    =item *
143    $CVSROOT existence is checked too late
144    
145    =item *
146    Correctly handle the CVSROOT=:local:/... option (useful for
147    interactive testing)
148    
149    =item *
150    Replacing shoddy "$universal_off" logic
151    (Thanks to Karl-Konig Konigsson for pointing this out.)
152    
153    =back
154    
155    =head2 Enhancements
156    
157    =over 2
158    
159    =item *
160    Checks modules in the 'cvsacl' file for valid files and directories
161    
162    =item *
163    Accurately report restricted entries and their matching patterns
164    
165    =item *
166    Simplified and commented overly complex PERL REGEXPs for readability
167    and maintainability
168    
169    =item *
170    Skip the rest of processing if a mismatch on portion of the 'cvsacl' line
171    
172    =item *
173    Get rid of opaque "karma" messages in favor of user-friendly messages
174    that describe which user, file(s) and branch(es) were disallowed.
175    
176    =item *
177    Add optional 'restrict_msg' file for additional, site-specific
178    restriction messages.
179    
180    =item *
181    Take a "-u" parameter for $USER from commit_prep so that the script
182    can do restrictions based on the client-side userId rather than the
183    server-side userId (usually 'cvs').
184    
185    (See discussion below on "Admin Setup" for more on this point.)
186    
187    =item *
188    Added a lot more debug trace
189    
190    =item *
191    Tested these restrictions with concurrent use of pserver and SSH
192    access to model our transition from pserver to ext access.
193    
194    =item *
195    Added logging of restricted commit attempts.
196    Restricted commits can be sent to a default file:
197    $CVSROOT/CVSROOT/restrictlog or to one passed to the script
198    via the -f command parameter.
199    
200    =back
201    
202    =head2 ToDoS
203    
204    =over 2
205    
206    =item *
207    Need to deal with pserver/SSH transition with conflicting umasks?
208    
209    =item *
210    Use a CPAN module to handle command parameters.
211    
212    =item *
213    Use a CPAN module to clone data structures.
214    
215    =back
216    
217    =head1 Version Information
218    
219    This is not offered as a fix to the original 'cvs_acls' script since it
220    differs substantially in goals and methods from the original and there
221    are probably a significant number of people out there that still require
222    the original version's functionality.
223    
224    The 'cvsacl' file flags of 'allow' and 'deny' were intentionally
225    changed to 'allow' and 'deny' because there are enough differences
226    between the original script's behavior and this one's that we wanted to
227    make sure that users will rethink their 'cvsacl' file formats before
228    plugging in this newer script.
229    
230    Please note that there has been very limited cross-platform testing of
231    this script!!! (We did not have the time or resources to do exhaustive
232    cross-platform testing.)
233    
234    It was developed and tested under Red Hat Linux 9.0 using PERL 5.8.0.
235    Additionally, it was built and tested under Red Hat Linux 7.3 using
236    PERL 5.6.1.
237    
238    $Id$
239    
240    This version is based on the 1.11.13 version of cvs_acls
241    peter.connolly@cnet.com (Peter Connolly)
242    
243      Access control lists for CVS.  dgg@ksr.com (David G. Grubbs)
244      Branch specific controls added by voisine@bytemobile.com (Aaron Voisine)
245    
246    =head1 Installation
247    
248    To use this program, do the following four things:
249    
250    0. Install PERL, version 5.6.1 or 5.8.0.
251    
252    1. Admin Setup:
253    
254       There are two choices here.
255    
256       a) The first option is to use the $ENV{"USER"}, server-side userId
257          (from the third column of your pserver 'passwd' file) as the basis for
258          your restrictions.  In this case, you will (at a minimum) want to set
259          up a new "cvsadmin" userId and group on the pserver machine.  
260          CVS administrators will then set up their 'passwd' file entries to
261          run either as "cvs" (for regular users) or as "cvsadmin" (for power
262          users).  Correspondingly, your 'cvsacl' file will only list 'cvs'
263          and 'cvsadmin' as the userIds in the second column.
264    
265          Commentary: A potential weakness of this is that the xinetd
266          cvspserver process will need to run as 'root' in order to switch
267          between the 'cvs' and the 'cvsadmin' userIds.  Some sysadmins don't
268          like situations like this and may want to chroot the process.
269          Talk to them about this point...
270    
271       b) The second option is to use the client-side userId as the basis for
272          your restrictions.  In this case, all the xinetd cvspserver processes
273          can run as userId 'cvs' and no 'root' userId is required.  If you have
274          a 'passwd' file that lists 'cvs' as the effective run-time userId for
275          all your users, then no changes to this file are needed.  Your 'cvsacl'
276          file will use the individual, client-side userIds in its 2nd column.
277    
278          As long as the userIds in pserver's 'passwd' file match those userIds
279          that your Linux server know about, this approach is ideal if you are
280          planning to move from pserver to SSH access at some later point in time.
281          Just by switching the CVSROOT var from CVSROOT=:pserver:<userId>... to
282          CVSROOT=:ext:<userId>..., users can switch over to SSH access without
283          any other administrative changes.  When all users have switched over to
284          SSH, the inherently insecure xinetd cvspserver process can be disabled.
285          [http://ximbiot.com/cvs/manual/cvs-1.11.17/cvs_2.html#SEC32]
286    
287          :TODO: The only potential glitch with the SSH approach is the possibility
288          that each user can have differing umasks that might interfere with one
289          another, especially during a transition from pserver to SSH.  As noted
290          in the ToDo section, this needs a good strategy and set of tests for that
291          yet...
292    
293    2. Put two lines, as the *only* non-comment lines, in your commitinfo file:
294    
295       ALL $CVSROOT/CVSROOT/commit_prep
296       ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f <logfilename>]
297    
298       where "-d" turns on debug trace
299             "-u $USER" passes the client-side userId to cvs_acls
300             "-f <logfilename"> overrides the default filename used to log
301                                restricted commit attempts.
302    
303       (These are handled in the processArgs() subroutine.)
304    
305    If you are using client-side userIds to restrict access to your
306    repository, make sure that they are in this order since the commit_prep
307    script is required in order to pass the $USER parameter.
308    
309    A final note about the repository matching pattern.  The example above
310    uses "ALL" but note that this means that the cvs_acls script will run
311    for each and every commit in your repository.  Obviously, in a large
312    repository this adds up to a lot of overhead that may not be necesary.
313    A better strategy is to use a repository pattern that is more specific
314    to the areas that you wish to secure.
315    
316    3. Install this file as $CVSROOT/CVSROOT/cvs_acls and make it executable.
317    
318    4. Create a file named CVSROOT/cvsacl and optionally add it to
319       CVSROOT/checkoutlist and check it in.  See the CVS manual's
320       administrative files section about checkoutlist.  Typically:
321    
322       $ cvs checkout CVSROOT
323       $ cd CVSROOT
324       [ create the cvsacl file, include 'commitinfo' line ]
325       [ add cvsacl to checkoutlist ]
326       $ cvs add cvsacl
327       $ cvs commit -m 'Added cvsacl for use with cvs_acls.' cvsacl checkoutlist
328    
329    Note: The format of the 'cvsacl' file is described in detail immediately
330    below but here is an important set up point:
331    
332       Make sure to include a line like the following:
333    
334         deny||CVSROOT/commitinfo CVSROOT/cvsacl
335         allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl
336    
337       that restricts access to commitinfo and cvsacl since this would be one of
338       the easiest "end runs" around this ACL approach. ('commitinfo' has the
339       line that executes the cvs_acls script and, of course, all the
340       restrictions are in 'cvsacl'.)
341    
342    5. (Optional) Create a 'restrict_msg' file in the $CVSROOT/CVSROOT directory.
343       Whenever there is a restricted file or dir message, cvs_acls will look
344       for this file and, if it exists, print its contents as part of the
345       commit-denial message.  This gives you a chance to print any site-specific
346       information (e.g., who to call, what procedures to look up,...) whenever
347       a commit is denied.
348    
349  $debug = 0;  =head1 Format of the cvsacl file
350    
351    The 'cvsacl' file determines whether you may commit files.  It contains lines
352    read from top to bottom, keeping track of whether a given user, repository
353    and branch combination is "allowed" or "denied."  The script will assume
354    "allowed" on all repository paths until 'allow' and 'deny' rules change
355    that default.  
356    
357    The normal pattern is to specify an 'deny' rule to turn off
358    access to ALL users, then follow it with a matching 'allow' rule that will
359    turn on access for a select set of users.  In the case of multiple rules for
360    the same user, repository and branch, the last one takes precedence.
361    
362    Blank lines and lines with only comments are ignored.  Any other lines not
363    beginning with "allow" or "deny" are logged to the restrict_log file.
364    
365    Lines beginning with "allow" or "deny" are assumed to be '|'-separated
366    triples: (All spaces and tabs are ignored in a line.)
367    
368      {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
369    
370       1. String starting with "allow" or "deny".
371       2. Optional, comma-separated list of usernames.
372       3. Optional, comma-separated list of repository pathnames.
373          These are pathnames relative to $CVSROOT.  They can be directories or
374          filenames.  A directory name allows or restricts access to all files and
375          directories below it. One line can have either directories or filenames
376          but not both.
377       4. Optional, comma-separated list of branch tags.
378          If not specified, all branches are assumed. Use HEAD to reference the
379          main branch.
380    
381    Example:  (Note: No in-line comments.)
382    
383       # ----- Make whole repository unavailable.
384       deny
385    
386       # ----- Except for user "dgg".
387       allow|dgg
388    
389       # ----- Except when "fred" or "john" commit to the
390       #       module whose repository is "bin/ls"
391       allow|fred, john|bin/ls
392    
393       # ----- Except when "ed" commits to the "stable"
394       #       branch of the "bin/ls" repository
395       allow|ed|/bin/ls|stable
396    
397    =head1 Program Logic
398    
399    CVS passes to @ARGV an absolute directory pathname (the repository
400    appended to your $CVSROOT variable), followed by a list of filenames
401    within that directory that are to be committed.
402    
403    The script walks through the 'cvsacl' file looking for matches on
404    the username, repository and branch.
405    
406    A username match is simply the user's name appearing in the second
407    column of the cvsacl line in a space-or-comma separate list. If
408    blank, then any user will match.
409    
410    A repository match:
411    
412    =over 2
413    
414    =item *
415    Each entry in the modules section of the current 'cvsacl' line is
416    examined to see if it is a dir or a file. The line must have
417    either files or dirs, but not both. (To simplify the logic.)
418    
419    =item *
420    If neither, then assume the 'cvsacl' file was set up in error and
421    skip that 'allow' line.
422    
423    =item *
424    If a dir, then each dir pattern is matched separately against the
425    beginning of each of the committed files in @ARGV.
426    
427    =item *
428    If a file, then each file pattern is matched exactly against each
429    of the files to be committed in @ARGV.
430    
431    =item *
432    Repository and branch must BOTH match together. This is to cover
433    the use case where a user has multiple branches checked out in
434    a single work directory. Commit files can be from different
435    branches.
436    
437    A branch match is either:
438    
439    =over 4
440    
441    =item *
442    When no branches are listed in the fourth column. ("Match any.")
443    
444    =item *
445    All elements from the fourth column are matched against each of
446    the tag names for $ARGV[1..$#ARGV] found in the %branches file.
447    
448    =back
449    
450    =item *
451    'allow' match remove that match from the tally map.
452    
453    =item *
454    Restricted ('deny') matches are saved in the %repository_matches
455    table.
456    
457    =item *
458    If there is a match on user, repository and branch:
459    
460      If repository, branch and user match
461        if 'deny'
462          add %repository_matches entries to %restricted_entries
463        else if 'allow'
464          remove %repository_matches entries from %restricted_entries
465    
466    =item *
467    At the end of all the 'cvsacl' line checks, check to see if there
468    are any entries in the %restricted_entries.  If so, then deny the
469    commit.
470    
471    =back
472    
473    =head2 Pseudocode
474    
475         read CVS/Entries file and create branch{file}->{branch} hash table
476       + for each 'allow' and 'deny' line in the 'cvsacl' file:
477       |   user match?  
478       |     - Yes: set $user_match       = 1;
479       |   repository and branch match?
480       |     - Yes: add to %repository_matches;
481       |   did user, repository match?
482       |     - Yes: if 'deny' then
483       |                add %repository_matches -> %restricted_entries
484       |            if 'allow'   then
485       |                remove %repository_matches <- %restricted_entries
486       + end for loop
487         any saved restrictions?
488           no:  exit,
489                set exit code allowing commits and exit
490           yes: report restrictions,
491                set exit code prohibiting commits and exit
492    
493    =head2 Sanity Check
494    
495      1) file allow trumps a dir deny
496         deny||java/lib
497         allow||java/lib/README
498      2) dir allow can undo a file deny
499         deny||java/lib/README
500         allow||java/lib
501      3) file deny trumps a dir allow
502         allow||java/lib
503         deny||java/lib/README
504      4) dir deny trumps a file allow
505         allow||java/lib/README
506         deny||java/lib
507      ... so last match always takes precedence
508    
509    =cut
510    
511    $debug = 0;                     # Set to 1 for debug messages
512    
513    %repository_matches = ();       # hash of match file and pattern from 'cvsacl'
514                                    # repository_matches --> [branch, matching-pattern]
515                                    # (Used during module/branch matching loop)
516    
517    %restricted_entries = ();       # hash table of restricted commit files (from @ARGV)
518                                    # restricted_entries --> branch
519                                    # (If user/module/branch all match on an 'deny'
520                                    #  line, then entries added to this map.)
521    
522    %branch;                        # hash table of key: commit file; value: branch
523                                    # Built from ".../CVS/Entries" file of directory
524                                    # currently being examined
525    
526    # ---------------------------------------------------------------- get CVSROOT
527  $cvsroot = $ENV{'CVSROOT'};  $cvsroot = $ENV{'CVSROOT'};
528  $availfile = $cvsroot . "/CVSROOT/avail";  die "Must set CVSROOT\n" if !$cvsroot;
529  $myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"});  if ($cvsroot =~ /:([\/\w]*)$/) { # Filter ":pserver:", ":local:"-type prefixes
530        $cvsroot = $1;
531    }
532    
533    # ------------------------------------------------------------- set file paths
534    $entries = "CVS/Entries";                                # client-side file???
535    $cvsaclfile = $cvsroot . "/CVSROOT/cvsacl";
536    $restrictfile = $cvsroot . "/CVSROOT/restrict_msg";
537    $restrictlog = $cvsroot . "/CVSROOT/restrict_log";
538    
539    # --------------------------------------------------------------- process args
540    $user_name = processArgs(\@ARGV);
541    
542    print("$$ \@ARGV after processArgs is: @ARGV.\n") if $debug;
543    print("$$ ========== Begin $PROGRAM_NAME for \"$ARGV[0]\" repository. ========== \n") if $debug;
544    
545    # --------------------------------------------------------------- filter @ARGV
546  eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"  eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
547      while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));      while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
548  exit 255 if $die;               # process any variable=value switches  exit 255 if $die;                        # process any variable=value switches
549    
550  die "Must set CVSROOT\n" if !$cvsroot;  print("$$ \@ARGV after shift processing contains:",join("\, ",@ARGV),".\n") if $debug;
 ($repos = shift) =~ s:^$cvsroot/::;  
 grep($_ = $repos . '/' . $_, @ARGV);  
551    
552  print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;  # ---------------------------------------------------------------- get cvsroot
553    ($repository = shift) =~ s:^$cvsroot/::;
554    grep($_ = $repository . '/' . $_, @ARGV);
555    
556  $exit_val = 0;                          # Good Exit value  print("$$ \$cvsroot is: $cvsroot.\n") if $debug;
557    print "$$ Repos: $repository\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;
558    
559  $universal_off = 0;  $exit_val = 0;                           # presume good exit value for commit
560  open (AVAIL, $availfile) || exit(0);    # It is ok for avail file not to exist  
561  while (<AVAIL>) {  # ----------------------------------------------------------------------------
562    # ---------------------------------- create hash table $branch{file -> branch}
563    # ----------------------------------------------------------------------------
564    
565    # Here's a typical Entries file:
566    #
567    #   /checkoutlist/1.4/Wed Feb  4 23:51:23 2004//
568    #   /cvsacl/1.3/Tue Feb 24 23:05:43 2004//
569    #   ...
570    #   /verifymsg/1.1/Fri Mar 16 19:56:24 2001//
571    #   D/backup////
572    #   D/temp////
573    
574    open(ENTRIES, $entries) || die("Cannot open $entries.\n");
575    print("$$ File / Branch\n") if $debug;
576    my $i = 0;
577    while(<ENTRIES>) {
578      chop;      chop;
579      next if /^\s*\#/;      next if /^\s*$/;                    # Skip blank lines
580      next if /^\s*$/;      $i = $i + 1;
581      ($flagstr, $u, $m) = split(/[\s,]*\|[\s,]*/, $_);      if (m|
582              /                             # 1st slash
583      # Skip anything not starting with "avail" or "unavail" and complain.            ([\w.-]*)                     # file name -> $1
584      (print "Bad avail line: $_\n"), next            /                             # 2nd slash
585          if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/);            .*                            # revision number
586              /                             # 3rd slash
587      # Set which bit we are playing with. ('0' is OK == Available).            .*                            # date and time
588      $flag = (($& eq "avail") ? 0 : 1);            /                             # 4th slash
589              .*                            # keyword
590      # If we find a "universal off" flag (i.e. a simple "unavail") remember it            /                             # 5th slash
591      $universal_off = 1 if ($flag && !$u && !$m);            T?                            # 'T' constant
592              (\w*)                         # branch    -> #2
593      # $myname considered "in user list" if actually in list or is NULL                |x) {
594      $in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u)));          $branch{$repository . '/' . $1} = ($2) ? $2 : "HEAD";
595      print "$$ \$myname($myname) in user list: $_\n" if $debug && $in_user;          print "$$ CVS Entry $i: $1/$2\n" if $debug;
596        }
597      # Module matches if it is a NULL module list in the avail line.  If module  }
598      # list is not null, we check every argument combination.  close(ENTRIES);
599      if (!($in_repo = !$m)) {  
600          @tmp = split(/[\s,]+/,$m);  # ----------------------------------------------------------------------------
601          for $j (@tmp) {  # ------------------------------------- evaluate each active line from 'cvsacl'
602              # If the repos from avail is a parent(or equal) dir of $repos, OK  # ----------------------------------------------------------------------------
603              $in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//);  open (CVSACL, $cvsaclfile) || exit(0);  # It is ok for cvsacl file not to exist
604          }  while (<CVSACL>) {
605          if (!$in_repo) {      chop;
606              $in_repo = 1;      next if /^\s*\#/;                               # skip comments
607              for $j (@ARGV) {      next if /^\s*$/;                                # skip blank lines
608                  last if !($in_repo = grep ($_ eq $j, @tmp));      # --------------------------------------------- parse current 'cvsacl' line
609        print("$$ ==========\n$$ Processing \'cvsacl\' line: $_.\n") if $debug;
610        ($cvsacl_flag, $cvsacl_userIds, $cvsacl_modules, $cvsacl_branches) = split(/[\s,]*\|[\s,]*/, $_);
611    
612        # ------------------------------ Validate 'allow' or 'deny' line prefix
613        if ($cvsacl_flag !~ /^allow/ && $cvsacl_flag !~ /^deny/) {
614            print ("Bad cvsacl line: $_\n") if $debug;
615            $log_text = sprintf "Bad cvsacl line: %s", $_;
616            write_restrictlog_record($log_text);
617            next;
618        }
619    
620        # -------------------------------------------------- init loop match flags
621        $user_match = 0;
622        %repository_matches = ();
623    
624        # ------------------------------------------------------------------------
625        # ---------------------------------------------------------- user matching
626        # ------------------------------------------------------------------------
627        # $user_name considered "in user list" if actually in list or is NULL
628        $user_match = (!$cvsacl_userIds || grep ($_ eq $user_name, split(/[\s,]+/,$cvsacl_userIds)));
629        print "$$ \$user_name: $user_name \$user_match match flag is: $user_match.\n" if $debug;
630        if (!$user_match) {
631            next;                            # no match, skip to next 'cvsacl' line
632        }
633    
634        # ------------------------------------------------------------------------
635        # ---------------------------------------------------- repository matching
636        # ------------------------------------------------------------------------
637        if (!$cvsacl_modules) {                  # blank module list = all modules
638            if (!$cvsacl_branches) {            # blank branch list = all branches
639                print("$$ Adding all modules to \%repository_matches; null " .
640                      "\$cvsacl_modules and \$cvsacl_branches.\n") if $debug;
641                for $commit_object (@ARGV) {
642                    $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules];
643                    print("$$ \$repository_matches{$commit_object} = " .
644                          "[$branch{$commit_object}, $cvsacl_modules].\n") if $debug;
645                }
646            }
647            else {                            # need to check for repository match
648                @branch_list = split (/[\s,]+/,$cvsacl_branches);
649                print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug;
650                for $commit_object (@ARGV) {
651                    if (grep($branch{$commit_object}, @branch_list)) {
652                        $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_modules];
653                        print("$$ \$repository_matches{$commit_object} = " .
654                              "[$branch{$commit_object}, $cvsacl_modules].\n") if $debug;
655                    }
656              }              }
657          }          }
658      }      }
659      print "$$ \$repos($repos) in repository list: $_\n" if $debug && $in_repo;      else {
660            # ----------------------------------- check every argument combination
661            # parse 'cvsacl' modules to array
662            my @module_list = split(/[\s,]+/,$cvsacl_modules);
663            # ------------- Check all modules in list for either file or directory
664            my $fileType = "";
665            if (($fileType = checkFileness(@module_list)) eq "") {
666                next;                                        # skip bad file types
667            }
668            # ---------- Check each combination of 'cvsacl' modules vs. @ARGV files
669            print("$$ Checking matches for \@module_list: ", join("\, ",@module_list), ".\n") if $debug;
670            # loop thru all command-line commit objects
671            for $commit_object (@ARGV) {              
672                # loop thru all modules on 'cvsacl' line
673                for $cvsacl_module (@module_list) {
674                    print("$$ Is \'cvsacl\': $cvsacl_modules pattern in: \@ARGV " .
675                          "\$commit_object: $commit_object?\n") if $debug;
676                    # Do match of beginning of $commit_object
677                    checkModuleMatch($fileType, $commit_object, $cvsacl_module);
678                } # end for commit objects
679            } # end for cvsacl modules
680        } # end if
681    
682        print("$$ Matches for: \%repository_matches: ", join("\, ", (keys %repository_matches)), ".\n") if $debug;
683    
684        # ------------------------------------------------------------------------
685        # ----------------------------------------------------- setting exit value
686        # ------------------------------------------------------------------------
687        if ($user_match && %repository_matches) {
688            print("$$ An \"$cvsacl_flag\" match on User(s): $cvsacl_userIds; Module(s):" .
689                  " $cvsacl_modules; Branch(es): $cvsacl_branches.\n") if $debug;
690            if ($cvsacl_flag eq "deny") {
691                # Add all matches to the hash of restricted modules
692                foreach $commitFile (keys %repository_matches) {
693                    print("$$ Adding \%repository_matches entry: $commitFile.\n") if $debug;
694                    $restricted_entries{$commitFile} = $repository_matches{$commitFile}[0];
695                }
696            }
697            else {
698                # Remove all matches from the restricted modules hash
699                foreach $commitFile (keys %repository_matches) {
700                    print("$$ Removing \%repository_matches entry: $commitFile.\n") if $debug;
701                    delete $restricted_entries{$commitFile};
702                }
703            }
704        }
705        print "$$ ==== End of processing for \'cvsacl\' line: $_.\n" if $debug;
706    }
707    close(CVSACL);
708    
709      $exit_val = $flag if ($in_user && $in_repo);  # ----------------------------------------------------------------------------
710      print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = $flag\n" if $debug;  # --------------------------------------- determine final 'commit' disposition
711    # ----------------------------------------------------------------------------
712    if (%restricted_entries) {                           # any restricted entries?
713        $exit_val = 1;                                              # don't commit
714        print("**** Access denied: Insufficient authority for user: '$user_name\' " .
715              "to commit to \'$repository\'.\n**** Contact CVS Administrators if " .
716              "you require update access to these directories or files.\n");
717        print("**** file(s)/dir(s) restricted were:\n\t", join("\n\t",keys %restricted_entries), "\n");
718        printOptionalRestrictionMessage();
719        write_restrictlog();
720  }  }
721  close(AVAIL);  elsif (!$exit_val && $debug) {
722        print "**** Access allowed: Sufficient authority for commit.\n";
723    }
724    
725  print "$$ ==== \$exit_val = $exit_val\n" if $debug;  print "$$ ==== \$exit_val = $exit_val\n" if $debug;
 print "**** Access denied: Insufficient Karma ($myname|$repos)\n" if $exit_val;  
 print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n"  
         if $universal_off && !$exit_val;  
726  exit($exit_val);  exit($exit_val);
727    
728    # ----------------------------------------------------------------------------
729    # -------------------------------------------------------------- end of "main"
730    # ----------------------------------------------------------------------------
731    
732    
733    # ----------------------------------------------------------------------------
734    # -------------------------------------------------------- process script args
735    # ----------------------------------------------------------------------------
736    sub processArgs {
737    
738    # This subroutine is passed a reference to @ARGV.
739    
740    # If @ARGV contains a "-u" entry, use that as the effective userId.  In this
741    # case, the userId is the client-side userId that has been passed to this
742    # script by the commit_prep script.  (This is why the commit_prep script must
743    # be placed *before* the cvs_acls script in the commitinfo admin file.)
744    
745    # Otherwise, pull the userId from the server-side environment.
746    
747        my $userId = "";
748        my ($argv) = shift;             # pick up ref to @ARGV
749        my @argvClone = ();             # immutable copy for foreach loop
750        for ($i=0; $i<(scalar @{$argv}); $i++) {
751            $argvClone[$i]=$argv->[$i];
752        }
753    
754        print("$$ \@_ to processArgs is: @_.\n") if $debug;
755    
756        # Parse command line arguments (file list is seen as one arg)
757        foreach $arg (@argvClone) {
758            print("$$ \$arg for processArgs loop is: $arg.\n") if $debug;
759            # Set $debug flag?
760            if ($arg eq '-d') {
761                shift @ARGV;
762                $debug = 1;
763                print("$$ \$debug flag set on.\n") if $debug;
764                print STDERR "Debug turned on...\n";
765            }
766            # Passing in a client-side userId?
767            elsif ($arg eq '-u') {
768                shift @ARGV;
769                $userId = shift @ARGV;
770                print("$$ client-side \$userId set to: $userId.\n") if $debug;
771            }
772            # An override for the default restrictlog file?
773            elsif ($arg eq '-f') {
774                shift @ARGV;
775                $restrictlog = shift @ARGV;
776            }
777            else {
778                next;
779            }
780        }
781    
782        # No client-side userId passed? then get from server env
783        if (!$userId) {
784            $userId = $ENV{"USER"} if !($userId = $ENV{"LOGNAME"});
785                print("$$ server-side \$userId set to: $userId.\n") if $debug;
786        }
787    
788        print("$$ processArgs returning \$userId: $userId.\n") if $debug;
789        return $userId;
790    
791    }
792    
793    
794    # ----------------------------------------------------------------------------
795    # --------------------- Check all modules in list for either file or directory
796    # ----------------------------------------------------------------------------
797    sub checkFileness {
798    
799    # Module patterns on the 'cvsacl' record can be files or directories.
800    # If it's a directory, we pattern-match the directory name from 'cvsacl'
801    # against the left side of the committed filename to see if the file is in
802    # that hierarchy.  By contrast, files use an explicit match.  If the entries
803    # are neither files nor directories, then the cvsacl file has been set up
804    # incorrectly; we return a "" and the caller skips that line as invalid.
805    #
806    # This function determines whether the entries on the 'cvsacl' record are all
807    # directories or all files; it cannot be a mixture.  This restriction put in
808    # to simplify the logic (without taking away much functionality).
809    
810        my @module_list = @_;
811        print("$$ Checking \"fileness\" or \"dir-ness\" for \@module_list entries.\n") if $debug;
812        print("$$     Entries are: ", join("\, ",@module_list), ".\n") if $debug;
813        my $filetype = "";
814        for $cvsacl_module (@module_list) {
815            my $reposDirName = $cvsroot . '/' . $cvsacl_module;
816            my $reposFileName = $reposDirName . "\,v";
817            print("$$ In checkFileness: \$reposDirName: $reposDirName; \$reposFileName: $reposFileName.\n") if $debug;
818            if (((-d $reposDirName) && ($filetype eq "file")) || ((-f $reposFileName) && ($filetype eq "dir"))) {
819                print("Can\'t mix files and directories on single \'cvsacl\' file record; skipping entry.\n");
820                print("    Please contact a CVS administrator.\n");
821                $filetype = "";
822                last;
823            }
824            elsif (-d $reposDirName) {
825                $filetype = "dir";
826                print("$$ $reposDirName is a directory.\n") if $debug;
827            }
828            elsif (-f $reposFileName) {
829                $filetype = "file";
830                print("$$ $reposFileName is a regular file.\n") if $debug;
831            }
832            else {
833                print("***** Item to commit was neither a regular file nor a directory.\n");
834                print("***** Current \'cvsacl\' line ignored.\n");
835                print("***** Possible problem with \'cvsacl\' admin file. Please contact a CVS administrator.\n");
836                $filetype = "";
837                $text = sprintf("Module entry on cvsacl line: %s is not a valid file or directory.\n", $cvsacl_module);
838                write_restrictlog_record($text);
839                last;
840            } # end if
841        } # end for
842    
843        print("$$ checkFileness will return \$filetype: $filetype.\n") if $debug;
844        return $filetype;
845    }
846    
847    
848    # ----------------------------------------------------------------------------
849    # ----------------------------------------------------- check for module match
850    # ----------------------------------------------------------------------------
851    sub checkModuleMatch {
852    
853    # This subroutine checks for a match between the directory or file pattern
854    # specified in the 'cvsacl' file (i.e., $cvsacl_modules) versus the commit file
855    # objects passed into the script via @ARGV (i.e., $commit_object).
856    
857    # The directory pattern only has to match the beginning portion of the commit
858    # file's name for a match since all files under that directory are considered
859    # a match. File patterns must exactly match.
860    
861    # Since (theoretically, if not normally in practice) a working directory can
862    # contain a mixture of files from different branches, this routine checks to
863    # see if there is also a match on branch before considering the file
864    # comparison a match.
865    
866        my $match_flag = "";
867    
868        print("$$ \@_ in checkModuleMatch is: @_.\n") if $debug;
869        my ($type,$commit_object,$cvsacl_module) = @_;
870    
871        if ($type eq "file") {             # Do exact file match of $commit_object
872            if ($commit_object eq $cvsacl_module) {
873                $match_flag = "file";
874            }                        # Do dir match at beginning of $commit_object
875        }
876        elsif ($commit_object =~ /^$cvsacl_module\//) {
877            $match_flag = "dir";
878        }
879    
880        if ($match_flag) {
881            print("$$ \$repository: $repository matches \$commit_object: $commit_object.\n") if $debug;
882            if (!$cvsacl_branches) {             # empty branch pattern matches all
883                print("$$ blank \'cvsacl\' branch matches all commit files.\n") if $debug;
884                $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module];
885                print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module].\n") if $debug;
886            }
887            else {                             # otherwise check branch hash table
888                @branch_list = split (/[\s,]+/,$cvsacl_branches);
889                print("$$ Branches from \'cvsacl\' record: ", join(", ",@branch_list),".\n") if $debug;
890                if (grep(/$branch{$commit_object}/, @branch_list)) {
891                    $repository_matches{$commit_object} = [$branch{$commit_object}, $cvsacl_module];
892                    print("$$ \$repository_matches{$commit_object} = [$branch{$commit_object}, " .
893                          "$cvsacl_module].\n") if $debug;
894                }
895            }
896        }
897    
898    }
899    
900    # ----------------------------------------------------------------------------
901    # ------------------------------------------------------- check for file match
902    # ----------------------------------------------------------------------------
903    sub printOptionalRestrictionMessage {
904    
905    # This subroutine optionally prints site-specific file restriction information
906    # whenever a restriction condition is met.  If the file 'restrict_msg' does
907    # not exist, the routine immediately exits.  If there is a 'restrict_msg' file
908    # then all the contents are printed at the end of the standard restriction
909    # message.
910    
911    # As seen from examining the definition of $restrictfile, the default filename
912    # is: $CVSROOT/CVSROOT/restrict_msg.
913    
914        open (RESTRICT, $restrictfile) || return;   # It is ok for cvsacl file not to exist
915        while (<RESTRICT>) {
916            chop;
917            # print out each line
918            print("**** $_\n");
919        }
920    
921    }
922    
923    # ----------------------------------------------------------------------------
924    # ---------------------------------------------------------- write log message
925    # ----------------------------------------------------------------------------
926    sub write_restrictlog {
927    
928    # This subroutine iterates through the list of restricted entries and logs
929    # each one to the error logfile.
930    
931        # write each line in @text out separately
932        foreach $commitfile (keys %restricted_entries) {
933            $log_text = sprintf "Commit attempt by: %s for: %s on branch: %s",
934                                $user_name, $commitfile, $branch{$commitfile};
935            write_restrictlog_record($log_text);
936        }
937    
938    }
939    
940    # ----------------------------------------------------------------------------
941    # ---------------------------------------------------------- write log message
942    # ----------------------------------------------------------------------------
943    sub write_restrictlog_record {
944    
945    # This subroutine receives a scalar string and writes it out to the
946    # $restrictlog file as a separate line. Each line is prepended with the date
947    # and time in the format: "2004/01/30 12:00:00 ".
948    
949        $text = shift;
950    
951        # return quietly if there is a problem opening the log file.
952        open(FILE, ">>$restrictlog") || return;
953    
954        (@time) = localtime();
955    
956        # write each line in @text out separately
957        $log_record = sprintf "%04d/%02d/%02d %02d:%02d:%02d %s.\n",
958                          $time[5]+1900, $time[4]+1, $time[3], $time[2], $time[1], $time[0], $text;
959        print FILE $log_record;
960        print("$$ restrict_log record being written: $log_record to $restrictlog.\n") if $debug;
961        
962        close(FILE);
963    }

Legend:
Removed from v.1.4  
changed lines
  Added in v.1.5

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26