FrontEnd GNU/NONGNU host redirection

Savannah handles three types of hosts redirection:

  1. Shortcut URL to full URL (e.g. *.sv.gnu.org => *.savannah.gnu.org).
  2. *.gnu.org <=> *.nongnu.org, depending on group type.
  3. automatic-login to both savannah.gnu.org and savannah.nongnu.org
  4. attachment requests from old URLs to dedicated domain (savannah.gnu.org/file/* => file.savannah.gnu.org/file/*, savannah.gnu.org/submission-uploads/ => file.savannah.gnu.org/submission-uploads/) and non-file requests from file.savannah.*.org to the original domain.

Shortcut URL redirection

Shortcurt URL redirection (.e.g *.sv.gnu.org => *.savannah.gnu.org) is handled entirely in the Apache configuration.

This would've been simple, if it were't for two complications:

  1. For HTTP, all URLs should be redirected as-is with full URI (e.g. http://sv.gnu.org/p/coreutils should redirect to http://savannah.gnu.org/p/coreutils, not just to http://savannah.gnu.org) EXCEPT the .well-known directory, which must not be redirected because of the use of Let's-Encrypt certboot (i.e. http://sv.gnu.org/.well-known/XXX must be valid and not redirected). See https for details about the certbot configuration.

  2. For HTTPS, slightly different configuration is set needed on whether it's gnu or nongnu domain, while still requiring that https://sv.gnu.org/p/coreutils redirects to the full https://savannah.gnu.org/p/coreutils.

The FrontEndDevelopmentSite currently uses the following method:

<VirtualHost *:80>
   ServerName   jsmith.frontend0.savannah.gnu.org
   ServerAlias  jsmith.frontend0.savannah.nongnu.org
   ServerAlias  jsmith.frontend0.sv.gnu.org
   ServerAlias  jsmith.frontend0.sv.nongnu.org

   <If "%{HTTP_HOST} == 'jsmith.frontend0.savannah.gnu.org' && %{REQUEST_URI} !~ /\.well-known/" >
    RedirectMatch "(.*)" https://jsmith.frontend0.savannah.gnu.org$1
   </If>
   <ElseIf "%{HTTP_HOST} == 'jsmith.frontend0.savannah.gnu.org' && %{REQUEST_URI} !~ /\.well-known/" >
    RedirectMatch "(.*)" https://jsmith.frontend0.savannah.nongnu.org$1
   </ElseIf>
   <ElseIf "%{HTTP_HOST} == 'jsmith.frontend0.sv.gnu.org' && %{REQUEST_URI} !~ /\.well-known/" >
    RedirectMatch "(.*)" https://jsmith.frontend0.savannah.gnu.org$1
   </ElseIf>
   <ElseIf "%{HTTP_HOST} == 'jsmith.frontend0.sv.nongnu.org' && %{REQUEST_URI} !~ /\.well-known/" >
    RedirectMatch "(.*)" https://jsmith.frontend0.savannah.nongnu.org$1
   </ElseIf>
   Include sites-available/certbot-wellknown.inc
</VirtualHost>


<VirtualHost *:443>
  ServerAlias jsmith.frontend0.sv.gnu.org
  ServerAlias jsmith.frontend0.sv.nongnu.org
  ServerName  jsmith.frontend0.savannah.gnu.org
  ServerAlias jsmith.frontend0.savannah.nongnu.org

  <If "%{HTTP_HOST} == 'jsmith.frontend0.savannah.gnu.org'">
    SetEnv SAVANE_CONF /home/jsmith/savannah/etc/savane/
  </If>
  <ElseIf "%{HTTP_HOST} == 'jsmith.frontend0.savannah.nongnu.org'">
    SetEnv SAVANE_CONF /home/jsmith/savannah/etc/savane/nongnu-conf/
  </ElseIf>
  # If the '.sv.gnu.org' shortcut is used, redirect to the full
  # '.savannah.gnu.org' URL.
  <ElseIf "%{HTTP_HOST} == 'jsmith.frontend0.sv.gnu.org'">
    RedirectMatch "(.*)" https://jsmith.frontend0.savannah.gnu.org$1
  </ElseIf>
  <ElseIf "%{HTTP_HOST} == 'jsmith.frontend0.sv.nongnu.org'">
    RedirectMatch "(.*)" https://jsmith.frontend0.savannah.nongnu.org$1
  </ElseIf>
  [...]
</VirtualHost>

Note that certbot's configuration uses Alias, which has lower precedence in Apache than RedirectMatch - requiring some fiddling with the conditions.

FIXME: If there's a better configuration, please write to savannah-hackers-public@gnu.org .

FIXME: Document the Apache setup for the production website, which use:

 sites-enabled/000-vhosts-gnu.org.conf -> ../sites-available/vhosts-gnu.org
 sites-enabled/vhosts-nongnu.org.conf -> ../sites-available/vhosts-nongnu.org

 # in sites-available/vhosts-gnu.org:
 <VirtualHost *:80>
   Include sites-available/sv.gnu.inc
 </VirtualHost>
 <VirtualHost *:443>
  Include sites-available/sv.gnu.inc
 </VirtualHost>
 <VirtualHost *:443>
   ServerName frontend0.savannah.gnu.org
   Include sites-available/sv.inc
 </VirtualHost>

 # in sites-available/vhosts-nongnu.org:
 <VirtualHost *:80>
   Include sites-available/sv.nongnu.inc
 </VirtualHost>
 <VirtualHost *:443>
   Include sites-available/sv.nongnu.inc
 </VirtualHost>
 <VirtualHost *:443>
   ServerName frontend0.savannah.nongnu.org
   SetEnv SV_LOCAL_INC_PREFIX /etc/savane/nongnu-conf
   Include sites-available/sv.inc
 </VirtualHost>

 # in sites-available/sv.gnu.inc:
 ServerName savannah.gnu.org
 Include sites-available/sv.inc

 # in sites-available/sv.nongnu.org:
 ServerName savannah.nongnu.org
 SetEnv SV_LOCAL_INC_PREFIX /etc/savane/nongnu-conf
 Include sites-available/sv.inc

GNU/NONGNU redirection

gnu/nongnu redirection is handled entirely in the PHP code, based on whether the viewed group belongs in GNU or not (sometimes referred to as 'brother' site in the code/config files).

Example: when users visit https://savannah.nongnu.org/projects/coreutils they are redirected to https://savannah.gnu.org/projects/coreutils because coreutils is an official GNU package.

Enabling/Disabling redirection

In .savane.conf.php, setting the boolean variable $sys_debug_nobasehost to true disables redirection completely - useful for FrontEndDevelopmentSite.

Group-based redirection

When a page is visited (if it relates to a hosted group), the function $project->getTypeBaseHost() is called and returns the host that is relevant to the viewed group (based on the group type).

The values on the production site are:

mysql> select type_id, name, base_host from group_type ;
+---------+------------------------------------+---------------------+
| type_id | name                               | base_host           |
+---------+------------------------------------+---------------------+
|       1 | Official GNU software              | savannah.gnu.org    |
|       2 | non-GNU software and documentation | savannah.nongnu.org |
|       3 | www.gnu.org portions               | savannah.gnu.org    |
|       4 | GUG                                | savannah.nongnu.org |
|       6 | www.gnu.org translation teams      | savannah.gnu.org    |
+---------+------------------------------------+---------------------+
5 rows in set (0.00 sec)

Meaning: if redirection is enabled on a development site, these values will cause redirection to the real website.

To test redirection on a FrontEndDevelopmentSite, do the following (on a development database copy, never on the real database savane):

mysql> update group_type set base_host = "jsmith.frontend0.savannah.gnu.org" where type_id in (1,3,6);
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> update group_type set base_host = "jsmith.frontend0.savannah.nongnu.org" where type_id in (2,4);
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

mysql> select type_id, name, base_host from group_type ;
+---------+------------------------------------+--------------------------------------+
| type_id | name                               | base_host                            |
+---------+------------------------------------+--------------------------------------+
|       1 | Official GNU software              | jsmith.frontend0.savannah.gnu.org    |
|       2 | non-GNU software and documentation | jsmith.frontend0.savannah.nongnu.org |
|       3 | www.gnu.org portions               | jsmith.frontend0.savannah.gnu.org    |
|       4 | GUG                                | jsmith.frontend0.savannah.nongnu.org |
|       6 | www.gnu.org translation teams      | jsmith.frontend0.savannah.gnu.org    |
+---------+------------------------------------+--------------------------------------+
5 rows in set (0.00 sec)

Then set $sys_debug_nobasehost=false; the development .savane.conf.php file.

PHP Code

The variable $sys_debug_nobasehost is mentioned in the following files:

in ./frontend/php/include/init.php:

# if we are at wrong url, redirect
if (!$sys_debug_nobasehost && strcasecmp($_SERVER['HTTP_HOST"], $project->getTypeBaseHost()) != 0 && $project->getTypeBaseHost())
{
  header ("Location: http".(session_issecure()?'s":'")."://".$project->getTypeBaseHost().$_SERVER['PHP_SELF"]);
  exit;
}

in ./frontend/php/include/project_home/php:

if (strcasecmp($_SERVER['HTTP_HOST"], $project->getTypeBaseHost()) != 0 && $project->getTypeBaseHost())
 {
  header ('Location: http".(session_issecure()?'s":'").'://".$project->getTypeBaseHost().$_SERVER["REQUEST_URI"]);
  exit;
 }

Auto-login to 'brother' site

When a user visits the login page on https://savannah.gnu.org/account/login.php, there's is a check-box at the bottom of the page asking for "Login also in savannah.nongnu.org".

This is called a "brother" site in the code and config files. It is implemented using several HTTP redirects from one site to the other (e.g from the default 'savannah.gnu.org' to 'savannah.nongnu.org') sending hashed session IDs back and forth (counting on the fact that both sites run on the same physical server and share PHP session cookies).

In /etc/savane/.savane.conf.php there are these two variables:

$sys_default_domain="savannah.gnu.org";
$sys_brother_domain="savannah.nongnu.org";
$sys_incdir="/etc/savane/content/gnu-content";

In the correspnding /etc/savane/nongnu-conf/.savane.conf.php the default/brother hosts are swapped:

$sys_default_domain="frontend0.savannah.nongnu.org";
$sys_https_host="$sys_default_domain";
$sys_brother_domain="frontend0.savannah.gnu.org";
$sys_incdir="/etc/savane/content/nongnu-content";

If using a development site FrontEndDevelopmentSite, use the following in /home/jsmith/savannah/etc/savane/.savane.conf.php:

$sys_default_domain="jsmith.frontend0.savannah.gnu.org";
$sys_brother_domain="jsmith.frontend0.savannah.nongnu.org";
$sys_incdir="/home/jsmith/savannah/etc/savane/content/gnu-content";

and in /home/jsmith/savannah/etc/savane/nongnu-conf/.savane.conf.php:

$sys_default_domain="jsmith.frontend0.savannah.nongnu.org";
$sys_https_host="$sys_default_domain";
$sys_brother_domain="jsmith.frontend0.savannah.gnu.org";
$sys_incdir="/home/jsmith/savannah/etc/savane/content/nongnu-content";

The Apache configuration must be set appropriately for each domain. See SAVANE_CONF settings above.

NOTE: If the "login to [brother] set" checkbox is set, Savannah will do the redirection even if the variable is set ($sys_debug_nobasehost=true). If you forget to update the settings in nongnu-conf/.savane.conf.php, you might get redirected to the real website (e.g. savannah.nongnu.org), which will not only confuse things, but will also cause troubles with cookie authentication (you'll see an error message saying "Savane thinks your cookies are not activated for XXX" from login.php).

The 'brother' login sequences is used in two files: ./frontend/php/account/login.php and ./frontend/php/account/su.php (su implements "become superuser" feature for admins).

The gist is:

  1. After successful login to savannah.gnu.org, login.php will redirect to: https://savannah.NONGNU.org/account/login.php?session_uid=94790&session_hash=XXXXXXXXXX&cookie_for_a_year=&from_brother=1&login=1&stay_in_ssl=1&brotherhood=1&uri=%2F

  2. login.php (this time called on host savannah.nongnu.org) will see that $from_brother=1, and will set the cookie/session for this domain as well, then redirect back to savannah.gnu.org/.

File redirection

Since July 2020, Savannah serves user-supplied files from a separate domain for security reasons. Old URLs are redirected to that domain, non-file requests are redirected to the main domain.

ServerAlias file.savannah.gnu.org

<If "%{HTTP_HOST} == 'savannah.gnu.org'">
  RedirectPermanent /submissions_uploads/ \
    https://file.savannah.gnu.org/submissions_uploads/
  RedirectPermanent /file https://file.savannah.gnu.org/file
</If>
<If "%{REQUEST_URI} =~ m#^/((submissions_uploads/)|(file((/.*)?)$))#">
  # Empty section: the server will redirect when %{REQUEST_URI}
  # does _not_ match the above regular expression.
</If>
<ElseIf "%{HTTP_HOST} == 'file.savannah.gnu.org'">
  RedirectPermanent / https://savannah.gnu.org/
</ElseIf>

Likewise, for savanah.nongnu.org.