Git-Server
view release on metacpan or search on metacpan
* Secured ~/.ssh/authorized_keys format, including "REMOTE_USER" settings
* Make sure ~/.ssh/authorized_keys has at least one ADMIN user
* ADMIN and ACL Management (if "ADMIN")
* Create / Remove repos (if "ADMIN")
* Validate or Configure "any-user"@server feature (if "ADMIN")
=head1 SUPPORTED
* Only allow safe read-only operations appropriate for REMOTE_USER powers.
* Validate git-server installation.
* Verify AllowAgentForwarding setting for two-way sync feature.
* Verify AcceptEnv setting.
* Check ExposeAuthInfo functionality to be able to determine key info, if desired.
* List accessible repos specified or scanned.
* Files and Directories and ~/.ssh/authorized_keys chmod permissions.
=head1 AUTHOR
Rob Brown <bbb@cpan.org>
=head1 COPYRIGHT AND LICENSE
Copyright 2025-2026 by Rob Brown <bbb@cpan.org>
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
=cut
use strict;
use warnings;
use Cwd qw(abs_path);
use FindBin qw($RealBin);
our $VERSION = "0.045";
if (@ARGV and local $_ = $ARGV[0] || die "Bad arg\n" and !/^--/) {
s{^ssh://(|[^/:]+\@)\[([a-z0-9\-\.\:]+)\]:(\d+)(?:$|/)}{}i ||
s{^ssh://(|[^/:]+\@)\[?([a-z0-9\-\.]+)\]?:\[?(\d+)\]?(?:$|/)}{}i ||
s{^ssh://(|[^/:]+\@)\[?([a-z0-9\-\.\:]+)\]?(?:$|/)}{}i ||
s{^(|[^/:]+\@)\[([a-z0-9\-\.]+):(\d+)\](?:$|:)}{}i ||
s{^(|[^/:]+\@)\[([a-z0-9\-\.\:]+)\]()(?:$|:)}{}i ||
s{^(|[^/:]+\@)([a-z0-9\-\.]+)()(?:$|:)}{}i and do {
my $userprefix = $1;
my $remotehost = $2;
my $remoteport = $3 || 22;
my $resolvable = undef;
$resolvable = gethostbyname($remotehost) or eval {
require Socket;
my ($err, @res) = Socket::getaddrinfo($remotehost,22,{socktype=>Socket::SOCK_STREAM(),family=>Socket::AF_UNSPEC(),flags=>((eval{Socket::AI_ALL()}||0)|(eval{Socket::AI_V4MAPPED()}||0))});
$resolvable = @res;
1;
} or eval {
require File::Which; # Try "dig" as last-ditch effort:
my $dig = File::Which::which("dig") or die "no dig";
$resolvable = `$dig +short $remotehost A $remotehost AAAA 2>/dev/null`;
1;
} or $resolvable = 1; # If IPv6 resolvers are unavailable, then assume it's valid, and just blindly run ssh
if ($resolvable) {
length()?$ARGV[0]=$_:shift;
$ENV{XMODIFIERS}="client=".abs_path($0)."\@v$VERSION";
my $search = q{$_=shift and 1+s/'/'"'"'/g and`(which '$_' git-verify|grep . || find . ~ /usr/local/bin /usr/bin /bin -name git-verify) 2>/dev/null`=~m{^(.+)}&&exec $1,@ARGV;warn"Cannot find git-verify on ".`hostname`."[$1][$!]['$_' @ARGV]...
pipe my $FAKE_STDIN, my $prog;
print $prog $search;
close $prog;
open STDIN, "<&", $FAKE_STDIN;
exec qw[ssh
-o PubKeyAuthentication=yes
-o KbdInteractiveAuthentication=no
-o PasswordAuthentication=no
-o SendEnv=XMODIFIERS
-p], $remoteport,
"$userprefix$remotehost","perl","-",abs_path($0),(
!(grep {m{[^\w/\-.]}} @ARGV) ? @ARGV : # Unable to find any potentionally stank chars so it's safe to pass @ARGV as is.
("--escaperepos",map{unpack"H*",$_}@ARGV) # Otherwise, encode every character for all repos just to be extra safe.
) or die "$0: remote verification failed.\n";
}
};
}
my @repo_dirs = @ARGV;
if (!@repo_dirs) {
local $_ = $ENV{SSH_ORIGINAL_COMMAND} || "";
@repo_dirs = split;
splice @repo_dirs, 0, 2 if $repo_dirs[0] && $repo_dirs[1] && $repo_dirs[0] eq "perl" && $repo_dirs[1] eq "-";
shift @repo_dirs if $repo_dirs[0] && $repo_dirs[0] =~ m{(^|/)git-verify$};
}
if (@repo_dirs and $repo_dirs[0] eq "--escaperepos") {
shift @repo_dirs;
foreach (@repo_dirs) {
$_ = pack "H*",$_;
}
}
if (!@repo_dirs and !$ENV{GIT_SERVER_VERSION} and my $gitdir = $ENV{GIT_DIR}) {
# Not running within git-server properly, so use a wrapper to avoid this problem in the future.
eval {
require File::Spec;
my $fixed = 0;
foreach my $path (File::Spec->path) {
if (-d $path and -w _) { # First writeable directory in $PATH
foreach (qw[git-upload-pack git-receive-pack]) {
symlink "$RealBin/git-packer","$path/$_" and ++$fixed and warn "Installed symlink: $path/$_ -> $RealBin/git-packer\n" if !-e "$path/$_";
}
last if $fixed;
}
}
};
@repo_dirs = (abs_path $gitdir);
}
sub run_how_cmd {
my $stderr = shift;
my @cmd = @_;
if (my $pid = open my $fh_out, "-|") {
# Parent process waiting for kid to say something
my $output = join "", <$fh_out>;
waitpid $pid, 0;
my $exit_status = $?;
close $fh_out;
$? = $exit_status;
return $output;
}
( run in 0.592 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )