From a03f37af5b920a8b111450f715ef419ec1a9ec33 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 23 Jul 2008 18:47:11 +0200 Subject: [PATCH] Big module renaming: Git::RepoCGI -> Girocco::* Rename Git::RepoCGI to Girocco::CGI and at the same time decouple the project and user modules to Girocco::Project and Girocco::User, respectively; the longer-term goal is to further factor out common stuff from Girocco::CGI (to Girocco::Util?). Configuration variables are factored out to Girocco::Config. --- Girocco/CGI.pm | 230 ++++++++++++++++ Girocco/Config.pm | 26 ++ Girocco/Project.pm | 390 ++++++++++++++++++++++++++ Girocco/User.pm | 164 +++++++++++ cgi/Girocco | 1 + cgi/Git/RepoCGI.pm | 793 ----------------------------------------------------- cgi/delproj.cgi | 10 +- cgi/editproj.cgi | 18 +- cgi/edituser.cgi | 8 +- cgi/pwproj.cgi | 14 +- cgi/regproj.cgi | 24 +- cgi/reguser.cgi | 10 +- cgi/tagproj.cgi | 6 +- clone.sh | 2 +- inactive | 2 +- 15 files changed, 858 insertions(+), 840 deletions(-) create mode 100644 Girocco/CGI.pm create mode 100644 Girocco/Config.pm create mode 100644 Girocco/Project.pm create mode 100644 Girocco/User.pm create mode 120000 cgi/Girocco delete mode 100644 cgi/Git/RepoCGI.pm diff --git a/Girocco/CGI.pm b/Girocco/CGI.pm new file mode 100644 index 0000000..effdde1 --- /dev/null +++ b/Girocco/CGI.pm @@ -0,0 +1,230 @@ +package Girocco::CGI; + +use strict; +use warnings; + +use Girocco::Config; + +### Administrativa + +BEGIN { + our $VERSION = '0.1'; + our @ISA = qw(Exporter); + our @EXPORT = qw(scrypt html_esc jailed_file + lock_file unlock_file + filedb_atomic_append filedb_atomic_edit + proj_get_forkee_name proj_get_forkee_path + valid_proj_name valid_user_name valid_email valid_repo_url valid_web_url); + + use CGI qw(:standard :escapeHTML -nosticky); + use CGI::Util qw(unescape); + use CGI::Carp qw(fatalsToBrowser); + use Digest::SHA1 qw(sha1_hex); +} + + +### RepoCGI object + +sub new { + my $class = shift; + my ($heading) = @_; + my $repo = {}; + + $repo->{cgi} = CGI->new; + + print $repo->{cgi}->header(-type=>'text/html', -charset => 'utf-8'); + + print < + + + + +$Girocco::Config::name :: $heading + + + + + + + + +EOT + + bless $repo, $class; +} + +sub DESTROY { + my $self = shift; + my $cgi = $self->cgi; + my $cgiurl = $cgi->url(-absolute => 1); + my ($cginame) = ($cgiurl =~ m#^.*/\([a-zA-Z0-9_.\/-]+?\.cgi\)$#); # + if ($cginame and $Girocco::Config::giroccourl) { + print < +(view source) + +EOT + } + print < + +EOT +} + +sub cgi { + my $self = shift; + $self->{cgi}; +} + +sub err { + my $self = shift; + print "

@_

\n"; + $self->{err}++; +} + +sub err_check { + my $self = shift; + my $err = $self->{err}; + $err and print "

Operation aborted due to $err errors.

\n"; + $err; +} + +sub wparam { + my $self = shift; + my ($param) = @_; + my $val = $self->{cgi}->param($param); + $val =~ s/^\s*(.*?)\s*$/$1/; + $val; +} + + +### Random utility functions + +sub scrypt { + my ($pwd) = @_; + crypt($pwd, join ('', ('.', '/', 2..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64])); +} + +sub html_esc { + my ($str) = @_; + $str =~ s/&/&/g; + $str =~ s//>/g; + $str =~ s/"/"/g; + $str; +} + +sub jailed_file { + my ($filename) = @_; + $Girocco::Config::chroot."/$filename"; +} + +sub lock_file { + my ($path) = @_; + + $path .= '.lock'; + + use Errno qw(EEXIST); + use Fcntl qw(O_WRONLY O_CREAT O_EXCL); + use IO::Handle; + my $handle = new IO::Handle; + + unless (sysopen($handle, $path, O_WRONLY|O_CREAT|O_EXCL)) { + my $cnt = 0; + while (not sysopen($handle, $path, O_WRONLY|O_CREAT|O_EXCL)) { + ($! == EEXIST) or die "$path open failed: $!"; + ($cnt++ < 16) or die "$path open failed: cannot open lockfile"; + sleep(1); + } + } + # XXX: filedb-specific + chmod 0664, $path or die "$path g+w failed: $!"; + + $handle; +} + +sub unlock_file { + my ($path) = @_; + + rename "$path.lock", $path or die "$path unlock failed: $!"; +} + +sub filedb_atomic_append { + my ($file, $line) = @_; + my $id = 65536; + + open my $src, $file or die "$file open for reading failed: $!"; + my $dst = lock_file($file); + + while (<$src>) { + my $aid = (split /:/)[2]; + $id = $aid + 1 if ($aid >= $id); + + print $dst $_ or die "$file(l) write failed: $!"; + } + + $line =~ s/\\i/$id/g; + print $dst "$line\n" or die "$file(l) write failed: $!"; + + close $dst or die "$file(l) close failed: $!"; + close $src; + + unlock_file($file); + + $id; +} + +sub filedb_atomic_edit { + my ($file, $fn) = @_; + + open my $src, $file or die "$file open for reading failed: $!"; + my $dst = lock_file($file); + + while (<$src>) { + print $dst $fn->($_) or die "$file(l) write failed: $!"; + } + + close $dst or die "$file(l) close failed: $!"; + close $src; + + unlock_file($file); +} + +sub proj_get_forkee_name { + $_ = $_[0]; + (m#^(.*)/.*?$#)[0]; +} +sub proj_get_forkee_path { + my $forkee = $Girocco::Config::reporoot.'/'.proj_get_forkee_name($_[0]).'.git'; + -d $forkee ? $forkee : ''; +} +sub valid_proj_name { + $_ = $_[0]; + (not m#/# or -d proj_get_forkee_path($_)) # will also catch ^/ + and (not m#\./#) + and (not m#/$#) + and m#^[a-zA-Z0-9+./_-]+$#; +} +sub valid_user_name { + $_ = $_[0]; + /^[a-zA-Z0-9+._-]+$/; +} +sub valid_email { + $_ = $_[0]; + /^[a-zA-Z0-9+._-]+@[a-zA-Z0-9-.]+$/; +} +sub valid_web_url { + $_ = $_[0]; + /^http:\/\/[a-zA-Z0-9-.]+(\/[_\%a-zA-Z0-9.\/~-]*)?(#[a-zA-Z0-9._-]+)?$/; +} +sub valid_repo_url { + $_ = $_[0]; + /^http:\/\/[a-zA-Z0-9-.]+(\/[_\%a-zA-Z0-9.\/~-]*)?$/ or + /^git:\/\/[a-zA-Z0-9-.]+(\/[_\%a-zA-Z0-9.\/~-]*)?$/; +} + + +1; diff --git a/Girocco/Config.pm b/Girocco/Config.pm new file mode 100644 index 0000000..6afe959 --- /dev/null +++ b/Girocco/Config.pm @@ -0,0 +1,26 @@ +package Girocco::Config; + +use strict; +use warnings; + +our $basedir = '/home/repo/repomgr'; # path to the Girocco files (checkout of this project) +our $mqueuedir = '/home/repo/repodata'; # path to the directory of the mirror queue +our $chroot = "/home/repo/j"; # ssh push chroot +our $reporoot = "/srv/git"; # repository collection +our $name = "repo.or.cz"; # title of the service +our $gitweburl = "http://repo.or.cz/w"; # URL of gitweb (pathinfo mode) +our $webadmurl = "http://repo.or.cz/m"; # URL of the 'repo' CGI web admin interface +our $httppullurl = "http://repo.or.cz/r"; # HTTP URL of the repository collection +our $gitpullurl = "git://repo.or.cz/"; # Git URL of the repository collection +our $pushurl = "ssh://repo.or.cz/srv/git"; # Pushy URL of the repository collection +our $jurisdiction = "Czech Republic"; # legal jurisdiction of the site +our $giroccourl = "$Girocco::Config::gitweburl/repo.git"; # URL of gitweb of this Girocco instance (set as undef if you're not nice to the community) +our $mob = "mob"; # set to undef to disable the special 'mob' user +our $moburl = "/mob.html"; # URL of the explanation of the mob user +our $mirror = 1; # enable mirroring mode +our $push = 1; # enable push mode +our $group = 'repo'; # UNIX group owning the repositories +our $git_bin = '/home/pasky/bin/git'; # path to Git binary to use + + +1; diff --git a/Girocco/Project.pm b/Girocco/Project.pm new file mode 100644 index 0000000..e12cf02 --- /dev/null +++ b/Girocco/Project.pm @@ -0,0 +1,390 @@ +package Girocco::Project; + +use strict; +use warnings; + +use Girocco::Config; + +BEGIN { use Girocco::CGI; } + +sub _mkdir_forkees { + my $self = shift; + my @pelems = split('/', $self->{name}); + pop @pelems; # do not create dir for the project itself + my $path = $self->{base_path}; + foreach my $pelem (@pelems) { + $path .= "/$pelem"; + (-d "$path") or mkdir $path or die "mkdir $path: $!"; + chmod 0775, $path; # ok if fails (dir may already exist and be owned by someone else) + } +} + +our %propmap = ( + url => 'base_url', + email => 'owner', + desc => 'description', + README => 'README.html', + hp => 'homepage', +); + +sub _property_path { + my $self = shift; + my ($name) = @_; + $self->{path}.'/'.$name; +} + +sub _property_fget { + my $self = shift; + my ($name) = @_; + $propmap{$name} or die "unknown property: $name"; + open P, $self->_property_path($propmap{$name}) or return undef; + my @value =

