2009-08-25 05:14:46 +02:00
|
|
|
#!/usr/bin/perl
|
2009-08-23 07:35:14 +02:00
|
|
|
|
|
|
|
use strict;
|
2009-08-25 05:14:46 +02:00
|
|
|
use warnings;
|
2009-08-23 10:16:45 +02:00
|
|
|
use Data::Dumper;
|
2009-09-18 14:30:14 +02:00
|
|
|
$Data::Dumper::Indent = 1;
|
2009-11-23 13:34:18 +01:00
|
|
|
$Data::Dumper::Sortkeys = 1;
|
2009-08-23 07:35:14 +02:00
|
|
|
|
|
|
|
# === add-auth-keys ===
|
|
|
|
|
2009-08-26 02:47:27 +02:00
|
|
|
# part of the gitolite (GL) suite
|
2009-08-23 07:35:14 +02:00
|
|
|
|
2009-08-23 10:16:45 +02:00
|
|
|
# (1) - "compiles" ~/.ssh/authorized_keys from the list of pub-keys
|
|
|
|
# (2) - also "compiles" the user-friendly GL conf file into something easier
|
|
|
|
# to parse. We're doing this because both the gl-auth-command and the
|
|
|
|
# (gl-)update hook need this, and it seems easier to do this than
|
|
|
|
# replicate the parsing code in both those places. As a bonus, it's
|
|
|
|
# probably more efficient.
|
2009-09-25 08:47:33 +02:00
|
|
|
# (3) - finally does what I have resisted doing all along -- handle gitweb and
|
|
|
|
# git-daemon access. It won't *setup* gitweb/daemon for you -- you have
|
|
|
|
# to that yourself. What this does is make sure that "repo.git"
|
|
|
|
# contains the file "git-daemon-export-ok" (for daemon case) and the
|
|
|
|
# line "repo.git" exists in the "projects.list" file (for gitweb case).
|
2009-08-23 10:16:45 +02:00
|
|
|
|
2009-08-23 07:35:14 +02:00
|
|
|
# how run: manual, by GL admin
|
2009-08-23 10:16:45 +02:00
|
|
|
# when:
|
2009-08-23 11:25:50 +02:00
|
|
|
# - anytime a pubkey is added/deleted
|
2009-08-26 02:47:27 +02:00
|
|
|
# - anytime gitolite.conf is changed
|
2009-08-23 10:16:45 +02:00
|
|
|
# input:
|
2009-12-04 16:04:40 +01:00
|
|
|
# - GL_CONF (default: ~/.gitolite/conf/gitolite.conf)
|
2009-08-26 02:47:27 +02:00
|
|
|
# - GL_KEYDIR (default: ~/.gitolite/keydir)
|
2009-08-23 10:16:45 +02:00
|
|
|
# output:
|
2009-08-23 11:25:50 +02:00
|
|
|
# - ~/.ssh/authorized_keys (dictated by sshd)
|
2009-12-04 16:04:40 +01:00
|
|
|
# - GL_CONF_COMPILED (default: ~/.gitolite/conf/gitolite.conf-compiled.pm)
|
2009-08-23 07:35:14 +02:00
|
|
|
# security:
|
|
|
|
# - touches a very critical system file that manages the restrictions on
|
|
|
|
# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see
|
|
|
|
# below) on any change to this script
|
|
|
|
# - no security checks within program. The GL admin runs this manually
|
|
|
|
|
2009-08-23 10:16:45 +02:00
|
|
|
# warnings:
|
2009-08-23 07:35:14 +02:00
|
|
|
# - if the "start" line exists, but the "end" line does not, you lose the
|
|
|
|
# rest of the existing authkey file. In general, "don't do that (TM)",
|
|
|
|
# but we do have a "vim -d" popping up so you can see the changes being
|
|
|
|
# made, just in case...
|
|
|
|
|
2009-08-23 11:25:50 +02:00
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# common definitions
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
2009-10-25 13:03:06 +01:00
|
|
|
# setup quiet mode if asked; please do not use this when running manually
|
|
|
|
open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q');
|
|
|
|
|
2009-12-04 05:21:22 +01:00
|
|
|
# these are set by the "rc" file
|
2010-04-09 17:22:32 +02:00
|
|
|
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS);
|
2009-12-04 05:21:22 +01:00
|
|
|
# and these are set by gitolite.pm
|
2009-12-05 14:08:46 +01:00
|
|
|
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN);
|
2009-09-15 17:32:23 +02:00
|
|
|
|
2009-10-25 03:59:52 +01:00
|
|
|
# the common setup module is in the same directory as this running program is
|
|
|
|
my $bindir = $0;
|
|
|
|
$bindir =~ s/\/[^\/]+$//;
|
2010-02-09 12:30:01 +01:00
|
|
|
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
|
2009-10-25 03:59:52 +01:00
|
|
|
require "$bindir/gitolite.pm";
|
|
|
|
|
|
|
|
# ask where the rc file is, get it, and "do" it
|
|
|
|
&where_is_rc();
|
2009-11-03 15:54:53 +01:00
|
|
|
die "$ABRT parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
|
2009-08-23 11:25:50 +02:00
|
|
|
|
2009-10-13 06:32:45 +02:00
|
|
|
# add a custom path for git binaries, if specified
|
|
|
|
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
|
|
|
|
|
2009-08-23 11:25:50 +02:00
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# definitions specific to this program
|
|
|
|
# ----------------------------------------------------------------------------
|
2009-08-23 07:35:14 +02:00
|
|
|
|
|
|
|
# command and options for authorized_keys
|
2009-12-15 08:05:48 +01:00
|
|
|
$AUTH_COMMAND="$bindir/gl-auth-command";
|
2009-12-19 17:06:55 +01:00
|
|
|
$AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding";
|
|
|
|
# note, for most users there's also a "no-pty" added to this, see later
|
2009-08-23 07:35:14 +02:00
|
|
|
|
2009-10-05 16:51:33 +02:00
|
|
|
# groups can now represent user groups or repo groups.
|
|
|
|
|
|
|
|
# $groups{group}{member} = "master" (or name of fragment file in which the
|
|
|
|
# group is defined).
|
2009-11-23 18:15:00 +01:00
|
|
|
our %groups = ();
|
2009-10-05 16:51:33 +02:00
|
|
|
|
|
|
|
# %repos has two functions.
|
|
|
|
|
|
|
|
# $repos{repo}{R|W}{user} = 1 if user has R (or W) permissions for at least
|
2009-12-05 14:08:46 +01:00
|
|
|
# one branch in repo. This is used by the "level 1 check" (see faq). There's
|
|
|
|
# also the new "C" (create a repo) permission now
|
2009-10-05 16:51:33 +02:00
|
|
|
|
|
|
|
# $repos{repo}{user} is a list of {ref, perms} pairs. This is used by the
|
|
|
|
# level 2 check. In order to allow "exclude" rules, the order of rules now
|
|
|
|
# matters, so what used to be entirely "hash of hash of hash" now has a list
|
|
|
|
# in between :)
|
2009-08-25 05:38:11 +02:00
|
|
|
my %repos = ();
|
2009-10-05 16:51:33 +02:00
|
|
|
|
|
|
|
# <sigh>... having been forced to use a list as described above, we lose some
|
|
|
|
# efficiency due to the possibility of the same {ref, perms} pair showing up
|
|
|
|
# multiple times for the same repo+user. So...
|
|
|
|
my %rurp_seen = ();
|
|
|
|
|
|
|
|
# catch usernames<->pubkeys mismatches; search for "lint" below
|
|
|
|
my %user_list = ();
|
2009-08-23 10:16:45 +02:00
|
|
|
|
2009-12-07 21:20:29 +01:00
|
|
|
# repo configurations
|
|
|
|
my %repo_config = ();
|
|
|
|
|
2009-11-27 08:53:48 +01:00
|
|
|
# gitweb descriptions and owners; plain text, keyed by "$repo.git"
|
2009-11-12 10:19:39 +01:00
|
|
|
my %desc = ();
|
2009-11-27 08:53:48 +01:00
|
|
|
my %owner = ();
|
2009-11-12 10:19:39 +01:00
|
|
|
|
2009-09-21 11:11:37 +02:00
|
|
|
# set the umask before creating any files
|
|
|
|
umask($REPO_UMASK);
|
2009-08-25 03:36:36 +02:00
|
|
|
|
2009-08-23 11:25:50 +02:00
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# subroutines
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
2009-09-15 17:37:00 +02:00
|
|
|
sub expand_list
|
2009-08-23 10:16:45 +02:00
|
|
|
{
|
|
|
|
my @list = @_;
|
|
|
|
my @new_list = ();
|
|
|
|
|
|
|
|
for my $item (@list)
|
|
|
|
{
|
|
|
|
if ($item =~ /^@/) # nested group
|
|
|
|
{
|
2009-11-03 15:54:53 +01:00
|
|
|
die "$ABRT undefined group $item\n" unless $groups{$item};
|
2009-08-23 10:16:45 +02:00
|
|
|
# add those names to the list
|
2009-10-02 18:24:23 +02:00
|
|
|
push @new_list, sort keys %{ $groups{$item} };
|
2009-08-23 10:16:45 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
push @new_list, $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return @new_list;
|
|
|
|
}
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# "compile" GL conf
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
sub parse_conf_file
|
|
|
|
{
|
2009-10-04 06:26:40 +02:00
|
|
|
my ($conffile, $fragment) = @_;
|
|
|
|
# the second arg, $fragment, is passed in as "master" when parsing the
|
|
|
|
# main config, and the fragment name when parsing a fragment. In the
|
|
|
|
# latter case, the parser uses that information to ignore (and warn about)
|
|
|
|
# any repos in the fragment that are not members of the "repo group" of
|
|
|
|
# the same name.
|
|
|
|
my %ignored = ();
|
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
my $conf_fh = wrap_open( "<", $conffile );
|
2009-08-23 10:16:45 +02:00
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
# the syntax is fairly simple, so we parse it inline
|
2009-08-23 10:16:45 +02:00
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
my @repos;
|
|
|
|
while (<$conf_fh>)
|
2009-08-23 10:16:45 +02:00
|
|
|
{
|
2010-01-23 10:27:22 +01:00
|
|
|
# kill comments, but take care of "#" inside *simple* strings
|
|
|
|
s/^((".*?"|[^#"])*)#.*/$1/;
|
2009-10-02 18:47:51 +02:00
|
|
|
# normalise whitespace; keeps later regexes very simple
|
|
|
|
s/=/ = /;
|
|
|
|
s/\s+/ /g;
|
|
|
|
s/^ //;
|
|
|
|
s/ $//;
|
|
|
|
# and blank lines
|
|
|
|
next unless /\S/;
|
|
|
|
|
|
|
|
# user or repo groups
|
|
|
|
if (/^(@\S+) = (.*)/)
|
|
|
|
{
|
2009-10-04 06:26:40 +02:00
|
|
|
# store the members of each group as hash key. Keep track of when
|
|
|
|
# the group was *first* created by using $fragment as the *value*
|
|
|
|
do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) );
|
2009-11-03 15:54:53 +01:00
|
|
|
die "$ABRT bad group $1\n" unless $1 =~ $REPONAME_PATT;
|
2009-10-02 18:47:51 +02:00
|
|
|
}
|
|
|
|
# repo(s)
|
|
|
|
elsif (/^repo (.*)/)
|
|
|
|
{
|
|
|
|
# grab the list and expand any @stuff in it
|
|
|
|
@repos = split ' ', $1;
|
2010-03-23 17:50:34 +01:00
|
|
|
unless (@repos == 1 and $repos[0] eq '@all') {
|
2009-12-21 00:55:45 +01:00
|
|
|
@repos = expand_list ( @repos );
|
2010-02-18 01:40:08 +01:00
|
|
|
do { die "$ABRT bad reponame $_\n" unless ($GL_WILDREPOS ? $_ =~ $REPOPATT_PATT : $_ =~ $REPONAME_PATT) } for @repos;
|
2009-12-21 00:55:45 +01:00
|
|
|
}
|
2009-12-21 13:03:53 +01:00
|
|
|
s/\bCREAT[EO]R\b/\$creater/g for @repos;
|
2009-10-02 18:47:51 +02:00
|
|
|
}
|
|
|
|
# actual permission line
|
2010-03-30 16:31:49 +02:00
|
|
|
elsif (/^(-|C|D|R|RW|RW\+) (.* )?= (.+)/)
|
2009-08-23 10:16:45 +02:00
|
|
|
{
|
2009-10-02 18:47:51 +02:00
|
|
|
my $perms = $1;
|
|
|
|
my @refs; @refs = split(' ', $2) if $2;
|
2010-02-13 15:30:45 +01:00
|
|
|
@refs = expand_list ( @refs );
|
2009-10-02 18:47:51 +02:00
|
|
|
my @users = split ' ', $3;
|
2010-02-05 11:30:47 +01:00
|
|
|
die "wildrepos disabled, cant use 'C' in config\n" if $perms eq 'C' and not $GL_WILDREPOS;
|
2009-10-02 18:47:51 +02:00
|
|
|
|
|
|
|
# if no ref is given, this PERM applies to all refs
|
|
|
|
@refs = qw(refs/.*) unless @refs;
|
2010-01-09 15:27:44 +01:00
|
|
|
# deprecation warning
|
|
|
|
map { warn "WARNING: old syntax 'PATH/' found; please use new syntax 'NAME/'\n" if s(^PATH/)(NAME/) } @refs;
|
2010-01-07 13:29:34 +01:00
|
|
|
# fully qualify refs that dont start with "refs/" or "NAME/";
|
2009-11-16 15:35:08 +01:00
|
|
|
# prefix them with "refs/heads/"
|
2010-01-07 13:29:34 +01:00
|
|
|
@refs = map { m(^(refs|NAME)/) or s(^)(refs/heads/); $_ } @refs;
|
2010-03-16 02:56:33 +01:00
|
|
|
@refs = map { s(/USER/)(/\$gl_user/); $_ } @refs;
|
2009-10-02 18:47:51 +02:00
|
|
|
|
|
|
|
# expand the user list, unless it is just "@all"
|
|
|
|
@users = expand_list ( @users )
|
|
|
|
unless (@users == 1 and $users[0] eq '@all');
|
2009-11-03 15:54:53 +01:00
|
|
|
do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users;
|
2009-10-02 18:47:51 +02:00
|
|
|
|
compile: remove the sortsub for data dumper
Data dumper was failing (returning an empty string!) on an input config
file of about 350 lines or so (output 2400 lines or so).
Removing the sort sub fixed the problem.
To recap why that sub was put in (see deleted lines in this commit for
details), what we really want is that $creater must appear *last* in the
resulting dump.
So we trick it. "man ascii" tells you that ~ is the highest valued
ASCII character (yes, I know, not utf-8 safe etc... I'll deal with that
if and when needed or punt!). So we just put that in front of $creater
and remove it later...
You *don't* want to do this for $readers and $writers -- then they will
once again sort *after* $creater, which would be a bad thing. Also,
it's probably better this way, because now the order of the hash keys
will be: $readers, $writers, any actual users listed, and then $creater.
This means the effective access rights will be:
1. if you are the creater you get CREATER's rights
2. else if your userid is listed *explicitly* in the config, you get
those rights
3. else if you've been setperm'd as a writer, you get WRITERS rights
4. else if you've been setperm'd as a reader, you get READERS rights
This is different from what used to happen till now; READERS and WRITERS
used to trump explicitly given rights. I'd been meaning to fix that
somehow, but never got around to it, until this DDD (damn Data Dumper!)
forced my hand :)
2010-03-17 13:15:49 +01:00
|
|
|
s/\bCREAT[EO]R\b/~\$creater/g for @users;
|
2009-12-05 14:08:46 +01:00
|
|
|
s/\bREADERS\b/\$readers/g for @users;
|
|
|
|
s/\bWRITERS\b/\$writers/g for @users;
|
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
# ok, we can finally populate the %repos hash
|
|
|
|
for my $repo (@repos) # each repo in the current stanza
|
2009-08-23 10:16:45 +02:00
|
|
|
{
|
2009-10-04 06:26:40 +02:00
|
|
|
# if we're processing a delegated config file (not the master
|
2009-12-05 14:08:46 +01:00
|
|
|
# config), we need to prevent attempts by that admin to obtain
|
|
|
|
# rights on stuff outside his domain
|
|
|
|
|
|
|
|
# trying to set access for $repo (='foo')...
|
|
|
|
if (
|
|
|
|
# processing the master config, not a fragment
|
|
|
|
( $fragment eq 'master' ) or
|
|
|
|
# fragment is also called 'foo' (you're allowed to have a
|
|
|
|
# fragment that is only concerned with one repo)
|
|
|
|
( $fragment eq $repo ) or
|
|
|
|
# fragment is called "bar" and "@bar = foo" has been
|
|
|
|
# defined in the master config
|
|
|
|
( ($groups{"\@$fragment"}{$repo} || '') eq 'master' )
|
|
|
|
) {
|
|
|
|
# all these are fine
|
|
|
|
} else {
|
|
|
|
# this is a little more complex
|
|
|
|
|
|
|
|
# fragment is called "bar", one or more "@bar = regex"
|
|
|
|
# have been specified in master, and "foo" matches some
|
|
|
|
# such "regex"
|
|
|
|
my @matched = grep { $repo =~ /^$_$/ }
|
|
|
|
grep { $groups{"\@$fragment"}{$_} eq 'master' }
|
|
|
|
sort keys %{ $groups{"\@$fragment"} };
|
|
|
|
if (@matched < 1) {
|
2009-10-04 06:26:40 +02:00
|
|
|
$ignored{$fragment}{$repo} = 1;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
}
|
2009-10-02 18:47:51 +02:00
|
|
|
for my $user (@users)
|
|
|
|
{
|
|
|
|
$user_list{$user}++; # only to catch lint, see later
|
2009-09-27 04:32:36 +02:00
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
# for 1st level check (see faq/tips doc)
|
2009-12-05 14:08:46 +01:00
|
|
|
$repos{$repo}{C}{$user} = 1, next if $perms eq 'C';
|
2009-10-02 18:47:51 +02:00
|
|
|
$repos{$repo}{R}{$user} = 1 if $perms =~ /R/;
|
2010-03-30 16:31:49 +02:00
|
|
|
$repos{$repo}{W}{$user} = 1 if $perms =~ /W|D/;
|
|
|
|
|
|
|
|
# if the user specified even a single 'D' anywhere, make
|
|
|
|
# that fact easy to find; this changes the meaning of RW+
|
|
|
|
# to no longer permit deletes (see update hook)
|
|
|
|
$repos{$repo}{DELETE_IS_D} = 1 if $perms eq 'D';
|
2009-09-18 14:30:14 +02:00
|
|
|
|
2009-10-02 18:47:51 +02:00
|
|
|
# for 2nd level check, store each "ref, perms" pair in order
|
|
|
|
for my $ref (@refs)
|
|
|
|
{
|
2010-01-07 13:29:34 +01:00
|
|
|
# checking NAME based restrictions is expensive for
|
2009-11-16 15:35:08 +01:00
|
|
|
# the update hook (see the changes to src/hooks/update
|
|
|
|
# in this commit for why) so we would *very* much like
|
|
|
|
# to avoid doing it for the large majority of repos
|
2010-01-07 13:29:34 +01:00
|
|
|
# that do *not* use NAME limits. Setting a flag that
|
2009-11-16 15:35:08 +01:00
|
|
|
# can be checked right away will help us do that
|
2010-01-07 13:29:34 +01:00
|
|
|
$repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//;
|
2009-10-05 16:51:33 +02:00
|
|
|
push @{ $repos{$repo}{$user} }, { $ref => $perms }
|
|
|
|
unless $rurp_seen{$repo}{$user}{$ref}{$perms}++;
|
2009-10-02 18:47:51 +02:00
|
|
|
}
|
2009-08-23 10:16:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-12-07 21:20:29 +01:00
|
|
|
# configuration
|
|
|
|
elsif (/^config (.+) = ?(.*)/)
|
|
|
|
{
|
|
|
|
my ($key, $value) = ($1, $2);
|
2010-02-07 08:39:16 +01:00
|
|
|
my @validkeys = split (' ', ($GL_GITCONFIG_KEYS || ''));
|
|
|
|
my @matched = grep { $key =~ /^$_$/ } @validkeys;
|
|
|
|
die "$ABRT git config $key not allowed\n" if (@matched < 1);
|
2009-12-07 21:20:29 +01:00
|
|
|
for my $repo (@repos) # each repo in the current stanza
|
|
|
|
{
|
|
|
|
$repo_config{$repo}{$key} = $value;
|
|
|
|
}
|
|
|
|
}
|
2010-01-05 19:34:40 +01:00
|
|
|
# include
|
|
|
|
elsif (/^include "(.+)"/)
|
|
|
|
{
|
|
|
|
my $file = $1;
|
|
|
|
$file = "$GL_ADMINDIR/conf/$file" unless $file =~ /^\//;
|
|
|
|
die "$WARN $fragment attempting to include configuration\n" if $fragment ne 'master';
|
|
|
|
die "$ABRT included file not found: '$file'\n" unless -f $file;
|
|
|
|
|
|
|
|
parse_conf_file($file, $fragment);
|
|
|
|
}
|
2009-11-27 08:53:48 +01:00
|
|
|
# very simple syntax for the gitweb description of repo; one of:
|
|
|
|
# reponame = "some description string"
|
|
|
|
# reponame "owner name" = "some description string"
|
|
|
|
elsif (/^(\S+)(?: "(.*?)")? = "(.*)"$/)
|
2009-11-12 10:19:39 +01:00
|
|
|
{
|
2009-11-27 08:53:48 +01:00
|
|
|
my ($repo, $owner, $desc) = ($1, $2, $3);
|
2009-11-12 10:19:39 +01:00
|
|
|
die "$ABRT bad repo name $repo\n" unless $repo =~ $REPONAME_PATT;
|
|
|
|
die "$WARN $fragment attempting to set description for $repo\n" if
|
|
|
|
$fragment ne 'master' and $fragment ne $repo and ($groups{"\@$fragment"}{$repo} || '') ne 'master';
|
2009-11-27 08:53:48 +01:00
|
|
|
$desc{"$repo.git"} = $desc;
|
|
|
|
$owner{"$repo.git"} = $owner || '';
|
2009-11-12 10:19:39 +01:00
|
|
|
}
|
2009-10-02 18:47:51 +02:00
|
|
|
else
|
|
|
|
{
|
2009-11-03 15:54:53 +01:00
|
|
|
die "$ABRT can't make head or tail of '$_'\n";
|
2009-10-02 18:47:51 +02:00
|
|
|
}
|
2009-08-24 10:00:58 +02:00
|
|
|
}
|
2009-10-04 06:26:40 +02:00
|
|
|
for my $ig (sort keys %ignored)
|
|
|
|
{
|
|
|
|
warn "\n\t\t***** WARNING *****\n" .
|
|
|
|
"\t$ig.conf attempting to set access for " .
|
|
|
|
join (", ", sort keys %{ $ignored{$ig} }) . "\n";
|
|
|
|
}
|
2009-08-23 10:16:45 +02:00
|
|
|
}
|
|
|
|
|
2009-10-04 06:26:40 +02:00
|
|
|
# parse the main config file
|
|
|
|
parse_conf_file($GL_CONF, 'master');
|
|
|
|
|
|
|
|
# parse any delegated fragments
|
|
|
|
wrap_chdir($GL_ADMINDIR);
|
|
|
|
for my $fragment_file (glob("conf/fragments/*.conf"))
|
|
|
|
{
|
2009-11-23 18:15:00 +01:00
|
|
|
# we already check (elsewhere) that a fragment called "foo" will not try
|
|
|
|
# to specify access control for a repo whose name is not "foo" or is not
|
|
|
|
# part of a group called "foo" created by master
|
|
|
|
|
|
|
|
# meanwhile, I found a possible attack where the admin for group B creates
|
|
|
|
# a "convenience" group of (a subset of) his users, and then the admin for
|
|
|
|
# repo group A (alphabetically before B) adds himself to that same group
|
|
|
|
# in his own fragment.
|
|
|
|
|
|
|
|
# as a result, admin_A now has access to group B repos :(
|
|
|
|
|
|
|
|
# so now we lock the groups hash to the value it had after parsing
|
|
|
|
# "master", and localise any changes to it by this fragment so that they
|
|
|
|
# don't propagate to the next fragment. Thus, each fragment now has only
|
|
|
|
# those groups that are defined in "master" and itself
|
|
|
|
|
|
|
|
local %groups = %groups;
|
|
|
|
|
2009-10-04 06:26:40 +02:00
|
|
|
my $fragment = $fragment_file;
|
|
|
|
$fragment =~ s/^conf\/fragments\/(.*).conf$/$1/;
|
|
|
|
parse_conf_file($fragment_file, $fragment);
|
|
|
|
}
|
2009-10-02 18:47:51 +02:00
|
|
|
|
2009-08-31 04:28:08 +02:00
|
|
|
my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED );
|
2009-12-05 14:08:46 +01:00
|
|
|
my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
|
|
|
|
# the dump uses single quotes, but we convert any strings containing $creater,
|
|
|
|
# $readers, $writers, to double quoted strings. A wee bit sneaky, but not too
|
|
|
|
# much...
|
compile: remove the sortsub for data dumper
Data dumper was failing (returning an empty string!) on an input config
file of about 350 lines or so (output 2400 lines or so).
Removing the sort sub fixed the problem.
To recap why that sub was put in (see deleted lines in this commit for
details), what we really want is that $creater must appear *last* in the
resulting dump.
So we trick it. "man ascii" tells you that ~ is the highest valued
ASCII character (yes, I know, not utf-8 safe etc... I'll deal with that
if and when needed or punt!). So we just put that in front of $creater
and remove it later...
You *don't* want to do this for $readers and $writers -- then they will
once again sort *after* $creater, which would be a bad thing. Also,
it's probably better this way, because now the order of the hash keys
will be: $readers, $writers, any actual users listed, and then $creater.
This means the effective access rights will be:
1. if you are the creater you get CREATER's rights
2. else if your userid is listed *explicitly* in the config, you get
those rights
3. else if you've been setperm'd as a writer, you get WRITERS rights
4. else if you've been setperm'd as a reader, you get READERS rights
This is different from what used to happen till now; READERS and WRITERS
used to trump explicitly given rights. I'd been meaning to fix that
somehow, but never got around to it, until this DDD (damn Data Dumper!)
forced my hand :)
2010-03-17 13:15:49 +01:00
|
|
|
$dumped_data =~ s/'(?=[^']*\$(?:creater|readers|writers|gl_user))~?(.*?)'/"$1"/g;
|
2009-12-05 14:08:46 +01:00
|
|
|
print $compiled_fh $dumped_data;
|
2009-11-03 15:54:53 +01:00
|
|
|
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
|
2009-08-23 11:25:50 +02:00
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
2009-09-27 04:32:36 +02:00
|
|
|
# any new repos to be created?
|
2009-08-23 11:25:50 +02:00
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
# modern gits allow cloning from an empty repo, so we just create it. Gitosis
|
|
|
|
# did not have that luxury, so it was forced to detect the first push and
|
|
|
|
# create it then
|
|
|
|
|
2009-09-21 04:18:30 +02:00
|
|
|
# but it turns out not everyone has "modern" gits :)
|
|
|
|
my $git_version = `git --version`;
|
|
|
|
my ($gv_maj, $gv_min, $gv_patchrel) = ($git_version =~ m/git version (\d+)\.(\d+)\.(\d+)/);
|
2009-11-03 15:54:53 +01:00
|
|
|
die "$ABRT I can't understand $git_version\n" unless ($gv_maj >= 1);
|
2009-09-21 04:18:30 +02:00
|
|
|
$git_version = $gv_maj*10000 + $gv_min*100 + $gv_patchrel; # now it's "normalised"
|
|
|
|
|
2009-12-01 17:24:23 +01:00
|
|
|
# repo-base needs to be an absolute path for this loop to work right
|
|
|
|
# so if it was not already absolute, prefix $HOME.
|
|
|
|
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
|
|
|
|
|
|
|
|
wrap_chdir("$repo_base_abs");
|
|
|
|
|
|
|
|
for my $repo (sort keys %repos) {
|
2009-12-05 14:08:46 +01:00
|
|
|
next unless $repo =~ $REPONAME_PATT;
|
2010-01-31 15:54:36 +01:00
|
|
|
next if $repo =~ m(^EXTCMD/); # these are not real repos
|
2009-12-01 17:24:23 +01:00
|
|
|
unless (-d "$repo.git") {
|
2010-02-04 10:12:10 +01:00
|
|
|
print STDERR "creating $repo...\n";
|
2010-02-09 15:07:37 +01:00
|
|
|
new_repo($repo, "$GL_ADMINDIR/hooks/common");
|
2009-12-01 17:24:23 +01:00
|
|
|
# new_repo would have chdir'd us away; come back
|
|
|
|
wrap_chdir("$repo_base_abs");
|
|
|
|
}
|
2010-03-07 14:35:56 +01:00
|
|
|
|
|
|
|
# when repos are copied over from elsewhere, one had to run easy install
|
|
|
|
# once again to make the new (OS-copied) repo contain the proper update
|
|
|
|
# hook. Perhaps we can make this easier now, and eliminate the easy
|
|
|
|
# install, with a quick check (and a new, empty, "hook" as a sentinel)
|
|
|
|
unless (-l "$repo.git/hooks/gitolite-hooked") {
|
|
|
|
ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo.git/hooks");
|
|
|
|
# in case of package install, GL_ADMINDIR is no longer the top cop;
|
|
|
|
# override with the package hooks
|
|
|
|
ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo.git/hooks") if $GL_PACKAGE_HOOKS;
|
|
|
|
}
|
2009-12-01 17:24:23 +01:00
|
|
|
}
|
|
|
|
|
2009-09-21 04:18:30 +02:00
|
|
|
warn "\n\t\t***** WARNING *****\n" .
|
|
|
|
"\tyour git version is older than 1.6.2\n" .
|
|
|
|
"\tgitolite will work but you MUST read the section on\n" .
|
|
|
|
"\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n"
|
2009-11-27 18:30:58 +01:00
|
|
|
if $git_version < 10602; # that's 1.6.2 to you
|
2009-08-24 10:00:58 +02:00
|
|
|
|
2009-12-07 21:20:29 +01:00
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# update repo configurations
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
for my $repo (keys %repo_config) {
|
|
|
|
wrap_chdir("$repo_base_abs/$repo.git");
|
|
|
|
while ( my ($key, $value) = each(%{ $repo_config{$repo} }) ) {
|
|
|
|
if ($value) {
|
|
|
|
$value =~ s/^"(.*)"$/$1/;
|
|
|
|
system("git", "config", $key, $value);
|
|
|
|
} else {
|
|
|
|
system("git", "config", "--unset-all", $key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-24 10:00:58 +02:00
|
|
|
# ----------------------------------------------------------------------------
|
2009-09-25 08:47:33 +02:00
|
|
|
# handle gitweb and daemon
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
# How you specify gitweb and daemon access is quite different from gitosis. I
|
|
|
|
# just assume you'll never have any *real* users called "gitweb" or "daemon"
|
|
|
|
# :-) These are now "pseduo users" -- giving them "R" access to a repo is all
|
|
|
|
# you have to do
|
|
|
|
|
|
|
|
wrap_chdir("$repo_base_abs");
|
|
|
|
|
|
|
|
# daemons first...
|
|
|
|
for my $repo (sort keys %repos) {
|
2009-12-05 14:08:46 +01:00
|
|
|
next unless $repo =~ $REPONAME_PATT;
|
2009-09-25 08:47:33 +02:00
|
|
|
my $export_ok = "$repo.git/git-daemon-export-ok";
|
|
|
|
if ($repos{$repo}{'R'}{'daemon'}) {
|
2009-11-26 15:00:37 +01:00
|
|
|
system("touch $export_ok");
|
2009-09-25 08:47:33 +02:00
|
|
|
} else {
|
2009-11-26 15:00:37 +01:00
|
|
|
unlink($export_ok);
|
2009-09-25 08:47:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-11-26 15:00:37 +01:00
|
|
|
my %projlist = ();
|
2009-09-25 08:47:33 +02:00
|
|
|
# ...then gitwebs
|
|
|
|
for my $repo (sort keys %repos) {
|
2009-12-05 14:08:46 +01:00
|
|
|
next unless $repo =~ $REPONAME_PATT;
|
2009-11-12 10:19:39 +01:00
|
|
|
my $desc_file = "$repo.git/description";
|
|
|
|
# note: having a description also counts as enabling gitweb
|
2009-11-27 08:53:48 +01:00
|
|
|
if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) {
|
2009-11-26 15:00:37 +01:00
|
|
|
$projlist{"$repo.git"} = 1;
|
2009-11-12 10:19:39 +01:00
|
|
|
# add the description file; no messages to user or error checking :)
|
2009-11-27 08:53:48 +01:00
|
|
|
$desc{"$repo.git"} and open(DESC, ">", $desc_file) and print DESC $desc{"$repo.git"} . "\n" and close DESC;
|
2010-02-03 09:13:47 +01:00
|
|
|
if ($owner{"$repo.git"}) {
|
|
|
|
# set the repository owner
|
|
|
|
system("git", "--git-dir=$repo.git", "config", "gitweb.owner", $owner{"$repo.git"});
|
|
|
|
} else {
|
|
|
|
# remove the repository owner setting
|
|
|
|
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
|
|
|
}
|
2009-09-25 08:47:33 +02:00
|
|
|
} else {
|
2009-11-12 10:19:39 +01:00
|
|
|
# delete the description file; no messages to user or error checking :)
|
|
|
|
unlink $desc_file;
|
2010-02-03 09:13:47 +01:00
|
|
|
# remove the repository owner setting
|
|
|
|
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
|
|
|
}
|
|
|
|
|
|
|
|
# unless there are other gitweb.* keys set, remove the section to keep the
|
|
|
|
# config file clean
|
|
|
|
my $keys = `git --git-dir=$repo.git config --get-regexp '^gitweb\\.' 2>/dev/null`;
|
|
|
|
if (length($keys) == 0) {
|
|
|
|
system("git --git-dir=$repo.git config --remove-section gitweb 2>/dev/null");
|
2009-09-25 08:47:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-11-26 15:00:37 +01:00
|
|
|
# update the project list
|
|
|
|
my $projlist_fh = wrap_open( ">", $PROJECTS_LIST);
|
2009-11-27 08:53:48 +01:00
|
|
|
for my $proj (sort keys %projlist) {
|
2010-02-03 09:13:47 +01:00
|
|
|
print $projlist_fh "$proj\n";
|
2009-11-27 08:53:48 +01:00
|
|
|
}
|
2009-11-26 15:00:37 +01:00
|
|
|
close $projlist_fh;
|
2009-09-25 08:47:33 +02:00
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
2009-08-24 10:00:58 +02:00
|
|
|
# "compile" ssh authorized_keys
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
2009-09-17 07:09:13 +02:00
|
|
|
my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys",
|
|
|
|
"\tFor security reasons, gitolite will not *create* this file if it does\n" .
|
|
|
|
"\tnot already exist. Please see the \"admin\" document for details\n");
|
2009-08-31 04:28:08 +02:00
|
|
|
my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" );
|
2009-08-24 10:00:58 +02:00
|
|
|
# save existing authkeys minus the GL-added stuff
|
2009-08-25 06:27:19 +02:00
|
|
|
while (<$authkeys_fh>)
|
2009-08-24 10:00:58 +02:00
|
|
|
{
|
2009-08-26 03:35:04 +02:00
|
|
|
print $newkeys_fh $_ unless (/^# gito(sis-)?lite start/../^# gito(sis-)?lite end/);
|
2009-08-24 10:00:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# add our "start" line, each key on its own line (prefixed by command and
|
|
|
|
# options, in the standard ssh authorized_keys format), then the "end" line.
|
2009-08-26 02:47:27 +02:00
|
|
|
print $newkeys_fh "# gitolite start\n";
|
2009-08-31 04:28:08 +02:00
|
|
|
wrap_chdir($GL_KEYDIR);
|
2009-09-27 04:32:36 +02:00
|
|
|
for my $pubkey (glob("*"))
|
2009-08-24 10:00:58 +02:00
|
|
|
{
|
2010-04-09 13:18:46 +02:00
|
|
|
# security check (thanks to divVerent for catching this)
|
|
|
|
unless ($pubkey =~ $USERNAME_PATT) {
|
|
|
|
print STDERR "$pubkey contains some unsavoury characters; ignored...\n";
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2009-09-27 04:32:36 +02:00
|
|
|
# lint check 1
|
|
|
|
unless ($pubkey =~ /\.pub$/)
|
|
|
|
{
|
|
|
|
print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n";
|
|
|
|
next;
|
|
|
|
}
|
2009-12-08 10:33:38 +01:00
|
|
|
my $user = $pubkey; $user =~ s/(\@[^.]+)?\.pub$//;
|
2009-09-27 04:32:36 +02:00
|
|
|
# lint check 2
|
|
|
|
print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n"
|
|
|
|
unless $user_list{$user};
|
|
|
|
$user_list{$user} = 'has pubkey';
|
2010-01-15 09:50:00 +01:00
|
|
|
# apparently some pubkeys don't end in a newline...
|
|
|
|
my $pubkey_content = `cat $pubkey`;
|
|
|
|
$pubkey_content =~ s/\s*$/\n/;
|
|
|
|
# don't trust files with multiple lines (i.e., something after a newline)
|
|
|
|
if ($pubkey_content =~ /\n./)
|
|
|
|
{
|
|
|
|
print STDERR "WARNING: a pubkey file can only have one line (key); ignoring $pubkey\n";
|
|
|
|
next;
|
|
|
|
}
|
2010-04-09 17:22:32 +02:00
|
|
|
print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS,no-pty ";
|
2009-10-23 05:25:03 +02:00
|
|
|
print $newkeys_fh $pubkey_content;
|
2009-08-24 10:00:58 +02:00
|
|
|
}
|
2009-09-27 04:32:36 +02:00
|
|
|
# lint check 3; a little more severe than the first two I guess...
|
|
|
|
for my $user (sort keys %user_list)
|
|
|
|
{
|
2010-03-17 16:12:19 +01:00
|
|
|
next if $user =~ /^(gitweb|daemon|\@all|~\$creater|\$readers|\$writers)$/ or $user_list{$user} eq 'has pubkey';
|
2009-11-03 15:54:53 +01:00
|
|
|
print STDERR "$WARN user $user in config, but has no pubkey!\n";
|
2009-09-27 04:32:36 +02:00
|
|
|
}
|
|
|
|
|
2009-08-26 02:47:27 +02:00
|
|
|
print $newkeys_fh "# gitolite end\n";
|
2009-11-03 15:54:53 +01:00
|
|
|
close $newkeys_fh or die "$ABRT close newkeys failed: $!\n";
|
2009-08-24 10:00:58 +02:00
|
|
|
|
|
|
|
# all done; overwrite the file (use cat to avoid perm changes)
|
2009-09-01 16:10:42 +02:00
|
|
|
system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys");
|
2010-02-04 18:25:11 +01:00
|
|
|
system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys")
|
|
|
|
and die "couldn't write authkeys file\n";
|
2009-08-30 17:44:15 +02:00
|
|
|
system("rm $ENV{HOME}/.ssh/new_authkeys");
|