From: Jason Molenda <jason@molenda.com>
To: overseers@sources.redhat.com
Subject: Re: Controlling cvs commit access
Date: Sat, 30 Dec 2000 06:08:00 -0000 [thread overview]
Message-ID: <20001016231918.A17732@shell17.ba.best.com> (raw)
In-Reply-To: <20001016194459.B3344@cygnus.com>
[-- Attachment #1: Type: text/plain, Size: 1434 bytes --]
On Mon, Oct 16, 2000 at 07:44:59PM -0400, Chris Faylor wrote:
> On Sat, Oct 14, 2000 at 12:27:46AM -0700, Jason Molenda wrote:
> >We have a large engineering organization at Yahoo with a wider
> >variety of cvs uses than I'd seen before. I needed some fine-grained
> >way of controlling access to parts of the repository, so I wrote
> >a nice little access control list script.
> I haven't seen anyone reply to this, but I would say that this is
> very interesting and I'd welcome a patch or even a patched CVS.
I'm not sure if it'll be of any use on sourceware/gcc or inside
Cygnus, but I'll attach the script and an example config files (the
(path)names have all been changed to protect the guilty). You drop
a call to it in your CVSROOT/commitinfo file like this:
ALL $CVSROOT/CVSROOT/cvs-acl.pl
It will be run in addition to other directory-specific entries you
might have in there (e.g. commit-prep). It will look for the
cvs-acl.conf file in $CVSROOT/CVSROOT unless you tell it to look
elsewhere via a command line option.
If there's enough interest, I could write some real documentation
(in the form of the much-beloved README file) and put a tar file
somewhere, but I kind of suspect there isn't. ;-)
Jason
Free the Software!
PS- Yes, I know my perl is not so great.
PPS- Despite being a pretty newish script, I've tested it quite a
lot. I'd be surprised if you found any problems while trying to
use it.
[-- Attachment #2: cvs-acl.pl --]
[-- Type: text/x-perl, Size: 15500 bytes --]
#! /usr/bin/perl -w
#
# Access control lists for CVS. Written by Jason Molenda who works
# for Yahoo!, jason-ca@molenda.com.
# Version 1.1 2000-10-16
# This is an entirely separate script from David Grubb's "cvs_acls.pl"
# which is included in the cvs distributions' contrib/ directory.
# David is obviously more familiar with perl, and his script is a lot
# more straightforward than mine. On the other hand, his ACL mechanism
# is not very flexible. My cvs-acls.pl script is many times more complex,
# but it makes it possible to express more involved permission sets.
# For instance, it is not possible with David's cvs_acls.pl to express
# "User foo is only able to check in to directory bar", without explicitly
# naming every user who has global checkin permissions.
use File::Basename;
use Getopt::Long;
use strict;
use vars qw ($allow_commit $deny_commit $DEBUG $DUMP_ACL_DB $script_name);
$allow_commit = 0;
$deny_commit = 1;
$DEBUG = 0;
$DUMP_ACL_DB = 0;
#
# main
#
# It all starts here.
#
sub main
{
my ($cvs_acls, $username, $dirname, @filenames);
my ($result, $denial_message, $cvsroot, $config_file);
my ($COMMIT_LOG_FILENAME, $LOG_COMMITS);
use vars qw($opt_debug $opt_dump_acls $opt_conf_file $opt_commit_logfile);
use vars qw($opt_help $opt_username);
$script_name = basename ($0);
##
## Start of argument parsing
##
GetOptions ("debug", "dump-acls|dump-acl|dump", "conf-file|conf|file=s",
"commit-logfile|commit-log-file|commit-log=s", "help",
"username|user|name=s");
if (defined ($opt_help))
{
usage ();
}
if (defined ($opt_debug))
{
$DEBUG = 1;
}
if (defined ($opt_dump_acls))
{
$DUMP_ACL_DB = 1;
}
$cvsroot = $ENV{'CVSROOT'};
if (defined ($opt_conf_file))
{
$config_file = $opt_conf_file;
}
else
{
$config_file = $cvsroot . "/CVSROOT/cvs-acl.conf";
}
if ($DEBUG)
{
print STDERR "DEBUG: Using $config_file as our config file.\n";
}
if (defined ($opt_commit_logfile))
{
$LOG_COMMITS = 1;
$COMMIT_LOG_FILENAME = $opt_commit_logfile;
}
elsif (defined ($LOG_COMMITS) && $LOG_COMMITS == 1)
{
$COMMIT_LOG_FILENAME = $cvsroot . "/CVSROOT/commit-logfile";
}
$cvs_acls = read_in_acls ($config_file);
if ($DUMP_ACL_DB)
{
dump_acls ($cvs_acls);
}
if (@ARGV < 2)
{
usage ();
}
if (defined ($opt_username))
{
$username = $opt_username;
}
else
{
# David Grubb's cvs_acls sets username like this:
# $myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"});
$username = (getpwuid($<))[0];
}
if (!defined ($username) || $username eq "")
{
print STDERR "$script_name: unable to determine username.\n";
exit $allow_commit;
}
if ($DEBUG)
{
print STDERR "DEBUG: Doing checks as user `$username'.\n";
}
##
## End of argument parsing
##
$dirname = shift @ARGV;
if ($dirname =~ m#^$cvsroot/#)
{
$dirname =~ s#^$cvsroot/##;
}
$dirname = canonical_dirname ($dirname);
@filenames = @ARGV;
foreach my $file (@filenames)
{
($result, $denial_message) =
check_acl ($dirname, $file, $username, $cvs_acls);
last if ($result eq 'deny');
}
# There isn't any locking here; this is not reliable. If two
# people commit at the same time (two different parts of the repo),
# their commits will stomp on each other and you'll get a truncated
# logfile entry. This is only intended as a debugging feature to ensure
# that the script is performing reasonably.
if ($LOG_COMMITS)
{
my $timestamp = scalar (localtime (time ()));
if (open (LOGFILE, ">> $COMMIT_LOG_FILENAME"))
{
print LOGFILE "[$timestamp] $result $username $dirname @filenames\n";
close (LOGFILE);
}
}
if ($result eq 'deny')
{
if (defined ($denial_message) && $denial_message ne '')
{
print STDERR $denial_message . "\n";
}
exit $deny_commit;
}
exit $allow_commit;
}
#
# usage
#
# Tell the users what they can do.
#
sub usage
{
print STDERR "Usage: $script_name [--help] [--debug] [--conf=FILENAME]\n";
print STDERR " [--dump-acls] [--commit-logfile=FILENAME] [--username=NAME]\n";
print STDERR " directory filename...\n";
print STDERR "\n";
print STDERR " --help This message.\n";
print STDERR " --debug Print out debugging information while running.\n";
print STDERR " --conf Location of ACL configuration file\n";
print STDERR " --dump-acls Print out the ACL before doing anything else.\n";
print STDERR " --commit-logfile Keep a record of all commits, and whether they were\n";
print STDERR " allowed or denied.\n";
print STDERR " --username Useful for testing, it lets you specify what uname to try as.\n";
print STDERR "\n";
print STDERR "Default location for --conf is \$CVSROOT/CVSROOT/cvs-acl.conf.\n";
exit ($allow_commit);
}
# read_in_acls (ACL_CONFIG_FILENAME)
#
# Returns a pointer to a hash. The top-level elements in the hash
# are directory/filenames (or "*ALL*" to indicate the entire repository).
# The elements the next level down are usernames (or "*ALL*" to indicate
# all users), and the 'permission' element below that is an enumerated
# type of either 'allow' or 'deny'. If it is deny, there is an optional
# denial message in the 'denial message' element.
#
# In short, you get a hash like this:
#
# $acl_hash->{"schmoo/libraries"}->{"meng"}->{"permission"} eq "deny"
# $acl_hash->{"schmoo/libraries"}->{"meng"}->{"denial message"} eq
# "Anonymous account meng not allowed to check in files."
# The format of the file that is is reading is (briefly) like this:
#
# DIRNAME[/FILENAME] : USERNAME[, USERNAME...] : {allow|deny} : DENIAL-MSG
#
# DIRNAME and USERNAME may be empty or "*ALL*", both of which mean
# "Applies to every dir/user".
sub read_in_acls
{
my ($acl_config_filename) = @_;
my $split_line;
my $acls = {};
$split_line = undef;
if (!open (ACLS, $acl_config_filename))
{
print STDERR "$script_name: Unable to open '$acl_config_filename'\n";
exit ($allow_commit);
}
while (<ACLS>)
{
next if (/^\s*$/ || /^\s*\#/);
chomp;
if (defined ($split_line))
{
s/^\s+/ /;
$_ = $split_line . $_;
$split_line = undef;
}
if (/\\$/)
{
chop;
$split_line ||= '';
$split_line = $split_line . $_;
next;
}
next if ($_ !~ /.*:.*:/);
my ($dirname, $usernames, $action, $deny_msg) = split (/[\s,]*:[\s,]*/);
if (!defined ($dirname) || $dirname eq "" ||
$dirname eq "*ALL*" || $dirname eq "*all*" ||
$dirname eq "*ANY*" || $dirname eq "*any*")
{
$dirname = "*ALL*";
}
if (!defined ($usernames) || $usernames eq "" ||
$usernames eq "*ALL*" || $usernames eq "*all*" ||
$usernames eq "*ANY*" || $usernames eq "*any*")
{
$usernames = "*ALL*";
}
$dirname = canonical_dirname ($dirname);
if (!defined ($action) || $action eq "" ||
($action ne "allow" && ($action ne "deny")))
{
print STDERR "$script_name: Badly formed line encountered " .
"- missing action. Line:\n$_\n";
next;
}
foreach my $uname (split (/[\s,]*,[\s,]*/, $usernames))
{
$acls->{$dirname}{$uname}{'permission'} = $action;
if (defined ($deny_msg) && $deny_msg ne "")
{
$acls->{$dirname}{$uname}{'denial message'} = $deny_msg;
}
}
}
close (ACLS);
return ($acls);
}
##
## check_acl
##
## This is the main function for checking a username/directory/filename
## against the ACL list. It returns a list of (PERMISSION-RESULTS,
## DENIAL-MESSAGE). PERMISSION-RESULTS is either 'allow' or 'deny'.
## DENIAL-MESSAGE is either undef or a string explaining why the commit
## was denied.
##
## Returns a result ("deny" || "allow") and a denial message, if any.
## It will return (undef, undef) if no matching directory entry is found.
sub check_acl
{
my ($dirname, $filename, $username, $aclh) = @_;
my $current_permission;
my $tmp_perm_holder;
my $dir_so_far;
my $denial_node = undef;
my $tmp_node_holder = undef;
my $denial_message = undef;
$dirname = canonical_dirname ($dirname);
$filename = canonical_dirname ($filename);
($current_permission, $denial_node) =
check_single_dir_acl ($username, "", $aclh);
foreach my $cur_dir_component (split (/\//, $dirname))
{
if (defined ($dir_so_far))
{
$dir_so_far = $dir_so_far . "/" . $cur_dir_component;
}
else
{
$dir_so_far = $cur_dir_component;
}
($tmp_perm_holder, $tmp_node_holder) =
check_single_dir_acl ($username, $dir_so_far, $aclh);
if (defined ($tmp_perm_holder))
{
$current_permission = $tmp_perm_holder;
}
if (defined ($tmp_node_holder))
{
$denial_node = $tmp_node_holder;
}
if ($DEBUG)
{
$tmp_perm_holder ||= "";
print STDERR "DEBUG: Check dir component $dir_so_far, perm " .
"$tmp_perm_holder\n";
}
}
($tmp_perm_holder, $tmp_node_holder) =
check_single_dir_acl ($username, $dirname ."/". $filename, $aclh);
if ($DEBUG)
{
$tmp_perm_holder ||= "";
print STDERR "DEBUG: Check entry ${dirname}/${filename}, perm " .
"$tmp_perm_holder\n";
}
if (defined ($tmp_perm_holder) && $tmp_perm_holder ne "")
{
$current_permission = $tmp_perm_holder;
if (defined ($tmp_node_holder))
{
$denial_node = $tmp_node_holder;
}
}
if ($current_permission eq 'deny' &&
defined ($denial_node->{"denial message"}) &&
$denial_node->{"denial message"} ne "")
{
$denial_message = $denial_node->{"denial message"};
}
if (defined ($current_permission) && $current_permission ne "")
{
if ($current_permission eq "allow")
{
return ($current_permission, undef);
}
else
{
return ($current_permission, $denial_message);
}
}
else
{
return ('allow');
}
}
##
## check_single_dir_acl ()
##
## Examine a single directory's entry and determine if a checkin to this
## dir is allowed. This func knows nothing about permissions inherited
## from superior directories. It returns a tuple of (PERMISSION, DENIAL-NODE).
## PERMISSION is either 'allow' or 'deny' and DENIAL-NODE is a pointer to
## the directory/user node that caused the denial.
## Dereferencing $DENIAL-NODE->{"denial message"} may work.
##
## NOTE: Originally I had some clever reason for passing back a pointer
## to the denial node, but I can't see any point to it. The denial
## msg, if any, should be returned here instead of dinking around with
## a pointer.
sub check_single_dir_acl
{
my ($username, $dirname, $aclh) = @_;
my $permission = undef;
my $node = undef;
my ($have_global_permission_entry, $username_specified) = (1, 1);
if (!defined ($dirname) || $dirname eq "")
{
$dirname = "*ALL*";
}
if (!defined ($aclh->{$dirname}))
{
return (undef, undef);
}
if (!defined ($aclh->{$dirname}->{"*ALL*"}))
{
$have_global_permission_entry = 0;
}
if (!defined ($username) || $username eq "" || $username eq "*ALL*")
{
$username_specified = 0;
$username = "*ALL*";
}
if (!$have_global_permission_entry && !$username_specified)
{
return (undef, undef);
}
if (!defined ($aclh->{$dirname}->{"*ALL*"}->{"permission"}) &&
!defined ($aclh->{$dirname}->{$username}->{"permission"}))
{
return (undef, undef);
}
if (defined ($aclh->{$dirname}->{"*ALL*"}->{"permission"}))
{
$node = $aclh->{$dirname}->{"*ALL*"};
$permission = $node->{"permission"};
}
if ($username_specified &&
defined ($aclh->{$dirname}->{$username}) &&
defined ($aclh->{$dirname}->{$username}->{"permission"}))
{
$node = $aclh->{$dirname}->{$username};
$permission = $node->{"permission"};
}
if ($DEBUG)
{
print STDERR "DEBUG: user $username dirname $dirname " .
"permission $permission\n";
}
if (!defined ($permission) || $permission eq 'allow')
{
return ($permission, undef);
}
else
{
return ($permission, $node);
}
}
##
## canonical_dirname ()
##
## Eliminate slashes at the beginning, end of a string. Eliminiate
## multiple slashes in the string. Eliminate "./" at the beginning of
## a string.
sub canonical_dirname
{
my ($string) = @_;
$string =~ s#^\/*##;
$string =~ s#^\.\/##;
$string =~ s#/*$##;
$string =~ s#/+#/#g;
return ($string);
}
##
## dump_acls
##
## Dumps out the ACL hash in a human-readable format.
##
sub dump_acls
{
my ($aclh) = @_;
foreach my $dirname (sort keys %$aclh)
{
my $allow_namelist = '';
my $deny_namelist = '';
foreach my $username (sort keys %{$aclh->{$dirname}})
{
if (!defined ($aclh->{$dirname}->{$username}->{'permission'}))
{
# This indicates that our hash is not being treated correctly.
# Someone is touching the hash when they shouldn't.
# Paper over it.
if ($DEBUG)
{
print STDERR "DEBUG: '$dirname' u '$username'\n";
}
next;
}
if ($aclh->{$dirname}->{$username}->{'permission'} eq "allow")
{
if ($allow_namelist ne '')
{
$allow_namelist = $allow_namelist . ", ";
}
$allow_namelist = $allow_namelist . $username;
}
else
{
if ($deny_namelist ne '')
{
$deny_namelist = $deny_namelist . ", ";
}
$deny_namelist = $deny_namelist . $username;
}
}
print "ACL_DUMP: $dirname:";
if ($allow_namelist ne '')
{
print " Allow $allow_namelist";
}
if ($deny_namelist ne '')
{
print " Deny $deny_namelist";
}
print "\n";
}
}
main ();
WARNING: multiple messages have this Message-ID
From: Jason Molenda <jason@molenda.com>
To: overseers@sources.redhat.com
Subject: Re: Controlling cvs commit access
Date: Mon, 16 Oct 2000 23:20:00 -0000 [thread overview]
Message-ID: <20001016231918.A17732@shell17.ba.best.com> (raw)
Message-ID: <20001016232000.LkEF9NPO9idDDB1R4ocVTNmBip3UsDmFwTNKo4I1XBw@z> (raw)
In-Reply-To: <20001016194459.B3344@cygnus.com>
[-- Attachment #1: Type: text/plain, Size: 1434 bytes --]
On Mon, Oct 16, 2000 at 07:44:59PM -0400, Chris Faylor wrote:
> On Sat, Oct 14, 2000 at 12:27:46AM -0700, Jason Molenda wrote:
> >We have a large engineering organization at Yahoo with a wider
> >variety of cvs uses than I'd seen before. I needed some fine-grained
> >way of controlling access to parts of the repository, so I wrote
> >a nice little access control list script.
> I haven't seen anyone reply to this, but I would say that this is
> very interesting and I'd welcome a patch or even a patched CVS.
I'm not sure if it'll be of any use on sourceware/gcc or inside
Cygnus, but I'll attach the script and an example config files (the
(path)names have all been changed to protect the guilty). You drop
a call to it in your CVSROOT/commitinfo file like this:
ALL $CVSROOT/CVSROOT/cvs-acl.pl
It will be run in addition to other directory-specific entries you
might have in there (e.g. commit-prep). It will look for the
cvs-acl.conf file in $CVSROOT/CVSROOT unless you tell it to look
elsewhere via a command line option.
If there's enough interest, I could write some real documentation
(in the form of the much-beloved README file) and put a tar file
somewhere, but I kind of suspect there isn't. ;-)
Jason
Free the Software!
PS- Yes, I know my perl is not so great.
PPS- Despite being a pretty newish script, I've tested it quite a
lot. I'd be surprised if you found any problems while trying to
use it.
[-- Attachment #2: cvs-acl.pl --]
[-- Type: text/x-perl, Size: 15500 bytes --]
#! /usr/bin/perl -w
#
# Access control lists for CVS. Written by Jason Molenda who works
# for Yahoo!, jason-ca@molenda.com.
# Version 1.1 2000-10-16
# This is an entirely separate script from David Grubb's "cvs_acls.pl"
# which is included in the cvs distributions' contrib/ directory.
# David is obviously more familiar with perl, and his script is a lot
# more straightforward than mine. On the other hand, his ACL mechanism
# is not very flexible. My cvs-acls.pl script is many times more complex,
# but it makes it possible to express more involved permission sets.
# For instance, it is not possible with David's cvs_acls.pl to express
# "User foo is only able to check in to directory bar", without explicitly
# naming every user who has global checkin permissions.
use File::Basename;
use Getopt::Long;
use strict;
use vars qw ($allow_commit $deny_commit $DEBUG $DUMP_ACL_DB $script_name);
$allow_commit = 0;
$deny_commit = 1;
$DEBUG = 0;
$DUMP_ACL_DB = 0;
#
# main
#
# It all starts here.
#
sub main
{
my ($cvs_acls, $username, $dirname, @filenames);
my ($result, $denial_message, $cvsroot, $config_file);
my ($COMMIT_LOG_FILENAME, $LOG_COMMITS);
use vars qw($opt_debug $opt_dump_acls $opt_conf_file $opt_commit_logfile);
use vars qw($opt_help $opt_username);
$script_name = basename ($0);
##
## Start of argument parsing
##
GetOptions ("debug", "dump-acls|dump-acl|dump", "conf-file|conf|file=s",
"commit-logfile|commit-log-file|commit-log=s", "help",
"username|user|name=s");
if (defined ($opt_help))
{
usage ();
}
if (defined ($opt_debug))
{
$DEBUG = 1;
}
if (defined ($opt_dump_acls))
{
$DUMP_ACL_DB = 1;
}
$cvsroot = $ENV{'CVSROOT'};
if (defined ($opt_conf_file))
{
$config_file = $opt_conf_file;
}
else
{
$config_file = $cvsroot . "/CVSROOT/cvs-acl.conf";
}
if ($DEBUG)
{
print STDERR "DEBUG: Using $config_file as our config file.\n";
}
if (defined ($opt_commit_logfile))
{
$LOG_COMMITS = 1;
$COMMIT_LOG_FILENAME = $opt_commit_logfile;
}
elsif (defined ($LOG_COMMITS) && $LOG_COMMITS == 1)
{
$COMMIT_LOG_FILENAME = $cvsroot . "/CVSROOT/commit-logfile";
}
$cvs_acls = read_in_acls ($config_file);
if ($DUMP_ACL_DB)
{
dump_acls ($cvs_acls);
}
if (@ARGV < 2)
{
usage ();
}
if (defined ($opt_username))
{
$username = $opt_username;
}
else
{
# David Grubb's cvs_acls sets username like this:
# $myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"});
$username = (getpwuid($<))[0];
}
if (!defined ($username) || $username eq "")
{
print STDERR "$script_name: unable to determine username.\n";
exit $allow_commit;
}
if ($DEBUG)
{
print STDERR "DEBUG: Doing checks as user `$username'.\n";
}
##
## End of argument parsing
##
$dirname = shift @ARGV;
if ($dirname =~ m#^$cvsroot/#)
{
$dirname =~ s#^$cvsroot/##;
}
$dirname = canonical_dirname ($dirname);
@filenames = @ARGV;
foreach my $file (@filenames)
{
($result, $denial_message) =
check_acl ($dirname, $file, $username, $cvs_acls);
last if ($result eq 'deny');
}
# There isn't any locking here; this is not reliable. If two
# people commit at the same time (two different parts of the repo),
# their commits will stomp on each other and you'll get a truncated
# logfile entry. This is only intended as a debugging feature to ensure
# that the script is performing reasonably.
if ($LOG_COMMITS)
{
my $timestamp = scalar (localtime (time ()));
if (open (LOGFILE, ">> $COMMIT_LOG_FILENAME"))
{
print LOGFILE "[$timestamp] $result $username $dirname @filenames\n";
close (LOGFILE);
}
}
if ($result eq 'deny')
{
if (defined ($denial_message) && $denial_message ne '')
{
print STDERR $denial_message . "\n";
}
exit $deny_commit;
}
exit $allow_commit;
}
#
# usage
#
# Tell the users what they can do.
#
sub usage
{
print STDERR "Usage: $script_name [--help] [--debug] [--conf=FILENAME]\n";
print STDERR " [--dump-acls] [--commit-logfile=FILENAME] [--username=NAME]\n";
print STDERR " directory filename...\n";
print STDERR "\n";
print STDERR " --help This message.\n";
print STDERR " --debug Print out debugging information while running.\n";
print STDERR " --conf Location of ACL configuration file\n";
print STDERR " --dump-acls Print out the ACL before doing anything else.\n";
print STDERR " --commit-logfile Keep a record of all commits, and whether they were\n";
print STDERR " allowed or denied.\n";
print STDERR " --username Useful for testing, it lets you specify what uname to try as.\n";
print STDERR "\n";
print STDERR "Default location for --conf is \$CVSROOT/CVSROOT/cvs-acl.conf.\n";
exit ($allow_commit);
}
# read_in_acls (ACL_CONFIG_FILENAME)
#
# Returns a pointer to a hash. The top-level elements in the hash
# are directory/filenames (or "*ALL*" to indicate the entire repository).
# The elements the next level down are usernames (or "*ALL*" to indicate
# all users), and the 'permission' element below that is an enumerated
# type of either 'allow' or 'deny'. If it is deny, there is an optional
# denial message in the 'denial message' element.
#
# In short, you get a hash like this:
#
# $acl_hash->{"schmoo/libraries"}->{"meng"}->{"permission"} eq "deny"
# $acl_hash->{"schmoo/libraries"}->{"meng"}->{"denial message"} eq
# "Anonymous account meng not allowed to check in files."
# The format of the file that is is reading is (briefly) like this:
#
# DIRNAME[/FILENAME] : USERNAME[, USERNAME...] : {allow|deny} : DENIAL-MSG
#
# DIRNAME and USERNAME may be empty or "*ALL*", both of which mean
# "Applies to every dir/user".
sub read_in_acls
{
my ($acl_config_filename) = @_;
my $split_line;
my $acls = {};
$split_line = undef;
if (!open (ACLS, $acl_config_filename))
{
print STDERR "$script_name: Unable to open '$acl_config_filename'\n";
exit ($allow_commit);
}
while (<ACLS>)
{
next if (/^\s*$/ || /^\s*\#/);
chomp;
if (defined ($split_line))
{
s/^\s+/ /;
$_ = $split_line . $_;
$split_line = undef;
}
if (/\\$/)
{
chop;
$split_line ||= '';
$split_line = $split_line . $_;
next;
}
next if ($_ !~ /.*:.*:/);
my ($dirname, $usernames, $action, $deny_msg) = split (/[\s,]*:[\s,]*/);
if (!defined ($dirname) || $dirname eq "" ||
$dirname eq "*ALL*" || $dirname eq "*all*" ||
$dirname eq "*ANY*" || $dirname eq "*any*")
{
$dirname = "*ALL*";
}
if (!defined ($usernames) || $usernames eq "" ||
$usernames eq "*ALL*" || $usernames eq "*all*" ||
$usernames eq "*ANY*" || $usernames eq "*any*")
{
$usernames = "*ALL*";
}
$dirname = canonical_dirname ($dirname);
if (!defined ($action) || $action eq "" ||
($action ne "allow" && ($action ne "deny")))
{
print STDERR "$script_name: Badly formed line encountered " .
"- missing action. Line:\n$_\n";
next;
}
foreach my $uname (split (/[\s,]*,[\s,]*/, $usernames))
{
$acls->{$dirname}{$uname}{'permission'} = $action;
if (defined ($deny_msg) && $deny_msg ne "")
{
$acls->{$dirname}{$uname}{'denial message'} = $deny_msg;
}
}
}
close (ACLS);
return ($acls);
}
##
## check_acl
##
## This is the main function for checking a username/directory/filename
## against the ACL list. It returns a list of (PERMISSION-RESULTS,
## DENIAL-MESSAGE). PERMISSION-RESULTS is either 'allow' or 'deny'.
## DENIAL-MESSAGE is either undef or a string explaining why the commit
## was denied.
##
## Returns a result ("deny" || "allow") and a denial message, if any.
## It will return (undef, undef) if no matching directory entry is found.
sub check_acl
{
my ($dirname, $filename, $username, $aclh) = @_;
my $current_permission;
my $tmp_perm_holder;
my $dir_so_far;
my $denial_node = undef;
my $tmp_node_holder = undef;
my $denial_message = undef;
$dirname = canonical_dirname ($dirname);
$filename = canonical_dirname ($filename);
($current_permission, $denial_node) =
check_single_dir_acl ($username, "", $aclh);
foreach my $cur_dir_component (split (/\//, $dirname))
{
if (defined ($dir_so_far))
{
$dir_so_far = $dir_so_far . "/" . $cur_dir_component;
}
else
{
$dir_so_far = $cur_dir_component;
}
($tmp_perm_holder, $tmp_node_holder) =
check_single_dir_acl ($username, $dir_so_far, $aclh);
if (defined ($tmp_perm_holder))
{
$current_permission = $tmp_perm_holder;
}
if (defined ($tmp_node_holder))
{
$denial_node = $tmp_node_holder;
}
if ($DEBUG)
{
$tmp_perm_holder ||= "";
print STDERR "DEBUG: Check dir component $dir_so_far, perm " .
"$tmp_perm_holder\n";
}
}
($tmp_perm_holder, $tmp_node_holder) =
check_single_dir_acl ($username, $dirname ."/". $filename, $aclh);
if ($DEBUG)
{
$tmp_perm_holder ||= "";
print STDERR "DEBUG: Check entry ${dirname}/${filename}, perm " .
"$tmp_perm_holder\n";
}
if (defined ($tmp_perm_holder) && $tmp_perm_holder ne "")
{
$current_permission = $tmp_perm_holder;
if (defined ($tmp_node_holder))
{
$denial_node = $tmp_node_holder;
}
}
if ($current_permission eq 'deny' &&
defined ($denial_node->{"denial message"}) &&
$denial_node->{"denial message"} ne "")
{
$denial_message = $denial_node->{"denial message"};
}
if (defined ($current_permission) && $current_permission ne "")
{
if ($current_permission eq "allow")
{
return ($current_permission, undef);
}
else
{
return ($current_permission, $denial_message);
}
}
else
{
return ('allow');
}
}
##
## check_single_dir_acl ()
##
## Examine a single directory's entry and determine if a checkin to this
## dir is allowed. This func knows nothing about permissions inherited
## from superior directories. It returns a tuple of (PERMISSION, DENIAL-NODE).
## PERMISSION is either 'allow' or 'deny' and DENIAL-NODE is a pointer to
## the directory/user node that caused the denial.
## Dereferencing $DENIAL-NODE->{"denial message"} may work.
##
## NOTE: Originally I had some clever reason for passing back a pointer
## to the denial node, but I can't see any point to it. The denial
## msg, if any, should be returned here instead of dinking around with
## a pointer.
sub check_single_dir_acl
{
my ($username, $dirname, $aclh) = @_;
my $permission = undef;
my $node = undef;
my ($have_global_permission_entry, $username_specified) = (1, 1);
if (!defined ($dirname) || $dirname eq "")
{
$dirname = "*ALL*";
}
if (!defined ($aclh->{$dirname}))
{
return (undef, undef);
}
if (!defined ($aclh->{$dirname}->{"*ALL*"}))
{
$have_global_permission_entry = 0;
}
if (!defined ($username) || $username eq "" || $username eq "*ALL*")
{
$username_specified = 0;
$username = "*ALL*";
}
if (!$have_global_permission_entry && !$username_specified)
{
return (undef, undef);
}
if (!defined ($aclh->{$dirname}->{"*ALL*"}->{"permission"}) &&
!defined ($aclh->{$dirname}->{$username}->{"permission"}))
{
return (undef, undef);
}
if (defined ($aclh->{$dirname}->{"*ALL*"}->{"permission"}))
{
$node = $aclh->{$dirname}->{"*ALL*"};
$permission = $node->{"permission"};
}
if ($username_specified &&
defined ($aclh->{$dirname}->{$username}) &&
defined ($aclh->{$dirname}->{$username}->{"permission"}))
{
$node = $aclh->{$dirname}->{$username};
$permission = $node->{"permission"};
}
if ($DEBUG)
{
print STDERR "DEBUG: user $username dirname $dirname " .
"permission $permission\n";
}
if (!defined ($permission) || $permission eq 'allow')
{
return ($permission, undef);
}
else
{
return ($permission, $node);
}
}
##
## canonical_dirname ()
##
## Eliminate slashes at the beginning, end of a string. Eliminiate
## multiple slashes in the string. Eliminate "./" at the beginning of
## a string.
sub canonical_dirname
{
my ($string) = @_;
$string =~ s#^\/*##;
$string =~ s#^\.\/##;
$string =~ s#/*$##;
$string =~ s#/+#/#g;
return ($string);
}
##
## dump_acls
##
## Dumps out the ACL hash in a human-readable format.
##
sub dump_acls
{
my ($aclh) = @_;
foreach my $dirname (sort keys %$aclh)
{
my $allow_namelist = '';
my $deny_namelist = '';
foreach my $username (sort keys %{$aclh->{$dirname}})
{
if (!defined ($aclh->{$dirname}->{$username}->{'permission'}))
{
# This indicates that our hash is not being treated correctly.
# Someone is touching the hash when they shouldn't.
# Paper over it.
if ($DEBUG)
{
print STDERR "DEBUG: '$dirname' u '$username'\n";
}
next;
}
if ($aclh->{$dirname}->{$username}->{'permission'} eq "allow")
{
if ($allow_namelist ne '')
{
$allow_namelist = $allow_namelist . ", ";
}
$allow_namelist = $allow_namelist . $username;
}
else
{
if ($deny_namelist ne '')
{
$deny_namelist = $deny_namelist . ", ";
}
$deny_namelist = $deny_namelist . $username;
}
}
print "ACL_DUMP: $dirname:";
if ($allow_namelist ne '')
{
print " Allow $allow_namelist";
}
if ($deny_namelist ne '')
{
print " Deny $deny_namelist";
}
print "\n";
}
}
main ();
next prev parent reply other threads:[~2000-12-30 6:08 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2000-12-30 6:08 Jason Molenda
2000-10-14 0:28 ` Jason Molenda
2000-12-30 6:08 ` Chris Faylor
2000-10-16 16:45 ` Chris Faylor
2000-12-30 6:08 ` Jason Molenda [this message]
2000-10-16 23:20 ` Jason Molenda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20001016231918.A17732@shell17.ba.best.com \
--to=jason@molenda.com \
--cc=overseers@sources.redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).