; + close P; + my $value = join('', @value); chomp $value; + $value; +} + +sub _property_fput { + my $self = shift; + my ($name, $value) = @_; + $propmap{$name} or die "unknown property: $name"; + + my $P = lock_file($self->_property_path($propmap{$name})); + $value ne '' and print $P "$value\n"; + close $P; + unlock_file($self->_property_path($propmap{$name})); +} + +sub _properties_load { + my $self = shift; + foreach my $prop (keys %propmap) { + $self->{$prop} = $self->_property_fget($prop); + } +} + +sub _properties_save { + my $self = shift; + foreach my $prop (keys %propmap) { + $self->_property_fput($prop, $self->{$prop}); + } +} + +sub _nofetch_path { + my $self = shift; + $self->_property_path('.nofetch'); +} + +sub _nofetch { + my $self = shift; + my ($nofetch) = @_; + my $np = $self->_nofetch_path; + if ($nofetch) { + open X, '>'.$np or die "nofetch failed: $!"; + close X; + } else { + unlink $np or die "yesfetch failed: $!"; + } +} + +sub _alternates_setup { + my $self = shift; + return unless $self->{name} =~ m#/#; + my $forkee_name = proj_get_forkee_name($self->{name}); + my $forkee_path = proj_get_forkee_path($self->{name}); + return unless -d $forkee_path; + mkdir $self->{path}.'/refs'; chmod 0775, $self->{path}.'/refs'; + mkdir $self->{path}.'/objects'; chmod 0775, $self->{path}.'/objects'; + mkdir $self->{path}.'/objects/info'; chmod 0775, $self->{path}.'/objects/info'; + + # We set up both alternates and http_alternates since we cannot use + # relative path in alternates - that doesn't work recursively. + + my $filename = $self->{path}.'/objects/info/alternates'; + open X, '>'.$filename or die "alternates failed: $!"; + print X "$forkee_path/objects\n"; + close X; + chmod 0664, $filename or warn "cannot chmod $filename: $!"; + + if ($Girocco::Config::httppullurl) { + $filename = $self->{path}.'/objects/info/http-alternates'; + open X, '>'.$filename or die "http-alternates failed: $!"; + my $upfork = $forkee_name; + do { print X "$Girocco::Config::httppullurl/$upfork.git/objects\n"; } while ($upfork =~ s#/?.+?$## and $upfork); + close X; + chmod 0664, $filename or warn "cannot chmod $filename: $!"; + } + + symlink "$forkee_path/refs", $self->{path}.'/refs/forkee'; +} + +sub _ctags_setup { + my $self = shift; + mkdir $self->{path}.'/ctags'; chmod 0775, $self->{path}.'/ctags'; +} + +sub _group_add { + my $self = shift; + my ($xtra) = @_; + $xtra .= join(',', @{$self->{users}}); + filedb_atomic_append(jailed_file('/etc/group'), + join(':', $self->{name}, $self->{crypt}, '\i', $xtra)); +} + +sub _group_update { + my $self = shift; + my $xtra = join(',', @{$self->{users}}); + filedb_atomic_edit(jailed_file('/etc/group'), + sub { + $_ = $_[0]; + chomp; + if ($self->{name} eq (split /:/)[0]) { + # preserve readonly flag + s/::([^:]*)$/:$1/ and $xtra = ":$xtra"; + return join(':', $self->{name}, $self->{crypt}, $self->{gid}, $xtra)."\n"; + } else { + return "$_\n"; + } + } + ); +} + +sub _group_remove { + my $self = shift; + filedb_atomic_edit(jailed_file('/etc/group'), + sub { + $self->{name} ne (split /:/)[0] and return $_; + } + ); +} + +sub _hook_path { + my $self = shift; + my ($name) = @_; + $self->{path}.'/hooks/'.$name; +} + +sub _hook_install { + my $self = shift; + my ($name) = @_; + open SRC, "$Girocco::Config::basedir/$name-hook" or die "cannot open hook $name: $!"; + open DST, '>'.$self->_hook_path($name) or die "cannot open hook $name for writing: $!"; + while () { print DST $_; } + close DST; + close SRC; + chmod 0775, $self->_hook_path($name) or die "cannot chmod hook $name: $!"; +} + +sub _hooks_install { + my $self = shift; + foreach my $hook ('update') { + $self->_hook_install($hook); + } +} + +# private constructor, do not use +sub _new { + my $class = shift; + my ($name, $base_path, $path) = @_; + valid_proj_name($name) or die "refusing to create project with invalid name ($name)!"; + $path ||= "$base_path/$name.git"; + my $proj = { name => $name, base_path => $base_path, path => $path }; + + bless $proj, $class; +} + +# public constructor #0 +# creates a virtual project not connected to disk image +# you can conjure() it later to disk +sub ghost { + my $class = shift; + my ($name, $mirror) = @_; + my $self = $class->_new($name, $mirror ? "$Girocco::Config::mqueuedir/to-clone" : $Girocco::Config::reporoot, + $mirror ? "$Girocco::Config::mqueuedir/to-clone/$name" : $Girocco::Config::reporoot."/$name.git"); + $self->{users} = []; + $self->{mirror} = $mirror; + $self; +} + +# public constructor #1 +sub load { + my $class = shift; + my ($name) = @_; + + open F, jailed_file("/etc/group") or die "project load failed: $!"; + while () { + chomp; + @_ = split /:+/; + next unless (shift eq $name); + + my $self = $class->_new($name, $Girocco::Config::reporoot); + (-d $self->{path}) or die "invalid path (".$self->{path}.") for project ".$self->{name}; + + my $ulist; + ($self->{crypt}, $self->{gid}, $ulist) = @_; + $ulist ||= ''; + $self->{users} = [split /,/, $ulist]; + $self->{mirror} = ! -e $self->_nofetch_path; + $self->{ccrypt} = $self->{crypt}; + + $self->_properties_load; + return $self; + } + close F; + undef; +} + +# $proj may not be in sane state if this returns false! +sub cgi_fill { + my $self = shift; + my ($repo) = @_; + my $cgi = $repo->cgi; + + my $pwd = $cgi->param('pwd'); + if ($pwd ne '' or not $self->{crypt}) { + $self->{crypt} = scrypt($pwd); + } + + if ($cgi->param('pwd2') and $pwd ne $cgi->param('pwd2')) { + $repo->err("Our high-paid security consultants have determined that the admin passwords you have entered do not match each other."); + } + + $self->{cpwd} = $cgi->param('cpwd'); + + $self->{email} = $repo->wparam('email'); + valid_email($self->{email}) + or $repo->err("Your email sure looks weird...?"); + + $self->{url} = $repo->wparam('url'); + if ($self->{url}) { + valid_repo_url($self->{url}) + or $repo->err("Invalid URL. Note that only HTTP and Git protocol is supported. If the URL contains funny characters, contact me."); + } + + $self->{desc} = $repo->wparam('desc'); + length($self->{desc}) <= 1024 + or $repo->err("Short description length > 1kb!"); + + $self->{README} = $repo->wparam('README'); + length($self->{README}) <= 8192 + or $repo->err("README length > 8kb!"); + + $self->{hp} = $repo->wparam('hp'); + if ($self->{hp}) { + valid_web_url($self->{hp}) + or $repo->err("Invalid homepage URL. Note that only HTTP protocol is supported. If the URL contains funny characters, contact me."); + } + + # FIXME: Permit only existing users + $self->{users} = [grep { valid_user_name($_) } $cgi->param('user')]; + + not $repo->err_check; +} + +sub form_defaults { + my $self = shift; + ( + name => $self->{name}, + email => $self->{email}, + url => $self->{url}, + desc => html_esc($self->{desc}), + README => html_esc($self->{README}), + hp => $self->{hp}, + users => $self->{users}, + ); +} + +sub authenticate { + my $self = shift; + my ($repo) = @_; + + $self->{ccrypt} or die "Can't authenticate against a project with no password"; + $self->{cpwd} or $repo->err("No password entered."); + unless ($self->{ccrypt} eq crypt($self->{cpwd}, $self->{ccrypt})) { + $repo->err("Your admin password does not match!"); + return 0; + } + return 1; +} + +sub premirror { + my $self = shift; + + $self->_mkdir_forkees; + mkdir $self->{path} or die "mkdir failed: $!"; + chmod 0775, $self->{path} or die "chmod failed: $!"; + $self->_properties_save; + $self->_alternates_setup; + $self->_ctags_setup; + $self->_group_add(':'); +} + +sub conjure { + my $self = shift; + + $self->_mkdir_forkees; + + mkdir($self->{path}) or die "mkdir $self->{path} failed: $!"; + my $gid = scalar(getgrnam($Girocco::Config::group)); + chown(-1, $gid, $self->{path}) or die "chgrp $gid $self->{path} failed: $!"; + chmod(2775, $self->{path}) or die "chmod 2775 $self->{path} failed: $!"; + system($Girocco::Config::git_bin, '--git-dir='.$self->{path}, 'init', '--bare', '--shared=group') + or die "git init $self->{path} failed: $!"; + system($Girocco::Config::git_bin, '--git-dir='.$self->{path}, 'config', 'receive.denyNonFastforwards', 'false') + or die "disabling receive.denyNonFastforwards failed: $!"; + + $self->_nofetch(1); + $self->_properties_save; + $self->_alternates_setup; + $self->_ctags_setup; + $self->_group_add; + $self->_hooks_install; +} + +sub update { + my $self = shift; + + $self->_properties_save; + $self->_group_update; +} + +sub update_password { + my $self = shift; + my ($pwd) = @_; + + $self->{crypt} = scrypt($pwd); + $self->_group_update; +} + +# You can explicitly do this just on a ghost() repository too. +sub delete { + my $self = shift; + + if (-d $self->{path}) { + system('rm', '-r', $self->{path}) == 0 + or die "rm -r failed: $?"; + } + $self->_group_remove; +} + +sub has_forks { + my $self = shift; + + return glob($Girocco::Config::reporoot.'/'.$self->{name}.'/*'); +} + +# static method +sub does_exist { + my ($name) = @_; + valid_proj_name($name) or die "tried to query for project with invalid name $name!"; + (available($name) + or -d $Girocco::Config::mqueuedir."/cloning/$name" + or -d $Girocco::Config::mqueuedir."/to-clone/$name"); +} +sub available { + my ($name) = @_; + valid_proj_name($name) or die "tried to query for project with invalid name $name!"; + (-d $Girocco::Config::reporoot."/$name.git"); +} + + +1; diff --git a/Girocco/User.pm b/Girocco/User.pm new file mode 100644 index 0000000..b55891d --- /dev/null +++ b/Girocco/User.pm @@ -0,0 +1,164 @@ +package Girocco::User; + +use strict; +use warnings; + +use Girocco::Config; + +BEGIN { use Girocco::CGI; } + +sub _passwd_add { + my $self = shift; + filedb_atomic_append(jailed_file('/etc/passwd'), + join(':', $self->{name}, 'x', '\i', 65534, $self->{email}, '/', '/bin/git-shell')); +} + +sub _sshkey_path { + my $self = shift; + '/etc/sshkeys/'.$self->{name}; +} + +sub _sshkey_load { + my $self = shift; + open F, "<".jailed_file($self->_sshkey_path) or die "sshkey load failed: $!"; + my @keys; + my $auth; + while () { + chomp; + if (/^ssh-(?:dss|rsa) /) { + push @keys, $_; + } elsif (/^# REPOAUTH ([0-9a-f]+) (\d+)/) { + my $expire = $2; + $auth = $1 unless (time >= $expire); + } + } + close F; + my $keys = join('', @keys); chomp $keys; + ($keys, $auth); +} + +sub _sshkey_save { + my $self = shift; + open F, ">".jailed_file($self->_sshkey_path) or die "sshkey failed: $!"; + if (defined($self->{auth}) && $self->{auth}) { + my $expire = time + 24 * 3600; + print F "# REPOAUTH $self->{auth} $expire\n"; + } + print F $self->{keys}."\n"; + close F; + chmod 0664, jailed_file($self->_sshkey_path); +} + +# private constructor, do not use +sub _new { + my $class = shift; + my ($name) = @_; + valid_user_name($name) or die "refusing to create user with invalid name ($name)!"; + my $proj = { name => $name }; + + bless $proj, $class; +} + +# public constructor #0 +# creates a virtual user not connected to disk record +# you can conjure() it later to disk +sub ghost { + my $class = shift; + my ($name) = @_; + my $self = $class->_new($name); + $self; +} + +# public constructor #1 +sub load { + my $class = shift; + my ($name) = @_; + + open F, jailed_file("/etc/passwd") or die "user load failed: $!"; + while () { + chomp; + @_ = split /:+/; + next unless (shift eq $name); + + my $self = $class->_new($name); + + (undef, $self->{uid}, undef, $self->{email}) = @_; + ($self->{keys}, $self->{auth}) = $self->_sshkey_load; + + return $self; + } + close F; + undef; +} + +# $user may not be in sane state if this returns false! +sub cgi_fill { + my $self = shift; + my ($repo) = @_; + my $cgi = $repo->cgi; + + $self->{name} = $repo->wparam('name'); + valid_user_name($self->{name}) + or $repo->err("Name contains invalid characters."); + + $self->{email} = $repo->wparam('email'); + valid_email($self->{email}) + or $repo->err("Your email sure looks weird...?"); + + $self->keys_fill($repo); +} + +sub keys_fill { + my $self = shift; + my ($repo) = @_; + my $cgi = $repo->cgi; + + $self->{keys} = $cgi->param('keys'); + length($self->{keys}) <= 4096 + or $repo->err("The list of keys is more than 4kb. Do you really need that much?"); + foreach (split /\r?\n/, $self->{keys}) { + /^ssh-(?:dss|rsa) .* \S+@\S+$/ or $repo->err("Your ssh key (\"$_\") appears to have invalid format (do not start by ssh-dss|rsa or do not end with @-identifier) - maybe your browser has split a single key to multiple lines?"); + } + + not $repo->err_check; +} + +sub keys_save { + my $self = shift; + + $self->_sshkey_save; +} + +sub gen_auth { + my $self = shift; + + $self->{auth} = Digest::SHA1::sha1_hex(time . $$ . rand() . $self->{keys}); + $self->_sshkey_save; + $self->{auth}; +} + +sub del_auth { + my $self = shift; + + delete $self->{auth}; +} + +sub conjure { + my $self = shift; + + $self->_passwd_add; + $self->_sshkey_save; +} + +# static method +sub does_exist { + my ($name) = @_; + valid_user_name($name) or die "tried to query for user with invalid name $name!"; + (-e jailed_file("/etc/sshkeys/$name")); +} +sub available { + does_exist(@_); +} + + +1; diff --git a/cgi/Girocco b/cgi/Girocco new file mode 120000 index 0000000..a96aa0e --- /dev/null +++ b/cgi/Girocco @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/cgi/Git/RepoCGI.pm b/cgi/Git/RepoCGI.pm deleted file mode 100644 index 017ee0e..0000000 --- a/cgi/Git/RepoCGI.pm +++ /dev/null @@ -1,793 +0,0 @@ -package Git::RepoCGI; - -use strict; -use warnings; - -$Git::RepoCGI::basedir = '/home/repo/repomgr'; # path to the Girocco files (checkout of this project) -$Git::RepoCGI::mqueuedir = '/home/repo/repodata'; # path to the directory of the mirror queue -$Git::RepoCGI::chroot = "/home/repo/j"; # ssh push chroot -$Git::RepoCGI::reporoot = "/srv/git"; # repository collection -$Git::RepoCGI::name = "repo.or.cz"; # title of the service -$Git::RepoCGI::gitweburl = "http://repo.or.cz/w"; # URL of gitweb (pathinfo mode) -$Git::RepoCGI::webadmurl = "http://repo.or.cz/m"; # URL of the 'repo' CGI web admin interface -$Git::RepoCGI::httppullurl = "http://repo.or.cz/r"; # HTTP URL of the repository collection -$Git::RepoCGI::gitpullurl = "git://repo.or.cz/"; # Git URL of the repository collection -$Git::RepoCGI::pushurl = "ssh://repo.or.cz/srv/git"; # Pushy URL of the repository collection -$Git::RepoCGI::jurisdiction = "Czech Republic"; # legal jurisdiction of the site -$Git::RepoCGI::giroccourl = "$Git::RepoCGI::gitweburl/repo.git"; # URL of gitweb of this Girocco instance (set as undef if you're not nice to the community) -$Git::RepoCGI::mob = "mob"; # set to undef to disable the special 'mob' user -$Git::RepoCGI::moburl = "/mob.html"; # URL of the explanation of the mob user -$Git::RepoCGI::mirror = 1; # enable mirroring mode -$Git::RepoCGI::push = 1; # enable push mode -$Git::RepoCGI::group = 'repo'; # UNIX group owning the repositories -$Git::RepoCGI::git_bin = '/home/pasky/bin/git'; # path to Git binary to use - -### Administrativa - -BEGIN { - our $VERSION = '0.1'; - our @ISA = qw(Exporter); - our @EXPORT = qw(scrypt html_esc jailed_file - lock_file unlock_file - filedb_atomic_append filedb_atomic_edit - proj_get_forkee_name proj_get_forkee_path - valid_proj_name valid_user_name valid_email valid_repo_url valid_web_url); - - use CGI qw(:standard :escapeHTML -nosticky); - use CGI::Util qw(unescape); - use CGI::Carp qw(fatalsToBrowser); - use Digest::SHA1 qw(sha1_hex); -} - - -### RepoCGI object - -sub new { - my $class = shift; - my ($heading) = @_; - my $repo = {}; - - $repo->{cgi} = CGI->new; - - print $repo->{cgi}->header(-type=>'text/html', -charset => 'utf-8'); - - print < - - - - -$Git::RepoCGI::name :: $heading - - - - - - -

- -EOT - - bless $repo, $class; -} - -sub DESTROY { - my $self = shift; - my $cgi = $self->cgi; - my $cgiurl = $cgi->url(-absolute => 1); - my ($cginame) = ($cgiurl =~ m#^.*/\([a-zA-Z0-9_.\/-]+?\.cgi\)$#); # - if ($cginame and $Git::RepoCGI::giroccourl) { - print < -(view source) - -EOT - } - print < - -EOT -} - -sub cgi { - my $self = shift; - $self->{cgi}; -} - -sub err { - my $self = shift; - print "

@_

\n"; - $self->{err}++; -} - -sub err_check { - my $self = shift; - my $err = $self->{err}; - $err and print "

Operation aborted due to $err errors.

\n"; - $err; -} - -sub wparam { - my $self = shift; - my ($param) = @_; - my $val = $self->{cgi}->param($param); - $val =~ s/^\s*(.*?)\s*$/$1/; - $val; -} - - -### Random utility functions - -sub scrypt { - my ($pwd) = @_; - crypt($pwd, join ('', ('.', '/', 2..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64])); -} - -sub html_esc { - my ($str) = @_; - $str =~ s/&/&/g; - $str =~ s//>/g; - $str =~ s/"/"/g; - $str; -} - -sub jailed_file { - my ($filename) = @_; - $Git::RepoCGI::chroot."/$filename"; -} - -sub lock_file { - my ($path) = @_; - - $path .= '.lock'; - - use Errno qw(EEXIST); - use Fcntl qw(O_WRONLY O_CREAT O_EXCL); - use IO::Handle; - my $handle = new IO::Handle; - - unless (sysopen($handle, $path, O_WRONLY|O_CREAT|O_EXCL)) { - my $cnt = 0; - while (not sysopen($handle, $path, O_WRONLY|O_CREAT|O_EXCL)) { - ($! == EEXIST) or die "$path open failed: $!"; - ($cnt++ < 16) or die "$path open failed: cannot open lockfile"; - sleep(1); - } - } - # XXX: filedb-specific - chmod 0664, $path or die "$path g+w failed: $!"; - - $handle; -} - -sub unlock_file { - my ($path) = @_; - - rename "$path.lock", $path or die "$path unlock failed: $!"; -} - -sub filedb_atomic_append { - my ($file, $line) = @_; - my $id = 65536; - - open my $src, $file or die "$file open for reading failed: $!"; - my $dst = lock_file($file); - - while (<$src>) { - my $aid = (split /:/)[2]; - $id = $aid + 1 if ($aid >= $id); - - print $dst $_ or die "$file(l) write failed: $!"; - } - - $line =~ s/\\i/$id/g; - print $dst "$line\n" or die "$file(l) write failed: $!"; - - close $dst or die "$file(l) close failed: $!"; - close $src; - - unlock_file($file); - - $id; -} - -sub filedb_atomic_edit { - my ($file, $fn) = @_; - - open my $src, $file or die "$file open for reading failed: $!"; - my $dst = lock_file($file); - - while (<$src>) { - print $dst $fn->($_) or die "$file(l) write failed: $!"; - } - - close $dst or die "$file(l) close failed: $!"; - close $src; - - unlock_file($file); -} - -sub proj_get_forkee_name { - $_ = $_[0]; - (m#^(.*)/.*?$#)[0]; -} -sub proj_get_forkee_path { - my $forkee = $Git::RepoCGI::reporoot.'/'.proj_get_forkee_name($_[0]).'.git'; - -d $forkee ? $forkee : ''; -} -sub valid_proj_name { - $_ = $_[0]; - (not m#/# or -d proj_get_forkee_path($_)) # will also catch ^/ - and (not m#\./#) - and (not m#/$#) - and m#^[a-zA-Z0-9+./_-]+$#; -} -sub valid_user_name { - $_ = $_[0]; - /^[a-zA-Z0-9+._-]+$/; -} -sub valid_email { - $_ = $_[0]; - /^[a-zA-Z0-9+._-]+@[a-zA-Z0-9-.]+$/; -} -sub valid_web_url { - $_ = $_[0]; - /^http:\/\/[a-zA-Z0-9-.]+(\/[_\%a-zA-Z0-9.\/~-]*)?(#[a-zA-Z0-9._-]+)?$/; -} -sub valid_repo_url { - $_ = $_[0]; - /^http:\/\/[a-zA-Z0-9-.]+(\/[_\%a-zA-Z0-9.\/~-]*)?$/ or - /^git:\/\/[a-zA-Z0-9-.]+(\/[_\%a-zA-Z0-9.\/~-]*)?$/; -} - - -### Project object - -package Git::RepoCGI::Project; - -BEGIN { use Git::RepoCGI; } - -sub _mkdir_forkees { - my $self = shift; - my @pelems = split('/', $self->{name}); - pop @pelems; # do not create dir for the project itself - my $path = $self->{base_path}; - foreach my $pelem (@pelems) { - $path .= "/$pelem"; - (-d "$path") or mkdir $path or die "mkdir $path: $!"; - chmod 0775, $path; # ok if fails (dir may already exist and be owned by someone else) - } -} - -our %propmap = ( - url => 'base_url', - email => 'owner', - desc => 'description', - README => 'README.html', - hp => 'homepage', -); - -sub _property_path { - my $self = shift; - my ($name) = @_; - $self->{path}.'/'.$name; -} - -sub _property_fget { - my $self = shift; - my ($name) = @_; - $propmap{$name} or die "unknown property: $name"; - open P, $self->_property_path($propmap{$name}) or return undef; - my @value =

; - close P; - my $value = join('', @value); chomp $value; - $value; -} - -sub _property_fput { - my $self = shift; - my ($name, $value) = @_; - $propmap{$name} or die "unknown property: $name"; - - my $P = lock_file($self->_property_path($propmap{$name})); - $value ne '' and print $P "$value\n"; - close $P; - unlock_file($self->_property_path($propmap{$name})); -} - -sub _properties_load { - my $self = shift; - foreach my $prop (keys %propmap) { - $self->{$prop} = $self->_property_fget($prop); - } -} - -sub _properties_save { - my $self = shift; - foreach my $prop (keys %propmap) { - $self->_property_fput($prop, $self->{$prop}); - } -} - -sub _nofetch_path { - my $self = shift; - $self->_property_path('.nofetch'); -} - -sub _nofetch { - my $self = shift; - my ($nofetch) = @_; - my $np = $self->_nofetch_path; - if ($nofetch) { - open X, '>'.$np or die "nofetch failed: $!"; - close X; - } else { - unlink $np or die "yesfetch failed: $!"; - } -} - -sub _alternates_setup { - my $self = shift; - return unless $self->{name} =~ m#/#; - my $forkee_name = proj_get_forkee_name($self->{name}); - my $forkee_path = proj_get_forkee_path($self->{name}); - return unless -d $forkee_path; - mkdir $self->{path}.'/refs'; chmod 0775, $self->{path}.'/refs'; - mkdir $self->{path}.'/objects'; chmod 0775, $self->{path}.'/objects'; - mkdir $self->{path}.'/objects/info'; chmod 0775, $self->{path}.'/objects/info'; - - # We set up both alternates and http_alternates since we cannot use - # relative path in alternates - that doesn't work recursively. - - my $filename = $self->{path}.'/objects/info/alternates'; - open X, '>'.$filename or die "alternates failed: $!"; - print X "$forkee_path/objects\n"; - close X; - chmod 0664, $filename or warn "cannot chmod $filename: $!"; - - if ($Git::RepoCGI::httppullurl) { - $filename = $self->{path}.'/objects/info/http-alternates'; - open X, '>'.$filename or die "http-alternates failed: $!"; - my $upfork = $forkee_name; - do { print X "$Git::RepoCGI::httppullurl/$upfork.git/objects\n"; } while ($upfork =~ s#/?.+?$## and $upfork); - close X; - chmod 0664, $filename or warn "cannot chmod $filename: $!"; - } - - symlink "$forkee_path/refs", $self->{path}.'/refs/forkee'; -} - -sub _ctags_setup { - my $self = shift; - mkdir $self->{path}.'/ctags'; chmod 0775, $self->{path}.'/ctags'; -} - -sub _group_add { - my $self = shift; - my ($xtra) = @_; - $xtra .= join(',', @{$self->{users}}); - filedb_atomic_append(jailed_file('/etc/group'), - join(':', $self->{name}, $self->{crypt}, '\i', $xtra)); -} - -sub _group_update { - my $self = shift; - my $xtra = join(',', @{$self->{users}}); - filedb_atomic_edit(jailed_file('/etc/group'), - sub { - $_ = $_[0]; - chomp; - if ($self->{name} eq (split /:/)[0]) { - # preserve readonly flag - s/::([^:]*)$/:$1/ and $xtra = ":$xtra"; - return join(':', $self->{name}, $self->{crypt}, $self->{gid}, $xtra)."\n"; - } else { - return "$_\n"; - } - } - ); -} - -sub _group_remove { - my $self = shift; - filedb_atomic_edit(jailed_file('/etc/group'), - sub { - $self->{name} ne (split /:/)[0] and return $_; - } - ); -} - -sub _hook_path { - my $self = shift; - my ($name) = @_; - $self->{path}.'/hooks/'.$name; -} - -sub _hook_install { - my $self = shift; - my ($name) = @_; - open SRC, "$Git::RepoCGI::basedir/$name-hook" or die "cannot open hook $name: $!"; - open DST, '>'.$self->_hook_path($name) or die "cannot open hook $name for writing: $!"; - while () { print DST $_; } - close DST; - close SRC; - chmod 0775, $self->_hook_path($name) or die "cannot chmod hook $name: $!"; -} - -sub _hooks_install { - my $self = shift; - foreach my $hook ('update') { - $self->_hook_install($hook); - } -} - -# private constructor, do not use -sub _new { - my $class = shift; - my ($name, $base_path, $path) = @_; - valid_proj_name($name) or die "refusing to create project with invalid name ($name)!"; - $path ||= "$base_path/$name.git"; - my $proj = { name => $name, base_path => $base_path, path => $path }; - - bless $proj, $class; -} - -# public constructor #0 -# creates a virtual project not connected to disk image -# you can conjure() it later to disk -sub ghost { - my $class = shift; - my ($name, $mirror) = @_; - my $self = $class->_new($name, $mirror ? "$Git::RepoCGI::mqueuedir/to-clone" : $Git::RepoCGI::reporoot, - $mirror ? "$Git::RepoCGI::mqueuedir/to-clone/$name" : $Git::RepoCGI::reporoot."/$name.git"); - $self->{users} = []; - $self->{mirror} = $mirror; - $self; -} - -# public constructor #1 -sub load { - my $class = shift; - my ($name) = @_; - - open F, jailed_file("/etc/group") or die "project load failed: $!"; - while () { - chomp; - @_ = split /:+/; - next unless (shift eq $name); - - my $self = $class->_new($name, $Git::RepoCGI::reporoot); - (-d $self->{path}) or die "invalid path (".$self->{path}.") for project ".$self->{name}; - - my $ulist; - ($self->{crypt}, $self->{gid}, $ulist) = @_; - $ulist ||= ''; - $self->{users} = [split /,/, $ulist]; - $self->{mirror} = ! -e $self->_nofetch_path; - $self->{ccrypt} = $self->{crypt}; - - $self->_properties_load; - return $self; - } - close F; - undef; -} - -# $proj may not be in sane state if this returns false! -sub cgi_fill { - my $self = shift; - my ($repo) = @_; - my $cgi = $repo->cgi; - - my $pwd = $cgi->param('pwd'); - if ($pwd ne '' or not $self->{crypt}) { - $self->{crypt} = scrypt($pwd); - } - - if ($cgi->param('pwd2') and $pwd ne $cgi->param('pwd2')) { - $repo->err("Our high-paid security consultants have determined that the admin passwords you have entered do not match each other."); - } - - $self->{cpwd} = $cgi->param('cpwd'); - - $self->{email} = $repo->wparam('email'); - valid_email($self->{email}) - or $repo->err("Your email sure looks weird...?"); - - $self->{url} = $repo->wparam('url'); - if ($self->{url}) { - valid_repo_url($self->{url}) - or $repo->err("Invalid URL. Note that only HTTP and Git protocol is supported. If the URL contains funny characters, contact me."); - } - - $self->{desc} = $repo->wparam('desc'); - length($self->{desc}) <= 1024 - or $repo->err("Short description length > 1kb!"); - - $self->{README} = $repo->wparam('README'); - length($self->{README}) <= 8192 - or $repo->err("README length > 8kb!"); - - $self->{hp} = $repo->wparam('hp'); - if ($self->{hp}) { - valid_web_url($self->{hp}) - or $repo->err("Invalid homepage URL. Note that only HTTP protocol is supported. If the URL contains funny characters, contact me."); - } - - # FIXME: Permit only existing users - $self->{users} = [grep { valid_user_name($_) } $cgi->param('user')]; - - not $repo->err_check; -} - -sub form_defaults { - my $self = shift; - ( - name => $self->{name}, - email => $self->{email}, - url => $self->{url}, - desc => html_esc($self->{desc}), - README => html_esc($self->{README}), - hp => $self->{hp}, - users => $self->{users}, - ); -} - -sub authenticate { - my $self = shift; - my ($repo) = @_; - - $self->{ccrypt} or die "Can't authenticate against a project with no password"; - $self->{cpwd} or $repo->err("No password entered."); - unless ($self->{ccrypt} eq crypt($self->{cpwd}, $self->{ccrypt})) { - $repo->err("Your admin password does not match!"); - return 0; - } - return 1; -} - -sub premirror { - my $self = shift; - - $self->_mkdir_forkees; - mkdir $self->{path} or die "mkdir failed: $!"; - chmod 0775, $self->{path} or die "chmod failed: $!"; - $self->_properties_save; - $self->_alternates_setup; - $self->_ctags_setup; - $self->_group_add(':'); -} - -sub conjure { - my $self = shift; - - $self->_mkdir_forkees; - - mkdir($self->{path}) or die "mkdir $self->{path} failed: $!"; - my $gid = scalar(getgrnam($Git::RepoCGI::group)); - chown(-1, $gid, $self->{path}) or die "chgrp $gid $self->{path} failed: $!"; - chmod(2775, $self->{path}) or die "chmod 2775 $self->{path} failed: $!"; - system($Git::RepoCGI::git_bin, '--git-dir='.$self->{path}, 'init', '--bare', '--shared=group') - or die "git init $self->{path} failed: $!"; - system($Git::RepoCGI::git_bin, '--git-dir='.$self->{path}, 'config', 'receive.denyNonFastforwards', 'false') - or die "disabling receive.denyNonFastforwards failed: $!"; - - $self->_nofetch(1); - $self->_properties_save; - $self->_alternates_setup; - $self->_ctags_setup; - $self->_group_add; - $self->_hooks_install; -} - -sub update { - my $self = shift; - - $self->_properties_save; - $self->_group_update; -} - -sub update_password { - my $self = shift; - my ($pwd) = @_; - - $self->{crypt} = scrypt($pwd); - $self->_group_update; -} - -# You can explicitly do this just on a ghost() repository too. -sub delete { - my $self = shift; - - if (-d $self->{path}) { - system('rm', '-r', $self->{path}) == 0 - or die "rm -r failed: $?"; - } - $self->_group_remove; -} - -sub has_forks { - my $self = shift; - - return glob($Git::RepoCGI::reporoot.'/'.$self->{name}.'/*'); -} - -# static method -sub does_exist { - my ($name) = @_; - valid_proj_name($name) or die "tried to query for project with invalid name $name!"; - (available($name) - or -d $Git::RepoCGI::mqueuedir."/cloning/$name" - or -d $Git::RepoCGI::mqueuedir."/to-clone/$name"); -} -sub available { - my ($name) = @_; - valid_proj_name($name) or die "tried to query for project with invalid name $name!"; - (-d $Git::RepoCGI::reporoot."/$name.git"); -} - - -### User object - -package Git::RepoCGI::User; - -BEGIN { use Git::RepoCGI; } - -sub _passwd_add { - my $self = shift; - filedb_atomic_append(jailed_file('/etc/passwd'), - join(':', $self->{name}, 'x', '\i', 65534, $self->{email}, '/', '/bin/git-shell')); -} - -sub _sshkey_path { - my $self = shift; - '/etc/sshkeys/'.$self->{name}; -} - -sub _sshkey_load { - my $self = shift; - open F, "<".jailed_file($self->_sshkey_path) or die "sshkey load failed: $!"; - my @keys; - my $auth; - while () { - chomp; - if (/^ssh-(?:dss|rsa) /) { - push @keys, $_; - } elsif (/^# REPOAUTH ([0-9a-f]+) (\d+)/) { - my $expire = $2; - $auth = $1 unless (time >= $expire); - } - } - close F; - my $keys = join('', @keys); chomp $keys; - ($keys, $auth); -} - -sub _sshkey_save { - my $self = shift; - open F, ">".jailed_file($self->_sshkey_path) or die "sshkey failed: $!"; - if (defined($self->{auth}) && $self->{auth}) { - my $expire = time + 24 * 3600; - print F "# REPOAUTH $self->{auth} $expire\n"; - } - print F $self->{keys}."\n"; - close F; - chmod 0664, jailed_file($self->_sshkey_path); -} - -# private constructor, do not use -sub _new { - my $class = shift; - my ($name) = @_; - valid_user_name($name) or die "refusing to create user with invalid name ($name)!"; - my $proj = { name => $name }; - - bless $proj, $class; -} - -# public constructor #0 -# creates a virtual user not connected to disk record -# you can conjure() it later to disk -sub ghost { - my $class = shift; - my ($name) = @_; - my $self = $class->_new($name); - $self; -} - -# public constructor #1 -sub load { - my $class = shift; - my ($name) = @_; - - open F, jailed_file("/etc/passwd") or die "user load failed: $!"; - while () { - chomp; - @_ = split /:+/; - next unless (shift eq $name); - - my $self = $class->_new($name); - - (undef, $self->{uid}, undef, $self->{email}) = @_; - ($self->{keys}, $self->{auth}) = $self->_sshkey_load; - - return $self; - } - close F; - undef; -} - -# $user may not be in sane state if this returns false! -sub cgi_fill { - my $self = shift; - my ($repo) = @_; - my $cgi = $repo->cgi; - - $self->{name} = $repo->wparam('name'); - valid_user_name($self->{name}) - or $repo->err("Name contains invalid characters."); - - $self->{email} = $repo->wparam('email'); - valid_email($self->{email}) - or $repo->err("Your email sure looks weird...?"); - - $self->keys_fill($repo); -} - -sub keys_fill { - my $self = shift; - my ($repo) = @_; - my $cgi = $repo->cgi; - - $self->{keys} = $cgi->param('keys'); - length($self->{keys}) <= 4096 - or $repo->err("The list of keys is more than 4kb. Do you really need that much?"); - foreach (split /\r?\n/, $self->{keys}) { - /^ssh-(?:dss|rsa) .* \S+@\S+$/ or $repo->err("Your ssh key (\"$_\") appears to have invalid format (do not start by ssh-dss|rsa or do not end with @-identifier) - maybe your browser has split a single key to multiple lines?"); - } - - not $repo->err_check; -} - -sub keys_save { - my $self = shift; - - $self->_sshkey_save; -} - -sub gen_auth { - my $self = shift; - - $self->{auth} = Digest::SHA1::sha1_hex(time . $$ . rand() . $self->{keys}); - $self->_sshkey_save; - $self->{auth}; -} - -sub del_auth { - my $self = shift; - - delete $self->{auth}; -} - -sub conjure { - my $self = shift; - - $self->_passwd_add; - $self->_sshkey_save; -} - -# static method -sub does_exist { - my ($name) = @_; - valid_user_name($name) or die "tried to query for user with invalid name $name!"; - (-e jailed_file("/etc/sshkeys/$name")); -} -sub available { - does_exist(@_); -} - - -1; diff --git a/cgi/delproj.cgi b/cgi/delproj.cgi index d50e3a4..20db5e2 100755 --- a/cgi/delproj.cgi +++ b/cgi/delproj.cgi @@ -6,9 +6,9 @@ use strict; use warnings; use lib "."; -use Git::RepoCGI; +use Girocco::CGI; -my $repo = Git::RepoCGI->new('Project Removal'); +my $repo = Girocco::CGI->new('Project Removal'); my $cgi = $repo->cgi; my $name = $cgi->param('name'); @@ -23,17 +23,17 @@ if (!valid_proj_name($name)) { exit; } -if (!Git::RepoCGI::Project::does_exist($name)) { +if (!Girocco::Project::does_exist($name)) { print "

Sorry but this project does not exist. Now, how did you get here?!

\n"; exit; } -if (!Git::RepoCGI::Project::available($name)) { +if (!Girocco::Project::available($name)) { print "

Sorry but your project has not finished mirroring yet. If it takes inordinate amount of time, please tell the administrator.

\n"; exit; } -my $proj = Git::RepoCGI::Project->load($name); +my $proj = Girocco::Project->load($name); $proj or die "not found project $name, that's really weird!"; $proj->{cpwd} = $cgi->param('cpwd'); diff --git a/cgi/editproj.cgi b/cgi/editproj.cgi index 4b5e13b..4d246b2 100755 --- a/cgi/editproj.cgi +++ b/cgi/editproj.cgi @@ -6,9 +6,9 @@ use strict; use warnings; use lib "."; -use Git::RepoCGI; +use Girocco::CGI; -my $repo = Git::RepoCGI->new('Project Settings'); +my $repo = Girocco::CGI->new('Project Settings'); my $cgi = $repo->cgi; my $name = $cgi->param('name'); @@ -24,17 +24,17 @@ if (!valid_proj_name($name)) { exit; } -if (!Git::RepoCGI::Project::does_exist($name)) { +if (!Girocco::Project::does_exist($name)) { print "

Sorry but the project $name does not exist. Now, how did you get here?!

\n"; exit; } -if (!Git::RepoCGI::Project::available($name)) { +if (!Girocco::Project::available($name)) { print "

Sorry but your project has not finished mirroring yet. If it takes inordinate amount of time, please ask the administrator about it.

\n"; exit; } -my $proj = Git::RepoCGI::Project->load($name); +my $proj = Girocco::Project->load($name); $proj or die "not found project $name, that's really weird!"; if ($cgi->param('email')) { @@ -61,7 +61,7 @@ EOT } print < -

Project name: $h{name}.git

+

Project name: $h{name}.git

Admin password: (forgot password?)

New admin password: (leave empty to keep it at the current value)

New admin password (retype):

@@ -75,9 +75,9 @@ print <{mirror}) { print "

Warning: This is a mirrored repository, thus you cannot push into it. Changing the user set will have no practical effect.

\n"; -} elsif ($Git::RepoCGI::mob and not grep { $_ eq $Git::RepoCGI::mob } @{$h{users}}) { - print "

(Please consider adding the $Git::RepoCGI::mob user."; - print "(learn more))" if $Git::RepoCGI::moburl; +} elsif ($Girocco::Config::mob and not grep { $_ eq $Girocco::Config::mob } @{$h{users}}) { + print "

(Please consider adding the $Girocco::Config::mob user."; + print "(learn more))" if $Girocco::Config::moburl; print "

\n"; } foreach my $user (@{$h{users}}) { diff --git a/cgi/edituser.cgi b/cgi/edituser.cgi index 93f75fc..3ac2c2b 100755 --- a/cgi/edituser.cgi +++ b/cgi/edituser.cgi @@ -7,9 +7,9 @@ use strict; use warnings; use lib "."; -use Git::RepoCGI; +use Girocco::CGI; -my $repo = Git::RepoCGI->new('User SSH Key Update'); +my $repo = Girocco::CGI->new('User SSH Key Update'); my $cgi = $repo->cgi; if ($cgi->param('mail')) { @@ -39,12 +39,12 @@ if ($cgi->param('name')) { # FIXME: racy, do a lock my $name = $repo->wparam('name'); !valid_user_name($name) - or !Git::RepoCGI::User::does_exist($name) + or !Girocco::User::does_exist($name) and $repo->err("Username is not registered."); $repo->err_check and exit; - my $user = Git::RepoCGI::User->load($name) or + my $user = Girocco::User->load($name) or die "Failed loading user but this can't really happen here"; if (!$cgi->param('auth')) { diff --git a/cgi/pwproj.cgi b/cgi/pwproj.cgi index c3d1f38..a35ad36 100755 --- a/cgi/pwproj.cgi +++ b/cgi/pwproj.cgi @@ -6,7 +6,7 @@ use strict; use warnings; use lib "."; -use Git::RepoCGI; +use Girocco::CGI; sub genpwd { @@ -15,7 +15,7 @@ sub genpwd { } -my $repo = Git::RepoCGI->new('Forgotten Project Password'); +my $repo = Girocco::CGI->new('Forgotten Project Password'); my $cgi = $repo->cgi; my $name = $cgi->param('name'); @@ -30,17 +30,17 @@ if (!valid_proj_name($name)) { exit; } -if (!Git::RepoCGI::Project::does_exist($name)) { +if (!Girocco::Project::does_exist($name)) { print "

Sorry but this project does not exist. Now, how did you get here?!

\n"; exit; } -if (!Git::RepoCGI::Project::available($name)) { +if (!Girocco::Project::available($name)) { print "

Sorry but your project has not finished mirroring yet. If it takes inordinate amount of time, please tell the administrator.

\n"; exit; } -my $proj = Git::RepoCGI::Project->load($name); +my $proj = Girocco::Project->load($name); $proj or die "not found project $name, that's really weird!"; my $mail = $proj->{email}; @@ -50,7 +50,7 @@ if ($cgi->param('y0')) { my $newpwd = genpwd(); - open (M, '|-', 'mail', '-s', "[$Git::RepoCGI::name] New password for project $name", $mail) or die "Cannot spawn mail: $!"; + open (M, '|-', 'mail', '-s', "[$Girocco::Config::name] New password for project $name", $mail) or die "Cannot spawn mail: $!"; print M <new('Project Registration'); +my $repo = Girocco::CGI->new('Project Registration'); my $cgi = $repo->cgi; my $name = $cgi->param('name'); @@ -24,29 +24,29 @@ if ($cgi->param('mode')) { # FIXME: racy, do a lock my $name = $repo->wparam('name'); valid_proj_name($name) - and Git::RepoCGI::Project::does_exist($name) + and Girocco::Project::does_exist($name) and $repo->err("Project with the name '$name' already exists."); $name =~ /\.git$/ and $repo->err("Project name should not end with .git - I'll add that automagically."); my $mirror = $cgi->param('mode') eq 'mirror'; - my $proj = Git::RepoCGI::Project->ghost($name, $mirror); + my $proj = Girocco::Project->ghost($name, $mirror); if ($proj->cgi_fill($repo)) { if ($mirror) { - $Git::RepoCGI::mirror or $repo->err("Mirroring mode is not enabled at this site."); + $Girocco::Config::mirror or $repo->err("Mirroring mode is not enabled at this site."); $proj->premirror; print "

Initiated mirroring. You will be notified about the result by mail.

\n"; } else { - $Git::RepoCGI::push or $repo->err("Push mode is not enabled at this site."); + $Girocco::Config::push or $repo->err("Push mode is not enabled at this site."); $proj->conjure; print < Project $name successfuly set up.

EOT - print "

The push URL for the project is $Git::RepoCGI::pushurl/$name.git.

"; + print "

The push URL for the project is $Girocco::Config::pushurl/$name.git.

"; print "

The read-only URL for the project is " . - join("/$name.git, ", $Git::RepoCGI::gitpullurl, $Git::RepoCGI::httppullurl) . - "/$name.git.

" if $Git::RepoCGI::gitpullurl or $Git::RepoCGI::httppullurl; + join("/$name.git, ", $Girocco::Config::gitpullurl, $Girocco::Config::httppullurl) . + "/$name.git.

" if $Girocco::Config::gitpullurl or $Girocco::Config::httppullurl; print <You can assign users now (use project name (without .git) as username, admin password as password) - don't forget to assign yourself as a user as well if you want to push! @@ -74,8 +74,8 @@ my $push_mode = { pwpurp => 'list of users allowed to push' }; -my $me = $Git::RepoCGI::mirror ? $mirror_mode : undef; -my ($me, $pe) = $Git::RepoCGI::push ? $push_mode : undef; +my $me = $Girocco::Config::mirror ? $mirror_mode : undef; +my ($me, $pe) = $Girocco::Config::push ? $push_mode : undef; if ($me and $pe) { print <At this site, you can host a project in one of two modes: $me->{name} mode and $pe->{name} mode. @@ -139,7 +139,7 @@ EOT print <By submitting this form, you are confirming that the repository contains only free software -and redistributing it does not violate any law of $Git::RepoCGI::jurisdiction. Read more details +and redistributing it does not violate any law of $Girocco::Config::jurisdiction. Read more details about the hosting and terms and conditions.

Have fun!

diff --git a/cgi/reguser.cgi b/cgi/reguser.cgi index 25123cf..fb81077 100755 --- a/cgi/reguser.cgi +++ b/cgi/reguser.cgi @@ -6,9 +6,9 @@ use strict; use warnings; use lib "."; -use Git::RepoCGI; +use Girocco::CGI; -my $repo = Git::RepoCGI->new('User Registration'); +my $repo = Girocco::CGI->new('User Registration'); my $cgi = $repo->cgi; if ($cgi->param('mail')) { @@ -21,10 +21,10 @@ if ($cgi->param('name')) { # FIXME: racy, do a lock my $name = $repo->wparam('name'); valid_user_name($name) - and Git::RepoCGI::User::does_exist($name) + and Girocco::User::does_exist($name) and $repo->err("User with that name already exists."); - my $user = Git::RepoCGI::User->ghost($name); + my $user = Girocco::User->ghost($name); if ($user->cgi_fill($repo)) { $user->conjure; print <

By submitting this form, you are confirming that you will push only free software -and no content that would violate any law of $Git::RepoCGI::jurisdiction. Have fun!

+and no content that would violate any law of $Girocco::Config::jurisdiction. Have fun!

Login:

Email:

diff --git a/cgi/tagproj.cgi b/cgi/tagproj.cgi index 9b65309..d890e73 100755 --- a/cgi/tagproj.cgi +++ b/cgi/tagproj.cgi @@ -6,7 +6,7 @@ use strict; use warnings; use lib "."; -use Git::RepoCGI; +use Girocco::CGI; use CGI; our $cgi = CGI->new; @@ -14,7 +14,7 @@ our $cgi = CGI->new; my $pname = $cgi->param('p'); my $ctags = $cgi->param('t'); -my $proj = Git::RepoCGI::Project->load($pname); +my $proj = Girocco::Project->load($pname); if (not $proj) { print $cgi->header(-status=>404); print "Project $pname does not exist."; @@ -35,4 +35,4 @@ foreach my $ctag (split(/ /, $ctags)) { open CT, '>'.$proj->{path}."/ctags/$ctag" and print CT ($val+1)."\n" and close CT; } -print $cgi->header(-status=>303, -location=>"$Git::RepoCGI::gitweburl/$pname.git"); +print $cgi->header(-status=>303, -location=>"$Girocco::Config::gitweburl/$pname.git"); diff --git a/clone.sh b/clone.sh index b801010..f00ba2f 100755 --- a/clone.sh +++ b/clone.sh @@ -21,7 +21,7 @@ bang() { # This removes any remnants from /srv/git/$proj.git _and_ gets rid # of the group record. - perl -I/home/repo/repomgr/cgi -MGit::RepoCGI -e 'Git::RepoCGI::Project->ghost('"'$proj'"')->delete;' + perl -I/home/repo/repomgr/cgi -MGirocco::Project -e 'Girocco::Project->ghost('"'$proj'"')->delete;' cd "$basedir" && rm -rf "$proj"/* && rmdir -p "$proj" rm "$log" exit 1 diff --git a/inactive b/inactive index c807c49..e0c38cd 100644 --- a/inactive +++ b/inactive @@ -7,4 +7,4 @@ while read x y; do echo -e 'Hello,\n\nI have removed your project '$x' from repo #for i in $(find . -name heads -type d); do [ "$(ls $i)" ] && continue; mv ${i%/refs/heads} /srv/git-recyclebin/; done while read x y; do f="${x%/*}"; x="${x##*/}"; [ "$f" != "$x" ] || f=""; echo "$f .. $x"; mkdir -p /srv/git-recyclebin/$f; mv /srv/git/$f/$x /srv/git-recyclebin/$f; done ghost("'$j'", 0)->_group_remove()'; done +for i in `cut -d ' ' -f 1 /srv/git/sack`; do echo $i; j=${i#./}; j=${j%.git}; perl -Icgi -MGirocco::Project -e 'Girocco::Project->ghost("'$j'", 0)->_group_remove()'; done -- 2.11.4.GIT