various: add read-only mode support
authorKyle J. McKay <[email protected]>
Tue, 21 Jun 2022 19:12:19 +0000 (21 12:12 -0700)
committerKyle J. McKay <[email protected]>
Tue, 21 Jun 2022 19:12:19 +0000 (21 12:12 -0700)
By creating a file named "readonly" located at
$Girocco::Config::chroot/etc/readonly, external pushes and project
modifications via the web UI will be prohibited.

Modifications via local tools such as projtool or usertool are NOT
prohibited by this read-only mode, nor does this mode stop jobd or
taskd from running.

The text on the home page of the UI may be automatically altered
as well provided an alternate version of the index text file has
been made available via the gitweb_config.perl $home_text_ro setting.

If the $Girocco::Config::chroot/etc/readonly file is not empty, its
contents will also be included in the messages denying any changes.
Except for the home page, which will only show a change if $home_text_ro
points at an alternate file.

14 files changed:
Girocco/Util.pm
bin/git-http-backend-verify
bin/git-shell-verify
cgi/delproj.cgi
cgi/deluser.cgi
cgi/editproj.cgi
cgi/edituser.cgi
cgi/mirrorproj.cgi
cgi/pwproj.cgi
cgi/regproj.cgi
cgi/reguser.cgi
cgi/tagproj.cgi
gitweb/gitweb_config.perl
install.sh

index 4039a90..4c0afcc 100644 (file)
@@ -31,7 +31,7 @@ BEGIN {
                         json_bool from_json ref_indicator get_token_key
                         get_timed_token get_token_field check_timed_token
                         valid_branch_name get_project_from_dir
-                        get_git_chomp);
+                        get_git_chomp check_readonly is_readonly);
 }
 
 BEGIN {require "Girocco/extra/capture_command.pl"}
@@ -1085,6 +1085,32 @@ sub is_git_dir {
        return $hv =~ /^[0-9a-f]{40}/;
 }
 
+# quick check that only tests for the existence of the file but
+# does not attempt to actually open it and read the contents
+sub is_readonly {
+       return -f "$Girocco::Config::chroot/etc/readonly" ? 1 : 0;
+}
+
+# return string containing read-only message if in read-only mode
+# otherwise return empty string
+# if the first argument is true, include a <br /> tag before the second
+# but only if there is a second line
+sub check_readonly {
+       my $brtag = $_[0] ? "<br />" : "";
+       my $msg = "";
+       if (-f "$Girocco::Config::chroot/etc/readonly") {
+               $msg = "$Girocco::Config::name is currently read-only, please try again later.";
+               my $romsg = "";
+               if (open my $fd, '<', "$Girocco::Config::chroot/etc/readonly") {
+                       local $/;
+                       $romsg = <$fd>;
+                       close $fd;
+               }
+               $romsg ne "" and $msg = $msg . "$brtag\n" . $romsg;
+       }
+       return $msg;
+}
+
 # Returns a PATH properly prefixed which guarantees that Git is found and the
 # basedir/bin utilities are found as intended.  $ENV{PATH} is LEFT UNCHANGED!
 # Caller is responsible for assigning result to $ENV{PATH} or otherwise
index ce0a4b8..67a5c07 100755 (executable)
@@ -377,6 +377,19 @@ if ! [ -f "$dir/.nofetch" ]; then
        exit 1
 fi
 
+if [ -f "$cfg_chroot/etc/readonly" ]; then
+       msgro="$cfg_name is currently read-only, please try again later."
+       msgro2=""
+       if [ -s "$cfg_chroot/etc/readonly" ]; then
+               msgro2="$(cat "$cfg_chroot/etc/readonly" 2>/dev/null)" || :
+       fi
+       if [ -n "$msgro2" ]; then
+               forbidden "$msgro" "$msgro2"
+       else
+               forbidden "$msgro"
+       fi
+fi
+
 # Set up the correct backend command depending on cfg_max_file_size512
 if [ "${cfg_max_file_size512:-0}" = "0" ]; then
        GIT_HTTP_BACKEND='"$cfg_git_http_backend_bin"'
index ef0d7bd..76180a0 100755 (executable)
@@ -33,12 +33,14 @@ if ! [ -x @perlbin@ ]; then
        XDG_CONFIG_HOME=/var/empty
        HOME=/etc/girocco
        GIT_ASKPASS=/bin/git-askpass-password
+       rofile=/etc/readonly
 else
        # We are NOT INSIDE the chroot
        reporoot=@reporoot@
        XDG_CONFIG_HOME=@chroot@/var/empty
        HOME=@chroot@/etc/girocco
        GIT_ASKPASS=@basedir@/bin/git-askpass-password
+       rofile=@chroot@/etc/readonly
 fi
 mob=@mob@
 webadmurl=@webadmurl@
@@ -50,6 +52,7 @@ var_upload_window=@upload_pack_window@
 cfg_fetch_stash_refs=@fetch_stash_refs@
 cfg_suppress_git_ssh_logging=@suppress_git_ssh_logging@
 cfg_max_file_size512=@max_file_size512@
+cfg_name=@cfg_name@
 
 export XDG_CONFIG_HOME
 export HOME
@@ -247,6 +250,17 @@ if [ "$type" = 'receive-pack' ] && ! [ -f "$dir/.nofetch" ]; then
        exit 3
 fi
 
+if [ -f "$rofile" ]; then
+       msgro="$cfg_name is currently read-only, please try again later."
+       msgro2=""
+       if [ -s "$rofile" ]; then
+               msgro2="$(cat "$rofile" 2>/dev/null)" || :
+       fi
+       echo "$msgro" >&2
+       [ -z "$msgro2" ] || echo "$msgro2" >&2
+       exit 3
+fi
+
 GIT_SHELL='git-shell'
 if [ "$type" = 'receive-pack' ]; then
        git_add_config 'receive.unpackLimit=1'
