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 =~ 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 =~ 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!