Use Apache

- Allows customization through .htaccess
- No need to configure or reload a server when adding a site
- Content negotiation
This commit is contained in:
Miraty 2023-04-10 00:50:42 +02:00
parent 2d6f2745a6
commit bd06fc7fbf
50 changed files with 138 additions and 551 deletions

View File

@ -101,7 +101,15 @@ Administrator email address published in every SOA record. Ends with a `.`, `@`
### `ht_path`
Filesystem path to the users files base directory. Files of a user are located inside `ht_path/<their-internal-user-id>/`
Apache can be [chroot](https://httpd.apache.org/docs/current/mod/mod_unixd.html#chrootdir)ed to this directory.
`<ht_path>/fs/<internal-user-id>/` is the users files base directory.
`<ht_path>/uri/<address>/` is automatically reachable by Apache (using [mod_vhost_alias](https://httpd.apache.org/docs/current/mod/mod_vhost_alias.html)) and contains relative symlinks to users managed directories.
### `user_quota_testing`, `user_quota_approved`
Maximum bytes a user can use on its SFTP space, depending on its account type.
### `subpath_domain` and `subpath_path`
@ -125,16 +133,6 @@ For the feature of sites in subdomains of a root domain:
`https://example.<subdomain_domain>/` maps to `<subdomain_path>/example/`
### `nginx_config_path`
Filesystem path to the directory that contains configuration files for dedicated sites.
The `http` block of nginx must contain something like `include <nginx_config_path>/*.conf;`
### `nginx_reload_cmd`
Command to execute through sudo to reload the nginx daemon.
### `tor_config_path`
Filesystem path to the directory containing Tor configuration for onion accesses. The full Tor configuration file path is `tor_config_path/<internal-user-id>/<site-dir-name>`
@ -151,6 +149,10 @@ Linux user as who runs the Tor daemon. Some commands are executed as this user t
Command to execute through sudo to reload the Tor daemon.
### `onion_internal_host`
HTTP Onion services listen on port 80 and forward requests to this host.
### `sudo_path`
Filesystem path to the sudo binary.
@ -198,19 +200,3 @@ Domain name that users need to direct their SFTP clients to. May be the same key
### `public_sftp_port`
Network port that users need to direct their SFTP clients to. The common default port is `22`.
### `https_port`
Network port where nginx listens. The common default port is `443`.
### `ipv6_listen_address`, `ipv4_listen_address`
IP address where nginx listens. May be the same as `ipv6_address` and `ipv4_address`, or `[::]` and `0.0.0.0` to listen on every address available.
### `http_onion_socket`
Filesystem path to the unix socket created by nginx and listening for incoming Onion services connections. (Used in Tor and nginx configuration files when creating an Onion service.)
### `user_quota_testing`, `user_quota_approved`
Maximum bytes a user can use on its SFTP space, depending on its account type.

View File

@ -54,8 +54,11 @@ Upload site's files to the server using SFTP. The way the site is accessed can t
[SFTPGo](https://github.com/drakkan/sftpgo)
: upload sites files using SFTP
[Apache HTTP Server](https://httpd.apache.org/)
: static HTTP server, with content negotiation and `.htaccess` dynamic configuration
[nginx](https://nginx.org/)
: static HTTP server
: HTTP reverse proxy for Apache; terminates TLS and enforces security header
Tor
: [Onion services](https://community.torproject.org/onion-services/)

View File

@ -30,20 +30,18 @@ public_soa_email = "hostmaster.invalid."
[ht]
ht_path = "/srv/servnest/ht"
user_quota_testing = 20971520
user_quota_approved = 209715200
subpath_domain = "ht.servnest.test"
subpath_path = "/srv/servnest/subpath"
subdomain_domain = "ht.servnest.test"
subdomain_path = "/srv/servnest/subdomain"
nginx_config_path = "/srv/servnest/nginx"
nginx_reload_cmd = "/usr/bin/systemctl reload nginx"
tor_config_path = "/srv/servnest/tor-config"
tor_keys_path = "/srv/servnest/tor-keys"
tor_user = "tor"
tor_reload_cmd = "/usr/bin/systemctl reload tor"
onion_internal_host = "[::1]:9080"
sudo_path = "/usr/bin/sudo"
certbot_path = "/usr/bin/certbot"
@ -65,12 +63,3 @@ sftp_fp = "/etc/sftpgo/ed25519.fp"
sftp_asciiart = "/etc/sftpgo/ed25519.asciiart"
sftp_domain = "sftp.servnest.test"
public_sftp_port = 2022
; Will be used in configuration files
https_port = 42443
ipv6_listen_address = "::1"
ipv4_listen_address = "127.0.0.1"
http_onion_socket = "/run/servnest/nginx.sock"
user_quota_testing = 20971520
user_quota_approved = 209715200

View File

@ -89,12 +89,12 @@ a:active {
text-decoration-thickness: 0.35em;
}
a[rel=help]:before {
a[rel~=help]:before {
content: '\202F';
font-size: 0.8rem;
}
a[rel=external]:after {
a[rel~=external]:after {
content: '\202F↗';
font-size: 0.8rem;
}

View File

@ -3,9 +3,9 @@
function htSetupUserFs($id) {
// Setup SFTP directory
umask(0002);
if (mkdir(CONF['ht']['ht_path'] . '/' . $id, 0775) !== true)
if (mkdir(CONF['ht']['ht_path'] . '/fs/' . $id, 0775) !== true)
output(500, 'Can\'t create user directory.');
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/' . $id . ' --no-dereference', result_code: $code);
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/fs/' . $id . ' --no-dereference', result_code: $code);
if ($code !== 0)
output(500, 'Can\'t change user directory group.');
@ -32,7 +32,7 @@ function formatDomain($domain) {
}
function listFsDirs($username) {
$absoluteDirs = glob(CONF['ht']['ht_path'] . '/' . $username . '/*/', GLOB_ONLYDIR);
$absoluteDirs = glob(CONF['ht']['ht_path'] . '/fs/' . $username . '/*/', GLOB_ONLYDIR);
$dirs = [];
foreach ($absoluteDirs as $absoluteDir)
if (preg_match('/^[a-zA-Z0-9_-]{1,64}$/D', basename($absoluteDir)))
@ -63,31 +63,23 @@ function dirsStatuses($type) {
return $dirs;
}
function htRelativeSymlink($target, $name) {
chdir(pathinfo($name)['dirname']);
$symlink = symlink($target, pathinfo($name)['basename']);
chdir(ROOT_PATH);
if ($symlink !== true)
output(500, 'Unable to create symlink.');
}
function htDeleteSite($address, $type) {
match ($type) {
'onion', 'dns' => htDeleteDedicatedSite($address, $type),
'subpath', 'subdomain' => htDeleteSubSite($address, $type)
};
}
function htDeleteSubSite($address, $type) {
if (unlink(CONF['ht'][$type . '_path'] . '/' . $address) !== true)
output(500, 'Unable to delete symlink.');
query('delete', 'sites', [
'username' => $_SESSION['id'],
'type' => $type,
'address' => $address,
]);
}
function htDeleteDedicatedSite($address, $type) {
$dir = query('select', 'sites', [
'address' => $address,
'type' => $type,
], 'site_dir')[0];
if ($type === 'onion') {
$dir = query('select', 'sites', [
'username' => $_SESSION['id'],
'address' => $address,
'type' => $type,
], 'site_dir')[0];
// Delete Tor config
if (unlink(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $dir) !== true)
output(500, 'Failed to delete Tor configuration.');
@ -103,15 +95,6 @@ function htDeleteDedicatedSite($address, $type) {
output(500, 'Failed to delete Tor keys.');
}
// Delete Nginx config
if (unlink(CONF['ht']['nginx_config_path'] . '/' . $address . '.conf') !== true)
output(500, 'Failed to delete Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
if ($type === 'dns') {
// Delete Let's Encrypt certificate
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' delete --quiet --cert-name ' . $address, $output, $code);
@ -119,10 +102,18 @@ function htDeleteDedicatedSite($address, $type) {
output(500, 'Certbot failed to delete the Let\'s Encrypt certificate.');
}
// Delete from database
$link = CONF['ht']['ht_path'] . '/uri/' . match ($type) {
'onion', 'dns' => $address,
'subdomain' => $address . '.' . CONF['ht']['subdomain_domain'],
'subpath' => CONF['ht']['subpath_domain'] . '/' . $address,
};
if (unlink($link) !== true)
output(500, 'Unable to delete symlink.');
query('delete', 'sites', [
'username' => $_SESSION['id'],
'type' => $type,
'site_dir' => $dir,
'address' => $address,
]);
}

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bad request · HTTP 400</title>
</head>
<body>
<h1>Bad request</h1>
<p>
The request is malformed.
</p>
<small>HTTP <code>400</code></small>
</body>
</html>

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Access forbidden · HTTP 403</title>
</head>
<body>
<h1>Access forbidden</h1>
<p>
The server refused to process the request for security reasons.
</p>
<small>HTTP <code>403</code></small>
</body>
</html>

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>File not found · HTTP 404</title>
</head>
<body>
<h1>File not found</h1>
<p>
The server did not find anything at the requested address.
</p>
<ul>
<li>The file may have been moved or deleted.</li>
<li>Perhaps the address was mistyped.</li>
</ul>
<small>HTTP <code>404</code></small>
</body>
</html>

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fichier introuvable · HTTP 404</title>
</head>
<body>
<h1>Fichier introuvable</h1>
<p>
Le serveur n'a rien trouvé à l'adresse demandée.
</p>
<ul>
<li>Le fichier a pu être déplacé ou supprimé.</li>
<li>L'adresse a peut-être été mal saisie.</li>
</ul>
<small>HTTP <code>404</code></small>
</body>
</html>

View File

@ -1,5 +0,0 @@
<?php
require 'lib.php';
echo file_get_contents('404.' . $locale . '.html');

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Method Not Allowed · HTTP 405</title>
</head>
<body>
<h1>Method Not Allowed</h1>
<p>
The request method is not supported for this resource.
</p>
<small>HTTP <code>405</code></small>
</body>
</html>

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gone · HTTP 410</title>
</head>
<body>
<h1>Gone</h1>
<p>
The requested resource is not available anymore.
</p>
<small>HTTP <code>410</code></small>
</body>
</html>

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>I'm a teapot · HTTP 418</title>
</head>
<body>
<h1>I'm a teapot</h1>
<p>
Your coffee cannot be brewed because this server is a teapot.
</p>
<small>HTTP <code>418</code></small>
</body>
</html>

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Server error · HTTP 500</title>
</head>
<body>
<h1>Server error</h1>
<p>
The server encountered an error and is unable to satisfy your request.
</p>
<ul>
<li>This error is probably temporary.</li>
<li>If it isn't, you can try to contact an administrator.</li>
</ul>
<small>HTTP <code>500</code></small>
</body>
</html>

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Erreur du serveur · HTTP 500</title>
</head>
<body>
<h1>Erreur du serveur</h1>
<p>
Le serveur a rencontré une erreur et ne peut pas répondre à cette requête.
</p>
<ul>
<li>Cette erreur est probablement temporaire.</li>
<li>Si ce n'est pas le cas, vous pouvez contacter ane administrataire.</li>
</ul>
<small>HTTP <code>500</code></small>
</body>
</html>

View File

@ -1,5 +0,0 @@
<?php
require 'lib.php';
echo file_get_contents('500.' . $locale . '.html');

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bad gateway · HTTP 502</title>
</head>
<body>
<h1>Bad gateway</h1>
<p>
The backend server encountered an error and is unable to satisfy your request.
</p>
<ul>
<li>This error is probably temporary.</li>
<li>If it isn't, you can try to contact an administrator.</li>
</ul>
<small>HTTP <code>502</code></small>
</body>
</html>

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Service unavailable · HTTP 503</title>
</head>
<body>
<h1>Service unavailable</h1>
<p>
The server cannot handle the request, because it is overloaded or down for maintenance.
</p>
<ul>
<li>This error is probably temporary.</li>
<li>If it isn't, you can try to contact an administrator.</li>
</ul>
<small>HTTP <code>503</code></small>
</body>
</html>

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Service indisponible · HTTP 503</title>
</head>
<body>
<h1>Service indisponible</h1>
<p>
Le serveur ne peut pas répondre à cette requête, car il est surchargé ou en cours de maintenance.
</p>
<ul>
<li>Cette erreur est probablement temporaire.</li>
<li>Si ce n'est pas le cas, vous pouvez contacter ane administrataire.</li>
</ul>
<small>HTTP <code>503</code></small>
</body>
</html>

View File

@ -1,5 +0,0 @@
<?php
require 'lib.php';
echo file_get_contents('503.' . $locale . '.html');

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gateway timeout · HTTP 504</title>
</head>
<body>
<h1>Gateway timeout</h1>
<p>
The backend server did not send a timely response.
</p>
<ul>
<li>This error is probably temporary.</li>
<li>If it isn't, you can try to contact an administrator.</li>
</ul>
<small>HTTP <code>504</code></small>
</body>
</html>

View File

@ -1,15 +0,0 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Broken site</title>
</head>
<body>
<h1>Broken site</h1>
<p>
The site you're trying to reach is misconfigured. This domain seems to be pointing to this server, but this server is not aware of a site using this domain.
</p>
<small>HTTP <code>404</code></small>
</body>
</html>

View File

@ -1,15 +0,0 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nothing here</title>
</head>
<body>
<h1>Nothing here</h1>
<p>
You reached the default site of this server directly using its IP address. There's nothing for you here.
</p>
<small>HTTP <code>404</code></small>
</body>
</html>

View File

@ -1,8 +0,0 @@
<?php
http_response_code(404);
if (filter_var(str_replace(['[', ']'], '', $_SERVER['HTTP_HOST']), FILTER_VALIDATE_IP))
echo file_get_contents('default-ip.html');
else
echo file_get_contents('default-domain.html');

View File

@ -1,10 +0,0 @@
<?php
$locale = 'en';
foreach (explode(',', preg_replace('/[A-Z0-9]|q=|;|-|\./', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')) as $client_locale) {
if (in_array($client_locale, ['en', 'fr'], true)) {
$locale = $client_locale;
break;
}
}
header('Content-Language: ' . $locale);

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TLS required</title>
</head>
<body>
<h1>TLS required</h1>
<p>
This site does not accept HTTP requests without TLS.
</p>
<h2>What happened?</h2>
<p>
You made a request using HTTP without TLS to the server, which refused this for privacy and security reasons, as unsecure HTTP connections can be watched and modified by any device on the way.
</p>
<h2>How to solve this?</h2>
<p>
You can replace the URI scheme <code>http</code> by <code>https</code> to tell your client to make the request using TLS.
</p>
<code>HTTP 403</code>
</body>
</html>

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nécessite TLS</title>
</head>
<body>
<h1>Nécessite TLS</h1>
<p>
Ce site n'accepte pas les connexions HTTP sans TLS.
</p>
<h2>Que s'est-il passé ?</h2>
<p>
Vous avez envoyé une requête HTTP sans TLS au serveur, qui l'a refusée pour des raisons de sécurité et de confidentialité.
</p>
<h2>Comment régler ce problème ?</h2>
<p>
Vous pouvez remplacer le schéma d'URI <code>http</code> par <code>https</code> pour indiquer à votre client de faire la requête avec TLS.
</p>
<code>HTTP 403</code>
</body>
</html>

View File

@ -1,7 +0,0 @@
<?php
http_response_code(403);
require 'lib.php';
require 'unsecure.' . $locale . '.html';

View File

@ -1,7 +1,5 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-25 16:16+0100\n"
"Language: fr\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -410,13 +408,13 @@ msgstr "Ce domaine doit avoir %2$s pour unique enregistrement %1$s."
msgid "No TXT record with the expected format has been found."
msgstr "Aucun enregistrement TXT avec le format attendu n'a été trouvé."
#: pg-act/ht/add-dns.php:59 pg-act/ht/add-onion.php:47
#: pg-act/ht/add-dns.php:41 pg-act/ht/add-onion.php:31
#: pg-act/ht/add-subdomain.php:19 pg-act/ht/add-subpath.php:19
#, php-format
msgid "%s added on this directory."
msgstr "%s ajouté sur ce dossier."
#: pg-act/ht/add-onion.php:47
#: pg-act/ht/add-onion.php:31
#, php-format
msgid "Its address is: %s"
msgstr "Son adresse est&nbsp;: %s"
@ -771,28 +769,12 @@ msgid "A content security policy (CSP) forbids Web browsers from loading JavaScr
msgstr "Une politique de sécurité du contenu (CSP) interdit l'intégration de ressources tierces ou de JavaScript."
#: pg-view/ht/index.php:114
msgid "gzip compression"
msgstr "Compression gzip"
msgid "<code>.htaccess</code> configuration"
msgstr "Configuration par <code>.htaccess</code>"
#: pg-view/ht/index.php:116
msgid "Static <em>gzip</em> compression is supported: if the client supports it and the file is available, <code>path.gz</code> is served instead of <code>path</code>."
msgstr "La compression <em>gzip</em> statique est supportée&nbsp;: si le client le supporte et que le fichier est disponible, <code>chemin.gz</code> est servi au lieu de <code>chemin</code>."
#: pg-view/ht/index.php:119
msgid "Index page"
msgstr "Page d'index"
#: pg-view/ht/index.php:121
msgid "When a request hits a directory, the first of the following files that exists <em>inside this directory</em> is served:"
msgstr "Lors d'une requête sur un dossier, le premier des fichiers suivants qui existe <em>dans ce dossier</em> est répondu&nbsp;:"
#: pg-view/ht/index.php:129
msgid "404 error page"
msgstr "Page d'erreur 404"
#: pg-view/ht/index.php:131
msgid "When a request ends in a <code>404</code> error, the first of the following files that exists <em>at the root of the site</em> is served:"
msgstr "Lors d'une requête aboutissant à une erreur <code>404</code>, le premier des fichiers suivants qui existe <em>à la racine du site</em> est répondu&nbsp;:"
msgid "You can change the way the HTTP server answers to requests in a directory by setting some directives in a file named <code>.htaccess</code> at the root of this directory. Only the following directives are allowed:"
msgstr "Vous pouvez modifier la façon dont le serveur HTTP répond aux requêtes dans un dossier en indiquant des directives dans un fichier nommé <code>.htaccess</code> à la racine de ce dossier. Seules les directives suivantes sont autorisées&nbsp;:"
#: pg-view/ns/caa.php:3
msgid "Flag"

View File

@ -407,13 +407,13 @@ msgstr ""
msgid "No TXT record with the expected format has been found."
msgstr ""
#: pg-act/ht/add-dns.php:59 pg-act/ht/add-onion.php:47
#: pg-act/ht/add-dns.php:41 pg-act/ht/add-onion.php:31
#: pg-act/ht/add-subdomain.php:19 pg-act/ht/add-subpath.php:19
#, php-format
msgid "%s added on this directory."
msgstr ""
#: pg-act/ht/add-onion.php:47
#: pg-act/ht/add-onion.php:31
#, php-format
msgid "Its address is: %s"
msgstr ""
@ -768,27 +768,11 @@ msgid "A content security policy (CSP) forbids Web browsers from loading JavaScr
msgstr ""
#: pg-view/ht/index.php:114
msgid "gzip compression"
msgid "<code>.htaccess</code> configuration"
msgstr ""
#: pg-view/ht/index.php:116
msgid "Static <em>gzip</em> compression is supported: if the client supports it and the file is available, <code>path.gz</code> is served instead of <code>path</code>."
msgstr ""
#: pg-view/ht/index.php:119
msgid "Index page"
msgstr ""
#: pg-view/ht/index.php:121
msgid "When a request hits a directory, the first of the following files that exists <em>inside this directory</em> is served:"
msgstr ""
#: pg-view/ht/index.php:129
msgid "404 error page"
msgstr ""
#: pg-view/ht/index.php:131
msgid "When a request ends in a <code>404</code> error, the first of the following files that exists <em>at the root of the site</em> is served:"
msgid "You can change the way the HTTP server answers to requests in a directory by setting some directives in a file named <code>.htaccess</code> at the root of this directory. Only the following directives are allowed:"
msgstr ""
#: pg-view/ns/caa.php:3

View File

@ -30,7 +30,7 @@ if (in_array('ht', $user_services, true)) {
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id']);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'], result_code: $code);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['ht_path'] . '/fs/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove user\'s directory.');
}

View File

@ -6,23 +6,23 @@ if (dirsStatuses('dns')[$_POST['dir']] !== false)
output(403, 'Wrong value for <code>dir</code>.');
if (query('select', 'sites', ['domain' => $_POST['domain']], 'domain') !== [])
output(403, _('This domain already exists on this service. Use another one.');
output(403, _('This domain already exists on this service. Use another one.'));
$remoteAaaaRecords = dns_get_record($_POST['domain'], DNS_AAAA);
if (is_array($remoteAaaaRecords) !== true)
output(500, sprintf(_('Can\'t retrieve the %s record.'), 'AAAA');
output(500, sprintf(_('Can\'t retrieve the %s record.'), 'AAAA'));
if (equalArrays([CONF['ht']['ipv6_address']], array_column($remoteAaaaRecords, 'ipv6')) !== true)
output(403, sprintf(_('This domain must have %2$s as its only %1$s record.'), 'AAAA', '<code>' . CONF['ht']['ipv6_address'] . '</code>'));
$remoteARecords = dns_get_record($_POST['domain'], DNS_A);
if (is_array($remoteARecords) !== true)
output(500, sprintf(_('Can\'t retrieve the %s record.'), 'A');
output(500, sprintf(_('Can\'t retrieve the %s record.'), 'A'));
if (equalArrays([CONF['ht']['ipv4_address']], array_column($remoteARecords, 'ip')) !== true)
output(403, sprintf(_('This domain must have %2$s as its only %1$s record.'), 'A', '<code>' . CONF['ht']['ipv4_address'] . '</code>'));
$remoteTXTRecords = dns_get_record($_POST['domain'], DNS_TXT);
if (is_array($remoteTXTRecords) !== true)
output(500, sprintf(_('Can\'t retrieve the %s record.'), 'TXT');
output(500, sprintf(_('Can\'t retrieve the %s record.'), 'TXT'));
if (preg_match('/^' . preg_quote(SERVER_NAME, '/') . '_domain-verification=([0-9a-f]{8})-([0-9a-f]{32})$/Dm', implode(LF, array_column($remoteTXTRecords, 'txt')), $matches) !== 1)
output(403, _('No TXT record with the expected format has been found.'));
@ -36,24 +36,6 @@ exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' ce
if ($returnCode !== 0)
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
$nginxConf = 'server {
listen [' . CONF['ht']['ipv6_listen_address'] . ']:' . CONF['ht']['https_port'] . ' ssl http2;
listen ' . CONF['ht']['ipv4_listen_address'] . ':' . CONF['ht']['https_port'] . ' ssl http2;
server_name ' . $_POST['domain'] . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
htRelativeSymlink('../fs/' . $_SESSION['id'] . '/' . $_POST['dir'], CONF['ht']['ht_path'] . '/uri/' . $_POST['domain']);
ssl_certificate /etc/letsencrypt/live/' . $_POST['domain'] . '/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/' . $_POST['domain'] . '/privkey.pem;
include inc/ht-tls.conf;
}
';
if (file_put_contents(CONF['ht']['nginx_config_path'] . '/' . $_POST['domain'] . '.conf', $nginxConf) === false)
output(500, 'Failed to write Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
output(200, sprintf(_('%s added on this directory.'), PAGE_METADATA['title']);
output(200, sprintf(_('%s added on this directory.'), PAGE_METADATA['title']));

View File

@ -7,8 +7,7 @@ rateLimit();
// Add Tor config
$torConf = 'HiddenServiceDir ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/
HiddenServicePort 80 unix:' . CONF['ht']['http_onion_socket'] . '
';
HiddenServicePort 80 ' . CONF['ht']['onion_internal_host'] . LF;
if (file_put_contents(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'], $torConf) === false)
output(500, 'Failed to write new Tor configuration.');
@ -26,22 +25,7 @@ if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
// Store it in the database
addSite($_SESSION['id'], $_POST['dir'], $onion, 'onion');
// Add Nginx config
$nginxConf = 'server {
listen unix:' . CONF['ht']['http_onion_socket'] . ';
server_name ' . $onion . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
include inc/ht-onion.conf;
}
';
if (file_put_contents(CONF['ht']['nginx_config_path'] . '/' . $onion . '.conf', $nginxConf) === false)
output(500, 'Failed to write Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
htRelativeSymlink('../fs/' . $_SESSION['id'] . '/' . $_POST['dir'], CONF['ht']['ht_path'] . '/uri/' . $onion);
// Tell the user their site address
output(200, sprintf(_('%s added on this directory.'), PAGE_METADATA['title']) . ' ' . sprintf(_('Its address is: %s'), '<a href="http://' . $onion . '/"><code>http://' . $onion . '/</code></a>'));

View File

@ -13,7 +13,7 @@ rateLimit();
addSite($_SESSION['id'], $_POST['dir'], $_POST['subdomain'], 'subdomain');
if (symlink(CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'], CONF['ht']['subdomain_path'] . '/' . $_POST['subdomain']) !== true)
if (symlink('../../fs/' . $_SESSION['id'] . '/' . $_POST['dir'], CONF['ht']['ht_path'] . '/uri/' . $_POST['subdomain'] . '.' . CONF['ht']['subdomain_domain']) !== true)
output(500, 'Unable to create symlink.');
output(200, sprintf(_('%s added on this directory.'), PAGE_METADATA['title']));

View File

@ -13,7 +13,7 @@ rateLimit();
addSite($_SESSION['id'], $_POST['dir'], $_POST['path'], 'subpath');
if (symlink(CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'], CONF['ht']['subpath_path'] . '/' . $_POST['path']) !== true)
if (symlink('../../fs/' . $_SESSION['id'] . '/' . $_POST['dir'], CONF['ht']['ht_path'] . '/uri/' . CONF['ht']['subpath_domain'] . '/' . $_POST['path']) !== true)
output(500, 'Unable to create symlink.');
output(200, sprintf(_('%s added on this directory.'), PAGE_METADATA['title']));

View File

@ -111,28 +111,57 @@ $quota = ($_SESSION['type'] ?? '' === 'approved') ? CONF['ht']['user_quota_appro
<?= _('A content security policy (CSP) forbids Web browsers from loading JavaScript or third-party resources.') ?>
</p>
<h3><?= _('gzip compression') ?></h3>
<h3><?= _('<code>.htaccess</code> configuration') ?></h3>
<p>
<?= _('Static <em>gzip</em> compression is supported: if the client supports it and the file is available, <code>path.gz</code> is served instead of <code>path</code>.') ?>
<?= _('You can change the way the HTTP server answers to requests in a directory by setting some directives in a file named <code>.htaccess</code> at the root of this directory. Only the following directives are allowed:') ?>
</p>
<ul>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/core.html#files"><code>&lt;Files&gt;</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/core.html#filesmatch"><code>&lt;FilesMatch&gt;</code></a></li>
<h3><?= _('Index page') ?></h3>
<p>
<?= _('When a request hits a directory, the first of the following files that exists <em>inside this directory</em> is served:') ?>
</p>
<ol>
<li><code>index.html</code></li>
<li><code>index.md</code></li>
<li><code>index.gmi</code></li>
</ol>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_alias.html#redirect"><code>Redirect</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_alias.html#redirectmatch"><code>RedirectMatch</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_alias.html#redirectpermanent"><code>RedirectPermanent</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_alias.html#redirecttemp"><code>RedirectTemp</code></a></li>
<h3><?= _('404 error page') ?></h3>
<p>
<?= _('When a request ends in a <code>404</code> error, the first of the following files that exists <em>at the root of the site</em> is served:') ?>
</p>
<ol>
<li><code>404.html</code></li>
<li><code>404.md</code></li>
<li><code>404.gmi</code></li>
</ol>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/core.html#errordocument"><code>ErrorDocument</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype"><code>AddType</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/core.html#forcetype"><code>ForceType</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#defaultlanguage"><code>DefaultLanguage</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#addanguage"><code>AddLanguage</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_negotiation.html#languagepriority"><code>LanguagePriority</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_negotiation.html#forcelanguagepriority"><code>ForceLanguagePriority</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_dir.html#directoryindex"><code>DirectoryIndex</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_dir.html#directoryslash"><code>DirectorySlash</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#removetype"><code>RemoveType</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#removeoutputfilter"><code>RemoveOutputFilter</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#removelanguage"><code>RemoveLanguage</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#removeencoding"><code>RemoveEncoding</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_mime.html#removecharset"><code>RemoveCharset</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_expires.html#expiresactive"><code>ExpiresActive</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_expires.html#expiresdefault"><code>ExpiresDefault</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_expires.html#expiresbytype"><code>ExpiresByType</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#defaulticon"><code>DefaultIcon</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#adddescription"><code>AddDescription</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#addalt"><code>AddAlt</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#addaltbyencoding"><code>AddAltByEncoding</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#addaltbytype"><code>AddAltByType</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#addicon"><code>AddIcon</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#addiconbyencoding"><code>AddIconByEncoding</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#addiconbytype"><code>AddIconByType</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#indexignore"><code>IndexIgnore</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#indexignorereset"><code>IndexIgnoreReset</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#indexoptions"><code>IndexOptions</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#indexorderdefault"><code>IndexOrderDefault</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#headername"><code>HeaderName</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#readmename"><code>ReadmeName</code></a></li>
<li><a rel="external help" href="https://httpd.apache.org/docs/current/mod/mod_autoindex.html#indexstylesheet"><code>IndexStyleSheet</code></a></li>
</ul>
</section>

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="flag"><?= _('Flag') ?></label>
<br>
<input id="flag" min="0" max="127" placeholder="0" name="flag" type="number">

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="cname"><?= _('Canonical name') ?></label>
<br>
<input id="cname" placeholder="main.<?= PLACEHOLDER_DOMAIN ?>" name="cname" type="text">

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="dname"><?= _('Delegation name') ?></label>
<br>
<input id="dname" placeholder="main.<?= PLACEHOLDER_DOMAIN ?>" name="dname" type="text">

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="ip"><?= _('IP address') ?></label><br>
<input required="" pattern="^[a-f0-9:.]+$" id="ip" name="ip" minlength="2" maxlength="39" size="40" type="text" placeholder="<?= PLACEHOLDER_IPV6 ?> ou <?= PLACEHOLDER_IPV4 ?>"><br>
<input type="submit" value="<?= _('Apply') ?>">

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<fieldset>
<legend><?= _('Latitude') ?></legend>
<div>

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="priority"><?= _('Priority') ?></label>
<br>
<input id="priority" min="0" max="65535" value="0" placeholder="0" name="priority" type="number">

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="ns"><?= _('Name server') ?></label>
<br>
<input id="ns" placeholder="ns1.<?= PLACEHOLDER_DOMAIN ?>" name="ns" type="text">

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="priority"><?= _('Priority') ?></label>
<br>

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="algo"><?= _('Algorithm') ?></label>
<br>

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="use"><?= _('Use') ?></label>
<br>

View File

@ -1,5 +1,5 @@
<form method="post">
<?php require 'form.ns.php'; ?>
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
<label for="txt"><?= _('Text') ?></label>
<br>
<input id="txt" minlenght="5" maxlength="8192" pattern="^[a-zA-Z0-9 .@=:!%$+/\()[\]_-]{5,8192}$" placeholder="<?= _('Some text…') ?>" name="txt" type="text">

View File

@ -10,7 +10,7 @@ date_default_timezone_set('UTC');
$locale = 'en';
foreach (explode(',', preg_replace('/[A-Z0-9]|q=|;|-|\./', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')) as $client_locale) {
if (in_array($client_locale, array_diff(scandir('locales'), ['..', '.']), true)) {
if (in_array($client_locale, array_diff(scandir(ROOT_PATH . '/locales'), ['..', '.']), true)) {
$locale = $client_locale;
break;
}
@ -31,7 +31,7 @@ const PLACEHOLDER_IPV4 = '203.0.113.42'; // From RFC5737: IPv4 Address Blocks Re
foreach (array_diff(scandir(ROOT_PATH . '/fn'), ['..', '.']) as $file)
require ROOT_PATH . '/fn/' . $file;
require 'pages.php';
require ROOT_PATH . '/pages.php';
if ($_SERVER['REQUEST_URI'] === '/sftpgo-auth.php')
return;
@ -150,12 +150,12 @@ if ($_POST !== []) {
output(403, _('This account doesn\'t exist anymore. Log out to end this ghost session.'));
}
if (file_exists('pg-act/' . PAGE_ADDRESS . '.php'))
require 'pg-act/' . PAGE_ADDRESS . '.php';
if (file_exists(ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php'))
require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php';
}
function displayPage($data) {
require 'view.php';
require ROOT_PATH . '/view.php';
exit();
}
displayPage($data ??= NULL);

View File

@ -10,7 +10,7 @@ function deny() {
if (CONF['common']['services']['ht'] !== 'enabled')
deny();
$auth_data = json_decode(file_get_contents('php://input'), true);
$auth_data = json_decode(file_get_contents('php://input'), true, flags: JSON_THROW_ON_ERROR);
$username = hashUsername($auth_data['username']);
@ -29,7 +29,7 @@ echo '
{
"status": 1,
"username": ' . json_encode($auth_data['username']) . ',
"home_dir": "' . CONF['ht']['ht_path'] . '/' . $id . '",
"home_dir": "' . CONF['ht']['ht_path'] . '/fs/' . $id . '",
"quota_size": ' . ((query('select', 'users', ['id' => $id], 'type')[0] === 'approved') ? CONF['ht']['user_quota_approved'] : CONF['ht']['user_quota_testing']) . ',
"permissions": {
"/": [

View File

@ -38,7 +38,7 @@
if (in_array(SERVICE, SERVICES_USER, true) AND CONF['common']['services'][SERVICE] === 'error')
echo '<p><strong>' . _('This service is currently under maintenance. No action can be taken on it until an administrator finishes repairing it.') . '</strong></p>';
require 'pg-view/' . PAGE_ADDRESS . '.php';
require ROOT_PATH . '/pg-view/' . PAGE_ADDRESS . '.php';
if ($_POST === [] AND PAGE_METADATA['require-login'] ?? true !== false AND !isset($_SESSION['id']) AND PAGE_TERMINAL)
echo '<p>' . sprintf(_('This form won\'t be accepted because you need to %slog in%s first.'), '<a class="auth" href="' . redirUrl('auth/login') . '">', '</a>') . '</p>';