index f7dec4f..ccfc0ce 100755 (executable)
@@ -31,6 +31,11 @@ if (!Girocco::Project::does_exist($name,1)) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $proj = Girocco::Project->load($name);
 if (!$proj) {
        print "<p>not found project $name, that's really weird!</p>\n";
index beaa452..ac78d28 100755 (executable)
@@ -31,6 +31,11 @@ if ($cgi->param('mail')) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 sub _auth_form {
        my ($name, $submit) = @_;
        print <<EOT;
index 22b2499..fc270c6 100755 (executable)
@@ -49,6 +49,11 @@ if (!Girocco::Project::does_exist($name,1)) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $proj = Girocco::Project->load($name);
 if (!$proj) {
        print "<p>not found project $name, that's really weird!</p>\n";
index d303edb..d738570 100755 (executable)
@@ -25,6 +25,11 @@ if ($cgi->param('mail')) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 sub _auth_form {
        my $name = shift;
        my $submit = shift;
index 4b1062d..b949e32 100755 (executable)
@@ -32,6 +32,11 @@ if (!Girocco::Project::does_exist($name,1)) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $proj = Girocco::Project->load($name);
 if (!$proj) {
        print "<p>not found project $name, that's really weird!</p>\n";
index 72cc5a0..f7676ee 100755 (executable)
@@ -37,6 +37,11 @@ if (!Girocco::Project::does_exist($name,1)) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $proj = Girocco::Project->load($name);
 if (!$proj) {
        print "<p>not found project $name, that's really weird!</p>\n";
index 60965ae..9b58798 100755 (executable)
@@ -14,6 +14,11 @@ use Girocco::Util;
 my $gcgi = Girocco::CGI->new('Project Registration');
 my $cgi = $gcgi->cgi;
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $name = $cgi->param('name');
 defined($name) or $name = '';
 
index 81d91b4..98989a5 100755 (executable)
@@ -24,6 +24,11 @@ if ($cgi->param('mail')) {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $y0 = $cgi->param('y0') || '';
 if ($cgi->param('name') && $y0 eq 'Register' && $cgi->request_method eq 'POST') {
        # submitted, let's see
index 7f6bdaf..2a7b5db 100755 (executable)
@@ -27,6 +27,12 @@ if ($cgi->request_method ne 'POST' || $pname eq '') {
        exit;
 }
 
+if (my $romsg=check_readonly(1)) {
+       print $cgi->header(-status=>403);
+       print "<p>$romsg</p>\n";
+       exit;
+}
+
 my $proj = Girocco::Project::does_exist($pname, 1) && Girocco::Project->load($pname);
 if (not $proj) {
        print $cgi->header(-status=>404);
index c2ae41e..a39041e 100644 (file)
@@ -1,7 +1,7 @@
 # Pull Girocco config
 use lib "__BASEDIR__";
 use Girocco::Config;
-use Girocco::Util qw(url_path git_add_config);
+use Girocco::Util qw(url_path git_add_config is_readonly);
 use Digest::MD5 qw(md5_hex);
 BEGIN {
        eval { require HTML::Email::Obfuscate; 1 } or
@@ -174,6 +174,12 @@ our $site_name = $Girocco::Config::title;
 ## html text to include at home page
 our $home_text = "$Girocco::Config::basedir/gitweb/indextext.html";
 
+## read-only version of text, but only when in read-only mode and this exists
+## note that when running in a fastcgi mode, changes to read-only mode will
+## not show up with regards to this text until the next fastcgi instance
+## spawns -- use a "graceful" web server restart to force an immediate effect
+our $home_text_ro = "$Girocco::Config::basedir/gitweb/indextext_readonly.html";
+
 ## URI of stylesheets
 our @stylesheets = ("@{[url_path($Girocco::Config::gitwebfiles)]}/gitweb.css");
 
@@ -251,6 +257,10 @@ $feature{'actions'}{'default'}=[
 # have already been completed by now it's safe to "cd /" at this point.
 chdir "/";
 
+# If in read-only mode and $home_text_ro exists, set $home_text to it
+defined($home_text_ro) && $home_text_ro ne "" && is_readonly() && -f $home_text_ro and
+       $home_text = $home_text_ro;
+
 # Stuff extra Git configuration options into GIT_CONFIG_PARAMETERS
 # This mirrors what shlib.sh does (mostly)
 # Only the options that are appropriate for gitweb are included here
index 7073586..842a361 100755 (executable)
@@ -1058,8 +1058,15 @@ rm -rf "$basedir/cgi"
 [ -z "$webreporoot" ] || { rm -f "$webreporoot" && ln -s "$cfg_reporoot" "$webreporoot"; }
 if [ -z "$cfg_httpspushurl" ] || [ -n "$cfg_pretrustedroot" ]; then
        grep -v 'rootcert[.]html' gitweb/indextext.html >"$basedir/gitweb/indextext.html"
+       if [ -f gitweb/indextext_readonly.html ]; then
+               grep -v 'rootcert[.]html' gitweb/indextext_readonly.html \
+                       >"$basedir/gitweb/indextext_readonly.html"
+       fi
 else
        cp gitweb/indextext.html "$basedir/gitweb"
+       if [ -f gitweb/indextext_readonly.html ]; then
+               cp gitweb/indextext_readonly.html "$basedir/gitweb"
+       fi
 fi
 mv "$basedir"/html/*.css "$basedir"/html/*.js "$webroot"
 cp mootools.js "$webroot"