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-12-05 18:09:56 +01:00
|
|
|
$Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; };
|
|
|
|
# this is to make sure that $creater etc go to the end of the dumped hash.
|
|
|
|
# Without this, a setup that has something like
|
|
|
|
# @team = u1 u2 u3
|
|
|
|
# repo priv/CREATER/.+
|
|
|
|
# RW+ = CREATER
|
|
|
|
# RW = @team
|
|
|
|
# has a problem. The RW overrides the RW+ when the dumped hash is read in
|
|
|
|
# (simply going by sequence), so creater's special privs are lost
|
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-03-07 14:35:56 +01:00
|
|
|
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $SHELL_USERS, $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;
|
2009-12-21 00:55:45 +01:00
|
|
|
if (@repos == 1 and $repos[0] eq '@all') {
|
|
|
|
@repos = keys %repos;
|
|
|
|
} else {
|
|
|
|
@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
|
2009-12-05 14:08:46 +01:00
|
|
|
elsif (/^(-|C|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
|
|
|
|
2009-12-05 14:08:46 +01:00
|
|
|
s/\bCREAT[EO]R\b/\$creater/g for @users;
|
|
|
|
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/;
|
|
|
|
$repos{$repo}{W}{$user} = 1 if $perms =~ /W/;
|
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...
|
2010-03-16 02:56:33 +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
|
|
|
{
|
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-01-14 10:44:40 +01:00
|
|
|
if ($SHELL_USERS and $SHELL_USERS =~ /(^|\s)$user(\s|$)/) {
|
2009-12-19 17:06:55 +01:00
|
|
|
print $newkeys_fh "command=\"$AUTH_COMMAND -s $user\",$AUTH_OPTIONS ";
|
|
|
|
} else {
|
|
|
|
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)
|
|
|
|
{
|
2009-12-05 14:08:46 +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");
|