Marc Poinot wrote:
> Commit info is not really useful for control ! I would expect it
> was the one to use ! Did I missed something ?
Attached is a script that I wrote on my own time that we (occasionally)
use to protect the trunk/mainline from unauthorized access.
If you have a valid perl installation, unpack this file, chmod +x it,
and type "perldoc cvs_mainline_protector" to read its documentation.
Hacking it to do what you like is probably pretty easy.
Cheers,
Laird
#!/usr/local/bin/perl -w
# Emacs-mode: -*-Perl-*-
# $Header: /cvsroots/cvssupport/cvssupport/head/src/bin/cvs_mainline_protector,v 1.1
1999/12/02 15:58:25 lnelson Exp $
# Time-stamp: <08/13/1999 15:15:50>
# See end of file for documentation and revision information.
package cvs_mainline_protector;
require 5.004;
use strict;
use English;
use File::Basename qw(basename);
use Cwd qw(fastcwd);
use Sys::Hostname;
use Net::SMTP;
use Getopt::Long;
###############################################################################
# For bootstrapping issues, just set the variable to 1 to bypass this program.
# You should not have to change anything else in this program.
my $shutup = 0;
###############################################################################
if ($shutup) {
exit(0);
}
# Get our program name, minus all the leading path information.
my $progname = &basename($0);
# Get our current location (somewhere like /tmp/cvsserver_23897).
my $cwd = &fastcwd();
# What host are we running on?
my $hostname = &hostname();
# Get our username from a number of places. Note that $ENV{'CVS_USER'} is
# only present with a patched version of cvs.
my $user = $ENV{'CVS_USER'} || $ENV{'USER'} || $ENV{'LOGNAME'} || 'unknown';
# Users are permitted to modify the mainline, all else being equal (which it
# may not be; see below).
my $permitted = 1;
# Get our command line options.
my %opts = ();
GetOptions(\%opts,
"email|e|t=s",
"fromaddress|f=s",
"smtphost|s=s",
"replyto|r=s");
# Get the SMTP host via which we will send email messages.
my $smtphost = $opts{"smtphost"} || $hostname;
# Pull our repository off the argument list. It will be the first one,
# according to CVS commitinfo documentation.
my $rep = shift(@ARGV);
die "*** $progname: path to cvs repository not passed as first argument!\n"
unless $rep;
# Check to see if $ENV{'CVSROOT'}/CVSROOT/mainline exists. If it does, then
# by default users are NOT permitted to modify the mainline unless they're listed
# in that file. Watch for bootstrapping issues!
if (open(ACL, "$ENV{'CVSROOT'}/CVSROOT/mainline")) {
$permitted = 0;
while (<ACL>) {
next if /^\s*$/os; # skip whitespace lines
next if /^\s*\#/os; # skip comments; convenient!
if (/^\s*$user\s*$/os) {
$permitted++;
last;
}
}
close(ACL);
}
# See what files are being committed at the moment and figure out what their
# old revision numbers are. We do this by looking in $cwd/CVS/Entries, which
# should be parseable. The format of this file is defined by CVS. This works
# for cvs v1.10 and later at least and is untested for earlier versions.
open(FILE, "$cwd/CVS/Entries") or die "*** Cannot open $cwd/CVS/Entries: $!\n";
my %entries;
while (<FILE>) {
next if /^D/os; # skip lines starting with D--these are directories, right?
next if /^\s*$/os; # skip whitespace
if (m!^\s*/(.*?)/(.*?)/!os) { # $1 is filename; $2 is revision
$entries{$1} = $2;
}
}
close(FILE);
# Now see if, for each file being committed, whether the "old" revision of
# that file was on the mainline. If it was, then the new one will be too, so
# then we check permissions.
my $exit_status = 0;
for (@ARGV) { # @ARGV == filenames being committed
if ($entries{$_}) { # an entry of "0" means item is being added, so skip it
# If the file in question's previous revision looks like a mainline
# one (e.g. x.x ONLY), then check in only if permitted.
# This section could be hacked pretty easily to read in the regex
# from somewhere else--then you could protect any branch you like.
if ($entries{$_} =~ /^\d+\.\d+$/os) {
unless ($permitted) {
my $relative_path = "$rep/$_";
$relative_path =~ s/^$ENV{'CVSROOT'}\/+//gos;
print STDOUT "*** $progname: You are not authorized to " .
"commit changes to $relative_path $entries{$_} to the " .
"main branch.\n";
$exit_status = 1;
if ($opts{"email"}) {
# send email to CM guy
my $cvsspec =
":pserver:$user\@$hostname:$ENV{'CVSROOT'}";
&send_email(<<START_OF_BODY, <<END_OF_MESSAGE);
$user tried to modify main branch in $cvsspec
START_OF_BODY
The user named "$user" attempted to modify the main branch for the
file named "$relative_path", but was denied because his or her name did not
appear in the file named "$ENV{'CVSROOT'}/CVSROOT/mainline" in the CVS
repository identified by "$cvsspec".
Details:
CVS repository: $cvsspec
File: $relative_path
Pre-commit file version: $entries{$_}
User: $user
END_OF_MESSAGE
}
}
}
}
}
exit($exit_status);
###############################################################################
# Subroutines
###############################################################################
sub send_email ($@) {
return unless $opts{"email"};
my $subject = shift || '(no subject)';
my $from = $opts{"fromaddress"} || scalar(getpwuid($UID)) . "\@$hostname";
eval {
my $smtp = new Net::SMTP($smtphost);
$smtp->mail($from);
$smtp->to($opts{"email"});
$smtp->data();
$smtp->datasend("To: $opts{'email'}\n");
if ($opts{'replyto'}) {
$smtp->datasend("Reply-to: $opts{'replyto'}\n");
}
$smtp->datasend("Subject: $subject\n\n");
$smtp->datasend(@_);
$smtp->dataend();
$smtp->quit();
};
if ($@) {
warn "*** $@";
}
}
1;
__END__
# Pod follows.
=head1 NAME
cvs_mainline_protector - protect a cvs repository mainline from modification
=head1 SYNOPSIS
# Line in commitinfo file:
ALL /path/to/cvs_mainline_protector [EMAIL PROTECTED]
[EMAIL PROTECTED] [EMAIL PROTECTED]
--smtphost=mail.yourorg.com
=head1 DESCRIPTION
B<cvs_mainline_protector> is a program designed to be run as a cvs(1)
F<commitinfo> script. It protects the main branch of any project from
unauthorized modification.
B<cvs_mainline_protector> consults an access list named F<mainline> in
the F<CVSROOT> directory of the project. If the user who is trying to
commit a file is not listed in this file, then he or she will not be
allowed to commit his or her changes. B<Note:> On initial
installation, F<mainline> must contain at a minimum the username of
the person who will modify the F<mainline> file itself!
You'll want to edit F<CVSROOT/checkoutlist> to include F<mainline> so
that it is automatically checked out into the F<CVSROOT> after it is
modified.
=head1 OPTIONS
B<cvs_mainline_protector> takes two options.
=over 4
=item B<--email=ADDRESS>
Optional. If this option is specified, then email will be sent to
B<ADDRESS> informing the recipient of any unauthorized attempts to
modify the mainline of any module in the cvs repository. If not
specified, then no email will be sent.
=item B<--smtphost=HOSTNAME>
Optional. The name of the SMTP host via which mail will be sent. If
this option is not specified, then the current hostname will be used.
=item B<--replyto=ADDRESS>
Optional. Valid only if B<--email=ADDRESS> is specified. If
specified, this option should indicate what email address should be
used for the C<Reply-To:> header in any email messages sent out by
B<cvs_mainline_protector>.
=item B<--fromaddress=ADDRESS>
Optional. Useful if B<--email=ADDRESS> is specified. Specifies the
user actually sending email. Your sendmail configuration will
determine what this string should be; generally it is
C<[EMAIL PROTECTED]>. If omitted, then the current username and the
current hostname are used, which are not always what you want.
=back
=head1 OPERANDS
cvs(1) documentation indicates that F<commitinfo> scripts are invoked
with the path to the cvs(1) repository as their first operand, and the
names of any files being committed as subsequent operands.
=head1 EXAMPLES
Your F<commitinfo> should contain a line like this:
# Line in commitinfo file:
ALL /path/to/cvs_mainline_protector [EMAIL PROTECTED]
[EMAIL PROTECTED]
It's best to place the email address of your configuration management
guru as the argument to the B<--email> option (see L<"OPTIONS">) if
you wish email to be sent. Of course, I<he> or I<she> may not want
email to be sent, but then he or she is probably setting this program
up in the first place. C<:-)>
=head1 REQUIRED PERL MODULES
strict
English
File::Basename
Cwd
Sys::Hostname
Net::SMTP
Getopt::Long
=head1 ENVIRONMENT
B<cvs_mainline_protector> relies on several environment variables
being set by cvs(1) as documented in its reference manual
(http://www.loria.fr/~molli/cvs/doc/cvs_19.html#SEC174).
=over 4
=item I<CVSROOT>
cvs(1) documents that F<commitinfo> scripts are invoked with
I<CVSROOT> set to the full path to the repository.
B<cvs_mainline_protector> uses this to look up the F<mainline> access
control list (see L<"FILES">).
=item I<CVS_USER>
Some patched versions of cvs(1) set this environment variable. It is
preferred over the more commonly set I<USER> variable, because it
works in client/server setups. B<cvs_mainline_protector> will use
this variable if found; otherwise it will fall back to using I<USER>
and then I<LOGNAME>.
=item I<USER>
B<cvs_mainline_protector> will consult this variable only if
I<CVS_USER> is not set.
=item I<LOGNAME>
B<cvs_mainline_protector> will consult this variable only if
I<CVS_USER> is not set.
=back
=head1 FILES
=over 4
=item F</path/to/cvsroot/CVSROOT/mainline>
F<path/to/cvsroot/CVSROOT/mainline> is a file containing one cvs
username per line. If this file exists at all, empty or not, then
B<cvs_mainline_protector> makes the assumption that the main branch is
sacred, and without any other information will not allow modification
to it. If this file exists and contains one cvs(1) username per line,
then only those usernames will be able to modify the main branch. A
sample excerpt of this file follows, showing that C<jbloggs> and
C<ltorvalds> are the only usernames permitted to modify the main
branch of any project in this repository:
jbloggs
ltorvalds
You'll want to edit F<CVSROOT/checkoutlist> to include F<mainline> so that it is
automatically checked out into the F<CVSROOT> after it is modified.
=back
=head1 DIAGNOSTICS
B<cvs_mainline_protector> outputs any messages or errors prefixed with
three asterisks and a space ("*** ").
=head1 EXIT STATUS
=over 4
=item 0
B<cvs_mainline_protector> exited normally.
=item >0
B<cvs_mainline_protector> encountered an error of some kind. Any
commit in process was not executed.
=back
=head1 AUTHOR
Laird Nelson <[EMAIL PROTECTED]>
=head1 BUGS
To report a bug, please send email to the author.
=head1 NOTES
Note that upon installation of B<cvs_mainline_protector> as a
F<commitinfo> script, the F</path/to/cvsroot/CVSROOT/mainline> file
(see L<"FILES">) must contain at least one username if it is to be
subsequently modified! If this does not happen, then no one will be
able to modify any main branches in the repository until this program
is disabled.
=head1 SEE ALSO
L<perl(1)>, L<cvs(1)>, http://www.loria.fr/~molli/cvs/doc/cvs_18.html#SEC162
=head1 HISTORY
$Log: cvs_mainline_protector,v $
Revision 1.1 1999/12/02 15:58:25 lnelson
Initial revisions in this repository; moved from private repository.
Revision 1.3 1999/08/13 19:18:09 lnelson
Improved mail handling somewhat.
Revision 1.2 1999/08/13 17:56:46 lnelson
Documentation update.
Revision 1.1 1999/08/13 17:52:53 lnelson
Initial revision.
=head1 COPYRIGHT
Copyright 1999 Laird Nelson <[EMAIL PROTECTED]>. All rights
reserved. This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.
=cut