Home | History | Annotate | Line # | Download | only in contrib
      1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
      2 <html xmlns="http://www.w3.org/1999/xhtml">
      3 <head>
      4 <title>cvs_acls</title>
      5 <link rev="made" href="mailto:root@localhost" />
      6 </head>
      7 
      8 <body style="background-color: white">
      9 
     10 <p><a name="__index__"></a></p>
     11 <!-- INDEX BEGIN -->
     12 
     13 <ul>
     14 
     15 	<li><a href="#name">Name</a></li>
     16 	<li><a href="#synopsis">Synopsis</a></li>
     17 	<li><a href="#licensing">Licensing</a></li>
     18 	<li><a href="#description">Description</a></li>
     19 	<li><a href="#enhancements">Enhancements</a></li>
     20 	<ul>
     21 
     22 		<li><a href="#fixed_bugs">Fixed Bugs</a></li>
     23 		<li><a href="#enhancements">Enhancements</a></li>
     24 		<li><a href="#todos">ToDoS</a></li>
     25 	</ul>
     26 
     27 	<li><a href="#version_information">Version Information</a></li>
     28 	<li><a href="#installation">Installation</a></li>
     29 	<li><a href="#format_of_the_cvsacl_file">Format of the cvsacl file</a></li>
     30 	<li><a href="#program_logic">Program Logic</a></li>
     31 	<ul>
     32 
     33 		<li><a href="#pseudocode">Pseudocode</a></li>
     34 		<li><a href="#sanity_check">Sanity Check</a></li>
     35 	</ul>
     36 
     37 </ul>
     38 <!-- INDEX END -->
     39 
     40 <hr />
     41 <p>
     42 </p>
     43 <h1><a name="name">Name</a></h1>
     44 <p>cvs_acls - Access Control List for CVS</p>
     45 <p>
     46 </p>
     47 <hr />
     48 <h1><a name="synopsis">Synopsis</a></h1>
     49 <p>In 'commitinfo':</p>
     50 <pre>
     51   repository/path/to/restrict $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER][-f &lt;logfile&gt;]</pre>
     52 <p>where:</p>
     53 <pre>
     54   -d  turns on debug information
     55   -u  passes the client-side userId to the cvs_acls script
     56   -f  specifies an alternate filename for the restrict_log file</pre>
     57 <p>In 'cvsacl':</p>
     58 <pre>
     59   {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]</pre>
     60 <p>where:</p>
     61 <pre>
     62   allow|deny - allow: commits are allowed; deny: prohibited
     63   user          - userId to be allowed or restricted
     64   repos         - file or directory to be allowed or restricted
     65   branch        - branch to be allowed or restricted</pre>
     66 <p>See below for examples.</p>
     67 <p>
     68 </p>
     69 <hr />
     70 <h1><a name="licensing">Licensing</a></h1>
     71 <p>cvs_acls - provides access control list functionality for CVS
     72 </p>
     73 <pre>
     74 
     75 Copyright (c) 2004 by Peter Connolly &lt;peter.connolly (a] cnet.com&gt;  
     76 All rights reserved.</pre>
     77 <p>This program is free software; you can redistribute it and/or modify  
     78 it under the terms of the GNU General Public License as published by  
     79 the Free Software Foundation; either version 2 of the License, or  
     80 (at your option) any later version.</p>
     81 <p>This program is distributed in the hope that it will be useful,  
     82 but WITHOUT ANY WARRANTY; without even the implied warranty of  
     83 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
     84 GNU General Public License for more details.</p>
     85 <p>You should have received a copy of the GNU General Public License  
     86 along with this program; if not, write to the Free Software  
     87 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA</p>
     88 <p>
     89 </p>
     90 <hr />
     91 <h1><a name="description">Description</a></h1>
     92 <p>This script--cvs_acls--is invoked once for each directory within a 
     93 ``cvs commit''. The set of files being committed for that directory as 
     94 well as the directory itself, are passed to this script.  This script 
     95 checks its 'cvsacl' file to see if any of the files being committed 
     96 are on the 'cvsacl' file's restricted list.  If any of the files are
     97 restricted, then the cvs_acls script passes back an exit code of 1
     98 which disallows the commits for that directory.</p>
     99 <p>Messages are returned to the committer indicating the <a href="#item_file"><code>file(s)</code></a> that 
    100 he/she are not allowed to committ.  Additionally, a site-specific 
    101 set of messages (e.g., contact information) can be included in these 
    102 messages.</p>
    103 <p>When a commit is prohibited, log messages are written to a restrict_log
    104 file in $CVSROOT/CVSROOT.  This default file can be redirected to 
    105 another destination.</p>
    106 <p>The script is triggered from the 'commitinfo' file in $CVSROOT/CVSROOT/.</p>
    107 <p>
    108 </p>
    109 <hr />
    110 <h1><a name="enhancements">Enhancements</a></h1>
    111 <p>This section lists the bug fixes and enhancements added to cvs_acls
    112 that make up the current cvs_acls.</p>
    113 <p>
    114 </p>
    115 <h2><a name="fixed_bugs">Fixed Bugs</a></h2>
    116 <p>This version attempts to get rid the following bugs from the
    117 original version of cvs_acls:</p>
    118 <ul>
    119 <li><strong><a name="item_files">Multiple entries on an 'cvsacl' line will be matched individually, 
    120 instead of requiring that all commit files *exactly* match all 
    121 'cvsacl' entries. Commiting a file not in the 'cvsacl' list would
    122 allow *all* files (including a restricted file) to be committed.</a></strong><br />
    123 </li>
    124 [IMO, this basically made the original script unuseable for our 
    125 situation since any arbitrary combination of committed files could 
    126 avoid matching the 'cvsacl's entries.]
    127 <p></p>
    128 <li><strong><a name="item_handle_specific_filename_restrictions_2e_cvs_acls_">Handle specific filename restrictions. cvs_acls didn't restrict
    129 individual files specified in 'cvsacl'.</a></strong><br />
    130 </li>
    131 <li><strong><a name="item_correctly_handle_multiple_2c_specific_filename_res">Correctly handle multiple, specific filename restrictions</a></strong><br />
    132 </li>
    133 <li><strong><a name="item_prohibit_mix_of_dirs_and_files_on_a_single__27cvsa">Prohibit mix of dirs and files on a single 'cvsacl' line
    134 [To simplify the logic and because this would be normal usage.]</a></strong><br />
    135 </li>
    136 <li><strong><a name="item_correctly_handle_a_mixture_of_branch_restrictions_">Correctly handle a mixture of branch restrictions within one work
    137 directory</a></strong><br />
    138 </li>
    139 <li><strong><a name="item__24cvsroot_existence_is_checked_too_late">$CVSROOT existence is checked too late</a></strong><br />
    140 </li>
    141 <li><strong><a name="item_option">Correctly handle the CVSROOT=:local:/... option (useful for 
    142 interactive testing)</a></strong><br />
    143 </li>
    144 <li><strong><a name="item_logic">Replacing shoddy ``$universal_off'' logic 
    145 (Thanks to Karl-Konig Konigsson for pointing this out.)</a></strong><br />
    146 </li>
    147 </ul>
    148 <p>
    149 </p>
    150 <h2><a name="enhancements">Enhancements</a></h2>
    151 <ul>
    152 <li><strong><a name="item_checks_modules_in_the__27cvsacl_27_file_for_valid_">Checks modules in the 'cvsacl' file for valid files and directories</a></strong><br />
    153 </li>
    154 <li><strong><a name="item_accurately_report_restricted_entries_and_their_mat">Accurately report restricted entries and their matching patterns</a></strong><br />
    155 </li>
    156 <li><strong><a name="item_simplified_and_commented_overly_complex_perl_regex">Simplified and commented overly complex PERL REGEXPs for readability 
    157 and maintainability</a></strong><br />
    158 </li>
    159 <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 />
    160 </li>
    161 <li><strong><a name="item_file">Get rid of opaque ``karma'' messages in favor of user-friendly messages
    162 that describe which user, <code>file(s)</code> and <code>branch(es)</code> were disallowed.</a></strong><br />
    163 </li>
    164 <li><strong><a name="item_add_optional__27restrict_msg_27_file_for_additiona">Add optional 'restrict_msg' file for additional, site-specific 
    165 restriction messages.</a></strong><br />
    166 </li>
    167 <li><strong><a name="item_userid">Take a ``-u'' parameter for $USER from commit_prep so that the script
    168 can do restrictions based on the client-side userId rather than the
    169 server-side userId (usually 'cvs').</a></strong><br />
    170 </li>
    171 (See discussion below on ``Admin Setup'' for more on this point.)
    172 <p></p>
    173 <li><strong><a name="item_added_a_lot_more_debug_trace">Added a lot more debug trace</a></strong><br />
    174 </li>
    175 <li><strong><a name="item_tested_these_restrictions_with_concurrent_use_of_p">Tested these restrictions with concurrent use of pserver and SSH
    176 access to model our transition from pserver to ext access.</a></strong><br />
    177 </li>
    178 <li><strong><a name="item_added_logging_of_restricted_commit_attempts_2e_res">Added logging of restricted commit attempts.
    179 Restricted commits can be sent to a default file:
    180 $CVSROOT/CVSROOT/restrictlog or to one passed to the script
    181 via the -f command parameter.</a></strong><br />
    182 </li>
    183 </ul>
    184 <p>
    185 </p>
    186 <h2><a name="todos">ToDoS</a></h2>
    187 <ul>
    188 <li><strong><a name="item_need_to_deal_with_pserver_2fssh_transition_with_co">Need to deal with pserver/SSH transition with conflicting umasks?</a></strong><br />
    189 </li>
    190 <li><strong><a name="item_use_a_cpan_module_to_handle_command_parameters_2e">Use a CPAN module to handle command parameters.</a></strong><br />
    191 </li>
    192 <li><strong><a name="item_use_a_cpan_module_to_clone_data_structures_2e">Use a CPAN module to clone data structures.</a></strong><br />
    193 </li>
    194 </ul>
    195 <p>
    196 </p>
    197 <hr />
    198 <h1><a name="version_information">Version Information</a></h1>
    199 <p>This is not offered as a fix to the original 'cvs_acls' script since it 
    200 differs substantially in goals and methods from the original and there 
    201 are probably a significant number of people out there that still require 
    202 the original version's functionality.</p>
    203 <p>The 'cvsacl' file flags of 'allow' and 'deny' were intentionally 
    204 changed to 'allow' and 'deny' because there are enough differences 
    205 between the original script's behavior and this one's that we wanted to
    206 make sure that users will rethink their 'cvsacl' file formats before
    207 plugging in this newer script.</p>
    208 <p>Please note that there has been very limited cross-platform testing of 
    209 this script!!! (We did not have the time or resources to do exhaustive
    210 cross-platform testing.)</p>
    211 <p>It was developed and tested under Red Hat Linux 9.0 using PERL 5.8.0.
    212 Additionally, it was built and tested under Red Hat Linux 7.3 using 
    213 PERL 5.6.1.</p>
    214 <p>$Id: cvs_acls.html,v 1.1.1.1 2009/04/07 22:10:10 christos Exp $</p>
    215 <p>This version is based on the 1.11.13 version of cvs_acls
    216 <a href="mailto:peter.connolly (a] cnet.com">peter.connolly (a] cnet.com</a> (Peter Connolly)</p>
    217 <pre>
    218   Access control lists for CVS.  dgg (a] ksr.com (David G. Grubbs)
    219   Branch specific controls added by voisine (a] bytemobile.com (Aaron Voisine)</pre>
    220 <p>
    221 </p>
    222 <hr />
    223 <h1><a name="installation">Installation</a></h1>
    224 <p>To use this program, do the following four things:</p>
    225 <p>0. Install PERL, version 5.6.1 or 5.8.0.</p>
    226 <p>1. Admin Setup:</p>
    227 <pre>
    228    There are two choices here.</pre>
    229 <pre>
    230    a) The first option is to use the $ENV{&quot;USER&quot;}, server-side userId
    231       (from the third column of your pserver 'passwd' file) as the basis for 
    232       your restrictions.  In this case, you will (at a minimum) want to set
    233       up a new &quot;cvsadmin&quot; userId and group on the pserver machine.  
    234       CVS administrators will then set up their 'passwd' file entries to
    235       run either as &quot;cvs&quot; (for regular users) or as &quot;cvsadmin&quot; (for power 
    236       users).  Correspondingly, your 'cvsacl' file will only list 'cvs'
    237       and 'cvsadmin' as the userIds in the second column.</pre>
    238 <pre>
    239       Commentary: A potential weakness of this is that the xinetd 
    240       cvspserver process will need to run as 'root' in order to switch 
    241       between the 'cvs' and the 'cvsadmin' userIds.  Some sysadmins don't
    242       like situations like this and may want to chroot the process.
    243       Talk to them about this point...</pre>
    244 <pre>
    245    b) The second option is to use the client-side userId as the basis for
    246       your restrictions.  In this case, all the xinetd cvspserver processes 
    247       can run as userId 'cvs' and no 'root' userId is required.  If you have
    248       a 'passwd' file that lists 'cvs' as the effective run-time userId for
    249       all your users, then no changes to this file are needed.  Your 'cvsacl'
    250       file will use the individual, client-side userIds in its 2nd column.</pre>
    251 <pre>
    252       As long as the userIds in pserver's 'passwd' file match those userIds 
    253       that your Linux server know about, this approach is ideal if you are 
    254       planning to move from pserver to SSH access at some later point in time.
    255       Just by switching the CVSROOT var from CVSROOT=:pserver:&lt;userId&gt;... to 
    256       CVSROOT=:ext:&lt;userId&gt;..., users can switch over to SSH access without
    257       any other administrative changes.  When all users have switched over to
    258       SSH, the inherently insecure xinetd cvspserver process can be disabled.
    259       [<a href="http://ximbiot.com/cvs/manual/cvs-1.11.17/cvs_2.html#SEC32">http://ximbiot.com/cvs/manual/cvs-1.11.17/cvs_2.html#SEC32</a>]</pre>
    260 <pre>
    261       :TODO: The only potential glitch with the SSH approach is the possibility 
    262       that each user can have differing umasks that might interfere with one 
    263       another, especially during a transition from pserver to SSH.  As noted
    264       in the ToDo section, this needs a good strategy and set of tests for that 
    265       yet...</pre>
    266 <p>2. Put two lines, as the *only* non-comment lines, in your commitinfo file:</p>
    267 <pre>
    268    ALL $CVSROOT/CVSROOT/commit_prep 
    269    ALL $CVSROOT/CVSROOT/cvs_acls [-d][-u $USER ][-f &lt;logfilename&gt;]</pre>
    270 <pre>
    271    where &quot;-d&quot; turns on debug trace
    272          &quot;-u $USER&quot; passes the client-side userId to cvs_acls 
    273          &quot;-f &lt;logfilename&quot;&gt; overrides the default filename used to log
    274                             restricted commit attempts.</pre>
    275 <pre>
    276    (These are handled in the processArgs() subroutine.)</pre>
    277 <p>If you are using client-side userIds to restrict access to your 
    278 repository, make sure that they are in this order since the commit_prep 
    279 script is required in order to pass the $USER parameter.</p>
    280 <p>A final note about the repository matching pattern.  The example above
    281 uses ``ALL'' but note that this means that the cvs_acls script will run
    282 for each and every commit in your repository.  Obviously, in a large
    283 repository this adds up to a lot of overhead that may not be necesary. 
    284 A better strategy is to use a repository pattern that is more specific 
    285 to the areas that you wish to secure.</p>
    286 <p>3. Install this file as $CVSROOT/CVSROOT/cvs_acls and make it executable.</p>
    287 <p>4. Create a file named CVSROOT/cvsacl and optionally add it to
    288    CVSROOT/checkoutlist and check it in.  See the CVS manual's
    289    administrative files section about checkoutlist.  Typically:</p>
    290 <pre>
    291    $ cvs checkout CVSROOT
    292    $ cd CVSROOT
    293    [ create the cvsacl file, include 'commitinfo' line ]
    294    [ add cvsacl to checkoutlist ]
    295    $ cvs add cvsacl
    296    $ cvs commit -m 'Added cvsacl for use with cvs_acls.' cvsacl checkoutlist</pre>
    297 <p>Note: The format of the 'cvsacl' file is described in detail immediately 
    298 below but here is an important set up point:</p>
    299 <pre>
    300    Make sure to include a line like the following:</pre>
    301 <pre>
    302      deny||CVSROOT/commitinfo CVSROOT/cvsacl
    303      allow|cvsadmin|CVSROOT/commitinfo CVSROOT/cvsacl</pre>
    304 <pre>
    305    that restricts access to commitinfo and cvsacl since this would be one of
    306    the easiest &quot;end runs&quot; around this ACL approach. ('commitinfo' has the 
    307    line that executes the cvs_acls script and, of course, all the 
    308    restrictions are in 'cvsacl'.)</pre>
    309 <p>5. (Optional) Create a 'restrict_msg' file in the $CVSROOT/CVSROOT directory.
    310    Whenever there is a restricted file or dir message, cvs_acls will look 
    311    for this file and, if it exists, print its contents as part of the 
    312    commit-denial message.  This gives you a chance to print any site-specific
    313    information (e.g., who to call, what procedures to look up,...) whenever
    314    a commit is denied.</p>
    315 <p>
    316 </p>
    317 <hr />
    318 <h1><a name="format_of_the_cvsacl_file">Format of the cvsacl file</a></h1>
    319 <p>The 'cvsacl' file determines whether you may commit files.  It contains lines
    320 read from top to bottom, keeping track of whether a given user, repository
    321 and branch combination is ``allowed'' or ``denied.''  The script will assume 
    322 ``allowed'' on all repository paths until 'allow' and 'deny' rules change 
    323 that default.</p>
    324 <p>The normal pattern is to specify an 'deny' rule to turn off
    325 access to ALL users, then follow it with a matching 'allow' rule that will 
    326 turn on access for a select set of users.  In the case of multiple rules for
    327 the same user, repository and branch, the last one takes precedence.</p>
    328 <p>Blank lines and lines with only comments are ignored.  Any other lines not 
    329 beginning with ``allow'' or ``deny'' are logged to the restrict_log file.</p>
    330 <p>Lines beginning with ``allow'' or ``deny'' are assumed to be '|'-separated
    331 triples: (All spaces and tabs are ignored in a line.)</p>
    332 <pre>
    333   {allow.*,deny.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]</pre>
    334 <pre>
    335    1. String starting with &quot;allow&quot; or &quot;deny&quot;.
    336    2. Optional, comma-separated list of usernames.
    337    3. Optional, comma-separated list of repository pathnames.
    338       These are pathnames relative to $CVSROOT.  They can be directories or
    339       filenames.  A directory name allows or restricts access to all files and
    340       directories below it. One line can have either directories or filenames
    341       but not both.
    342    4. Optional, comma-separated list of branch tags.
    343       If not specified, all branches are assumed. Use HEAD to reference the
    344       main branch.</pre>
    345 <p>Example:  (Note: No in-line comments.)</p>
    346 <pre>
    347    # ----- Make whole repository unavailable.
    348    deny</pre>
    349 <pre>
    350    # ----- Except for user &quot;dgg&quot;.
    351    allow|dgg</pre>
    352 <pre>
    353    # ----- Except when &quot;fred&quot; or &quot;john&quot; commit to the 
    354    #       module whose repository is &quot;bin/ls&quot;
    355    allow|fred, john|bin/ls</pre>
    356 <pre>
    357    # ----- Except when &quot;ed&quot; commits to the &quot;stable&quot; 
    358    #       branch of the &quot;bin/ls&quot; repository
    359    allow|ed|/bin/ls|stable</pre>
    360 <p>
    361 </p>
    362 <hr />
    363 <h1><a name="program_logic">Program Logic</a></h1>
    364 <p>CVS passes to @ARGV an absolute directory pathname (the repository
    365 appended to your $CVSROOT variable), followed by a list of filenames
    366 within that directory that are to be committed.</p>
    367 <p>The script walks through the 'cvsacl' file looking for matches on 
    368 the username, repository and branch.</p>
    369 <p>A username match is simply the user's name appearing in the second
    370 column of the cvsacl line in a space-or-comma separate list. If
    371 blank, then any user will match.</p>
    372 <p>A repository match:</p>
    373 <ul>
    374 <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 
    375 examined to see if it is a dir or a file. The line must have 
    376 either files or dirs, but not both. (To simplify the logic.)</a></strong><br />
    377 </li>
    378 <li><strong><a name="item_if_neither_2c_then_assume_the__27cvsacl_27_file_wa">If neither, then assume the 'cvsacl' file was set up in error and
    379 skip that 'allow' line.</a></strong><br />
    380 </li>
    381 <li><strong><a name="item_if_a_dir_2c_then_each_dir_pattern_is_matched_separ">If a dir, then each dir pattern is matched separately against the 
    382 beginning of each of the committed files in @ARGV.</a></strong><br />
    383 </li>
    384 <li><strong><a name="item_if_a_file_2c_then_each_file_pattern_is_matched_exa">If a file, then each file pattern is matched exactly against each
    385 of the files to be committed in @ARGV.</a></strong><br />
    386 </li>
    387 <li><strong><a name="item_repository_and_branch_must_both_match_together_2e_">Repository and branch must BOTH match together. This is to cover
    388 the use case where a user has multiple branches checked out in
    389 a single work directory. Commit files can be from different
    390 branches.</a></strong><br />
    391 </li>
    392 A branch match is either:
    393 <ul>
    394 <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 />
    395 </li>
    396 <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 
    397 the tag names for $ARGV[1..$#ARGV] found in the %branches file.</a></strong><br />
    398 </li>
    399 </ul>
    400 <li><strong><a name="item__27allow_27_match_remove_that_match_from_the_tally">'allow' match remove that match from the tally map.</a></strong><br />
    401 </li>
    402 <li><strong><a name="item_restricted">Restricted ('deny') matches are saved in the %repository_matches 
    403 table.</a></strong><br />
    404 </li>
    405 <li><strong><a name="item_if_there_is_a_match_on_user_2c_repository_and_bran">If there is a match on user, repository and branch:</a></strong><br />
    406 </li>
    407 <pre>
    408   If repository, branch and user match
    409     if 'deny'
    410       add %repository_matches entries to %restricted_entries
    411     else if 'allow'
    412       remove %repository_matches entries from %restricted_entries</pre>
    413 <li><strong><a name="item_at_the_end_of_all_the__27cvsacl_27_line_checks_2c_">At the end of all the 'cvsacl' line checks, check to see if there
    414 are any entries in the %restricted_entries.  If so, then deny the
    415 commit.</a></strong><br />
    416 </li>
    417 </ul>
    418 <p>
    419 </p>
    420 <h2><a name="pseudocode">Pseudocode</a></h2>
    421 <pre>
    422      read CVS/Entries file and create branch{file}-&gt;{branch} hash table
    423    + for each 'allow' and 'deny' line in the 'cvsacl' file:
    424    |   user match?   
    425    |     - Yes: set $user_match       = 1;
    426    |   repository and branch match?
    427    |     - Yes: add to %repository_matches;
    428    |   did user, repository match?
    429    |     - Yes: if 'deny' then 
    430    |                add %repository_matches -&gt; %restricted_entries
    431    |            if 'allow'   then 
    432    |                remove %repository_matches &lt;- %restricted_entries
    433    + end for loop
    434      any saved restrictions?
    435        no:  exit, 
    436             set exit code allowing commits and exit
    437        yes: report restrictions, 
    438             set exit code prohibiting commits and exit</pre>
    439 <p>
    440 </p>
    441 <h2><a name="sanity_check">Sanity Check</a></h2>
    442 <pre>
    443   1) file allow trumps a dir deny
    444      deny||java/lib
    445      allow||java/lib/README
    446   2) dir allow can undo a file deny
    447      deny||java/lib/README
    448      allow||java/lib
    449   3) file deny trumps a dir allow
    450      allow||java/lib
    451      deny||java/lib/README
    452   4) dir deny trumps a file allow
    453      allow||java/lib/README
    454      deny||java/lib
    455   ... so last match always takes precedence</pre>
    456 
    457 </body>
    458 
    459 </html>
    460