Merge pull request 'dev' (#8) from dev into main
Reviewed-on: https://code.antopie.org/servnest/servnest/pulls/8
This commit is contained in:
commit
f5aee06ff5
41 changed files with 645 additions and 241 deletions
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
## Program flow
|
## Program flow
|
||||||
|
|
||||||
|
`init.php`
|
||||||
|
: Initializes common values
|
||||||
|
|
||||||
`router.php`
|
`router.php`
|
||||||
: Receives every external HTTP request from the web server, executes actions required in any case, executes matching code in `pg-act` if appropriate, and calls `view.php` either way.
|
: Receives every external HTTP request from the web server, executes actions required in any case, executes matching code in `pg-act` if appropriate, and calls `view.php` either way.
|
||||||
|
|
||||||
|
@ -21,12 +24,12 @@ The `output` function is used to return success or error messages and stop proce
|
||||||
`fn/`
|
`fn/`
|
||||||
: Functions, grouped by concerned service
|
: Functions, grouped by concerned service
|
||||||
|
|
||||||
|
`jobs/`
|
||||||
|
: CLI scripts
|
||||||
|
|
||||||
`sftpgo-auth.php`
|
`sftpgo-auth.php`
|
||||||
: When someone tries to log in over SFTP, SFTPGo sends username and password to this script, which queries the database and replies whether authentication succeeded or not.
|
: When someone tries to log in over SFTP, SFTPGo sends username and password to this script, which queries the database and replies whether authentication succeeded or not.
|
||||||
|
|
||||||
`check.php`
|
|
||||||
: This file is not part of the normal program execution, it is meant to be run by developers to test that the current setup is working.
|
|
||||||
|
|
||||||
`DOCS/`
|
`DOCS/`
|
||||||
: Documentation (some important or standard files may be directly in the root)
|
: Documentation (some important or standard files may be directly in the root)
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ I plan to create and maintain a public stable instance of ServNest, but I haven'
|
||||||
### Name server (`ns`)
|
### Name server (`ns`)
|
||||||
|
|
||||||
* Host a zone on the server
|
* Host a zone on the server
|
||||||
* Zone file edition through `<textarea>`
|
* Plain zone file edition
|
||||||
* Dedicated forms to set/unset `A`, `AAAA`, `NS`, `TXT`, `CAA`, `SRV`, `MX`, `SRV`, `SSHFP`, `TLSA`, `CNAME`, `DNAME` and `LOC` records
|
* Dedicated forms to set/unset `A`, `AAAA`, `NS`, `TXT`, `CAA`, `SRV`, `MX`, `SRV`, `SSHFP`, `TLSA`, `CNAME`, `DNAME` and `LOC` records
|
||||||
* Display records or the full zone file
|
* Display records or the full zone file
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,14 @@ input[type=password] {
|
||||||
width: 7ch;
|
width: 7ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#public-key {
|
||||||
|
width: 70ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
#key {
|
||||||
|
width: 65ch;
|
||||||
|
}
|
||||||
|
|
||||||
:disabled {
|
:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ h3 {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
main > *:not(pre, form), form > *:not(textarea), footer {
|
main > *:not(pre, form), footer {
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
11
db/migrations/005-create-ssh-keys-table.sql
Normal file
11
db/migrations/005-create-ssh-keys-table.sql
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "ssh-keys" (
|
||||||
|
"key" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"directory" TEXT NOT NULL,
|
||||||
|
UNIQUE("key", "username", "directory"),
|
||||||
|
FOREIGN KEY("username") REFERENCES "users"("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -53,4 +53,11 @@ CREATE TABLE IF NOT EXISTS "sites" (
|
||||||
PRIMARY KEY("address", "type"),
|
PRIMARY KEY("address", "type"),
|
||||||
FOREIGN KEY("username") REFERENCES "users"("id")
|
FOREIGN KEY("username") REFERENCES "users"("id")
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS "ssh-keys" (
|
||||||
|
"key" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"directory" TEXT NOT NULL,
|
||||||
|
UNIQUE("key", "username", "directory"),
|
||||||
|
FOREIGN KEY("username") REFERENCES "users"("id")
|
||||||
|
);
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
51
fn/auth.php
51
fn/auth.php
|
@ -87,6 +87,57 @@ function setupDisplayUsername($display_username) {
|
||||||
$_SESSION['display-username-cyphertext'] = $cyphertext;
|
$_SESSION['display-username-cyphertext'] = $cyphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function authDeleteUser($user_id) {
|
||||||
|
$user_services = explode(',', query('select', 'users', ['id' => $user_id], 'services')[0]);
|
||||||
|
|
||||||
|
foreach (SERVICES_USER as $service)
|
||||||
|
if (in_array($service, $user_services, true) AND CONF['common']['services'][$service] !== 'enabled')
|
||||||
|
output(503, sprintf(_('Your account can\'t be deleted because the %s service is currently unavailable.'), '<em>' . PAGES[$service]['index']['title'] . '</em>'));
|
||||||
|
|
||||||
|
if (in_array('reg', $user_services, true))
|
||||||
|
foreach (query('select', 'registry', ['username' => $user_id], 'domain') as $domain)
|
||||||
|
regDeleteDomain($domain, $user_id);
|
||||||
|
|
||||||
|
if (in_array('ns', $user_services, true))
|
||||||
|
foreach (query('select', 'zones', ['username' => $user_id], 'zone') as $zone)
|
||||||
|
nsDeleteZone($zone, $user_id);
|
||||||
|
|
||||||
|
if (in_array('ht', $user_services, true)) {
|
||||||
|
foreach (query('select', 'sites', ['username' => $user_id]) as $site)
|
||||||
|
htDeleteSite($site['address'], $site['type'], $user_id);
|
||||||
|
|
||||||
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'-u',
|
||||||
|
CONF['ht']['tor_user'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['rm_path'],
|
||||||
|
'-r',
|
||||||
|
'--',
|
||||||
|
CONF['ht']['tor_keys_path'] . '/' . $user_id,
|
||||||
|
], result_code: $code);
|
||||||
|
if ($code !== 0)
|
||||||
|
output(500, 'Can\'t remove Tor keys directory.');
|
||||||
|
|
||||||
|
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $user_id);
|
||||||
|
|
||||||
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'-u',
|
||||||
|
CONF['ht']['sftpgo_user'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['rm_path'],
|
||||||
|
'-r',
|
||||||
|
'--',
|
||||||
|
CONF['ht']['ht_path'] . '/fs/' . $user_id
|
||||||
|
], result_code: $code);
|
||||||
|
if ($code !== 0)
|
||||||
|
output(500, 'Can\'t remove user\'s directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
query('delete', 'users', ['id' => $user_id]);
|
||||||
|
}
|
||||||
|
|
||||||
function rateLimit() {
|
function rateLimit() {
|
||||||
if (PAGE_METADATA['tokens_account_cost'] ?? 0 > 0)
|
if (PAGE_METADATA['tokens_account_cost'] ?? 0 > 0)
|
||||||
rateLimitAccount(PAGE_METADATA['tokens_account_cost']);
|
rateLimitAccount(PAGE_METADATA['tokens_account_cost']);
|
||||||
|
|
|
@ -10,7 +10,15 @@ function output($code, $msg = '', $logs = [''], $data = []) {
|
||||||
4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
||||||
5 => '<p><output>' . _('<strong>Server error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
5 => '<p><output>' . _('<strong>Server error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
||||||
};
|
};
|
||||||
|
if (is_callable('displayPage'))
|
||||||
displayPage(array_merge(['final_message' => $final_message], $data));
|
displayPage(array_merge(['final_message' => $final_message], $data));
|
||||||
|
echo $final_message;
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exescape(array $args, array &$output = NULL, int &$result_code = NULL): int {
|
||||||
|
exec('2>&1 ' . implode(' ', array_map('escapeshellarg', $args)), $output, $result_code);
|
||||||
|
return $result_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
function insert($table, $values) {
|
function insert($table, $values) {
|
||||||
|
|
35
fn/dns.php
35
fn/dns.php
|
@ -15,42 +15,53 @@ function parseZoneFile($zone_content, $types, $filter_domain = false) {
|
||||||
return $parsed_zone_content;
|
return $parsed_zone_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function knotcConfExec($cmds) {
|
function knotc(array $cmds, array &$output = NULL, int &$return_code = NULL): void {
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 conf-begin', $output['begin'], $code['begin']);
|
exescape([
|
||||||
|
CONF['dns']['knotc_path'],
|
||||||
|
'--blocking',
|
||||||
|
'--timeout',
|
||||||
|
'3',
|
||||||
|
'--',
|
||||||
|
...$cmds,
|
||||||
|
], $output, $return_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function knotcConfExec(array $cmds): void {
|
||||||
|
knotc(['conf-begin'], $output['begin'], $code['begin']);
|
||||||
if ($code['begin'] !== 0)
|
if ($code['begin'] !== 0)
|
||||||
output(500, 'knotcConfExec: <code>knotc</code> failed with exit code <samp>' . $code['begin'] . '</samp>: <samp>' . $output['begin'][0] . '</samp>.');
|
output(500, 'knotcConfExec: <code>knotc</code> failed with exit code <samp>' . $code['begin'] . '</samp>: <samp>' . $output['begin'][0] . '</samp>.');
|
||||||
|
|
||||||
foreach ($cmds as $cmd) {
|
foreach ($cmds as $cmd) {
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 conf-' . $cmd, $output['op'], $code['op']);
|
knotc($cmd, $output['op'], $code['op']);
|
||||||
if ($code['op'] !== 0) {
|
if ($code['op'] !== 0) {
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking conf-abort');
|
knotc(['conf-abort']);
|
||||||
output(500, 'knotcConfExec: <code>knotc</code> failed with exit code <samp>' . $code['op'] . '</samp>: <samp>' . $output['op'][0] . '</samp>.');
|
output(500, 'knotcConfExec: <code>knotc</code> failed with exit code <samp>' . $code['op'] . '</samp>: <samp>' . $output['op'][0] . '</samp>.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 conf-commit', $output['commit'], $code['commit']);
|
knotc(['conf-commit'], $output['commit'], $code['commit']);
|
||||||
if ($code['commit'] !== 0) {
|
if ($code['commit'] !== 0) {
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking conf-abort');
|
knotc(['conf-abort']);
|
||||||
output(500, 'knotcConfExec: <code>knotc</code> failed with exit code <samp>' . $code['commit'] . '</samp>: <samp>' . $output['commit'][0] . '</samp>.');
|
output(500, 'knotcConfExec: <code>knotc</code> failed with exit code <samp>' . $code['commit'] . '</samp>: <samp>' . $output['commit'][0] . '</samp>.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function knotcZoneExec($zone, $cmd, $action = NULL) {
|
function knotcZoneExec(string $zone, array $cmd, string $action = NULL) {
|
||||||
$action = checkAction($action ?? $_POST['action']);
|
$action = checkAction($action ?? $_POST['action']);
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-begin ' . $zone, $output['begin'], $code['begin']);
|
knotc(['zone-begin', $zone], $output['begin'], $code['begin']);
|
||||||
if ($code['begin'] !== 0)
|
if ($code['begin'] !== 0)
|
||||||
output(500, 'knotcZoneExec: <code>knotc</code> failed with exit code <samp>' . $code['begin'] . '</samp>: <samp>' . $output['begin'][0] . '</samp>.');
|
output(500, 'knotcZoneExec: <code>knotc</code> failed with exit code <samp>' . $code['begin'] . '</samp>: <samp>' . $output['begin'][0] . '</samp>.');
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-' . $action . 'set ' . $zone . ' ' . implode(' ', $cmd), $output['op'], $code['op']);
|
knotc(['zone-' . $action . 'set', $zone, ...$cmd], $output['op'], $code['op']);
|
||||||
if ($code['op'] !== 0) {
|
if ($code['op'] !== 0) {
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-abort ' . $zone);
|
knotc(['zone-abort', $zone]);
|
||||||
output(500, 'knotcZoneExec: <code>knotc</code> failed with exit code <samp>' . $code['op'] . '</samp>: <samp>' . $output['op'][0] . '</samp>.');
|
output(500, 'knotcZoneExec: <code>knotc</code> failed with exit code <samp>' . $code['op'] . '</samp>: <samp>' . $output['op'][0] . '</samp>.');
|
||||||
}
|
}
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-commit ' . $zone, $output['commit'], $code['commit']);
|
knotc(['zone-commit', $zone], $output['commit'], $code['commit']);
|
||||||
if ($code['commit'] !== 0) {
|
if ($code['commit'] !== 0) {
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-abort ' . $zone);
|
knotc(['zone-abort', $zone]);
|
||||||
output(500, 'knotcZoneExec: <code>knotc</code> failed with exit code <samp>' . $code['commit'] . '</samp>: <samp>' . $output['commit'][0] . '</samp>.');
|
output(500, 'knotcZoneExec: <code>knotc</code> failed with exit code <samp>' . $code['commit'] . '</samp>: <samp>' . $output['commit'][0] . '</samp>.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
fn/ht.php
60
fn/ht.php
|
@ -1,12 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
const SUBPATH_REGEX = '^[a-z0-9-]{4,63}$';
|
||||||
|
const ED25519_PUBKEY_REGEX = '^[a-zA-Z0-9/+]{68}$';
|
||||||
|
|
||||||
function htSetupUserFs($id) {
|
function htSetupUserFs($id) {
|
||||||
// Setup SFTP directory
|
// Setup SFTP directory
|
||||||
if (mkdir(CONF['ht']['ht_path'] . '/fs/' . $id, 0000) !== true)
|
if (mkdir(CONF['ht']['ht_path'] . '/fs/' . $id, 0000) !== true)
|
||||||
output(500, 'Can\'t create user directory.');
|
output(500, 'Can\'t create user directory.');
|
||||||
if (chmod(CONF['ht']['ht_path'] . '/fs/' . $id, 0775) !== true)
|
if (chmod(CONF['ht']['ht_path'] . '/fs/' . $id, 0775) !== true)
|
||||||
output(500, 'Can\'t chmod user directory.');
|
output(500, 'Can\'t chmod user directory.');
|
||||||
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/fs/' . $id . ' --no-dereference', result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['chgrp_path'],
|
||||||
|
'--no-dereference',
|
||||||
|
'--',
|
||||||
|
CONF['ht']['sftpgo_group'],
|
||||||
|
CONF['ht']['ht_path'] . '/fs/' . $id,
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Can\'t change user directory group.');
|
output(500, 'Can\'t change user directory group.');
|
||||||
|
|
||||||
|
@ -17,7 +28,16 @@ function htSetupUserFs($id) {
|
||||||
output(500, 'Can\'t chmod Tor config directory.');
|
output(500, 'Can\'t chmod Tor config directory.');
|
||||||
|
|
||||||
// Setup Tor keys directory
|
// Setup Tor keys directory
|
||||||
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['mkdir_path'] . ' --mode=0700 ' . CONF['ht']['tor_keys_path'] . '/' . $id, result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'-u',
|
||||||
|
CONF['ht']['tor_user'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['mkdir_path'],
|
||||||
|
'--mode=0700',
|
||||||
|
'--',
|
||||||
|
CONF['ht']['tor_keys_path'] . '/' . $id,
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Can\'t create Tor keys directory.');
|
output(500, 'Can\'t create Tor keys directory.');
|
||||||
}
|
}
|
||||||
|
@ -35,6 +55,8 @@ function formatDomain($domain) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function listFsDirs($username) {
|
function listFsDirs($username) {
|
||||||
|
if ($username === '')
|
||||||
|
return [];
|
||||||
$absoluteDirs = glob(CONF['ht']['ht_path'] . '/fs/' . $username . '/*/', GLOB_ONLYDIR);
|
$absoluteDirs = glob(CONF['ht']['ht_path'] . '/fs/' . $username . '/*/', GLOB_ONLYDIR);
|
||||||
$dirs = [];
|
$dirs = [];
|
||||||
foreach ($absoluteDirs as $absoluteDir)
|
foreach ($absoluteDirs as $absoluteDir)
|
||||||
|
@ -74,33 +96,53 @@ function htRelativeSymlink($target, $name) {
|
||||||
output(500, 'Unable to create symlink.');
|
output(500, 'Unable to create symlink.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function htDeleteSite($address, $type) {
|
function htDeleteSite($address, $type, $user_id) {
|
||||||
|
|
||||||
if ($type === 'onion') {
|
if ($type === 'onion') {
|
||||||
$dir = query('select', 'sites', [
|
$dir = query('select', 'sites', [
|
||||||
'username' => $_SESSION['id'],
|
'username' => $user_id,
|
||||||
'address' => $address,
|
'address' => $address,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
], 'site_dir')[0];
|
], 'site_dir')[0];
|
||||||
|
|
||||||
// Delete Tor config
|
// Delete Tor config
|
||||||
if (unlink(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $dir) !== true)
|
if (unlink(CONF['ht']['tor_config_path'] . '/' . $user_id . '/' . $dir) !== true)
|
||||||
output(500, 'Failed to delete Tor configuration.');
|
output(500, 'Failed to delete Tor configuration.');
|
||||||
|
|
||||||
// Reload Tor
|
// Reload Tor
|
||||||
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['tor_reload_cmd'], result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'--',
|
||||||
|
...explode(' ', CONF['ht']['tor_reload_cmd']),
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Failed to reload Tor.');
|
output(500, 'Failed to reload Tor.');
|
||||||
|
|
||||||
// Delete Tor keys
|
// Delete Tor keys
|
||||||
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $dir, result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'-u',
|
||||||
|
CONF['ht']['tor_user'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['rm_path'],
|
||||||
|
'-r',
|
||||||
|
'--',
|
||||||
|
CONF['ht']['tor_keys_path'] . '/' . $user_id . '/' . $dir,
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Failed to delete Tor keys.');
|
output(500, 'Failed to delete Tor keys.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type === 'dns') {
|
if ($type === 'dns') {
|
||||||
// Delete Let's Encrypt certificate
|
// Delete Let's Encrypt certificate
|
||||||
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' delete --quiet --cert-name ' . $address, result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
CONF['ht']['certbot_path'],
|
||||||
|
'delete',
|
||||||
|
'--quiet',
|
||||||
|
'--cert-name',
|
||||||
|
$address,
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Certbot failed to delete the Let\'s Encrypt certificate.');
|
output(500, 'Certbot failed to delete the Let\'s Encrypt certificate.');
|
||||||
}
|
}
|
||||||
|
@ -115,7 +157,7 @@ function htDeleteSite($address, $type) {
|
||||||
output(500, 'Unable to delete symlink.');
|
output(500, 'Unable to delete symlink.');
|
||||||
|
|
||||||
query('delete', 'sites', [
|
query('delete', 'sites', [
|
||||||
'username' => $_SESSION['id'],
|
'username' => $user_id,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'address' => $address,
|
'address' => $address,
|
||||||
]);
|
]);
|
||||||
|
|
18
fn/ns.php
18
fn/ns.php
|
@ -48,22 +48,32 @@ function nsCheckZonePossession($zone) {
|
||||||
output(403, 'You don\'t own this zone on the name server.');
|
output(403, 'You don\'t own this zone on the name server.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function nsDeleteZone($zone) {
|
function nsDeleteZone($zone, $user_id) {
|
||||||
// Remove from Knot configuration
|
// Remove from Knot configuration
|
||||||
knotcConfExec(["unset 'zone[$zone]'"]);
|
knotcConfExec([['conf-unset', 'zone[' . $zone . ']']]);
|
||||||
|
|
||||||
// Remove Knot zone file
|
// Remove Knot zone file
|
||||||
if (unlink(CONF['ns']['knot_zones_path'] . '/' . $zone . 'zone') !== true)
|
if (unlink(CONF['ns']['knot_zones_path'] . '/' . $zone . 'zone') !== true)
|
||||||
output(500, 'Failed to remove Knot zone file.');
|
output(500, 'Failed to remove Knot zone file.');
|
||||||
|
|
||||||
// Remove Knot related data
|
// Remove Knot related data
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 --force zone-purge ' . $zone . ' +orphan', result_code: $code);
|
exescape([
|
||||||
|
CONF['dns']['knotc_path'],
|
||||||
|
'--blocking',
|
||||||
|
'--timeout',
|
||||||
|
'3',
|
||||||
|
'--force',
|
||||||
|
'--',
|
||||||
|
'zone-purge',
|
||||||
|
$zone,
|
||||||
|
'+orphan',
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Failed to purge zone data.');
|
output(500, 'Failed to purge zone data.');
|
||||||
|
|
||||||
// Remove from database
|
// Remove from database
|
||||||
query('delete', 'zones', [
|
query('delete', 'zones', [
|
||||||
'zone' => $zone,
|
'zone' => $zone,
|
||||||
'username' => $_SESSION['id'],
|
'username' => $user_id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
const SUBDOMAIN_REGEX = '^[a-z0-9]{4,63}$';
|
const SUBDOMAIN_REGEX = '^(?!\-)(?!..\-\-)[a-z0-9-]{4,63}(?<!\-)$';
|
||||||
|
|
||||||
function regListUserDomains() {
|
function regListUserDomains() {
|
||||||
if (isset($_SESSION['id']))
|
if (isset($_SESSION['id']))
|
||||||
|
@ -13,7 +13,7 @@ function regCheckDomainPossession($domain) {
|
||||||
output(403, 'You don\'t own this domain on the registry.');
|
output(403, 'You don\'t own this domain on the registry.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function regDeleteDomain($domain) {
|
function regDeleteDomain($domain, $user_id) {
|
||||||
// Delete domain from registry file
|
// Delete domain from registry file
|
||||||
$path = CONF['reg']['suffixes_path'] . '/' . regParseDomain($domain)['suffix'] . 'zone';
|
$path = CONF['reg']['suffixes_path'] . '/' . regParseDomain($domain)['suffix'] . 'zone';
|
||||||
$content = file_get_contents($path);
|
$content = file_get_contents($path);
|
||||||
|
@ -28,7 +28,7 @@ function regDeleteDomain($domain) {
|
||||||
|
|
||||||
$conditions = [
|
$conditions = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'username' => $_SESSION['id'],
|
'username' => $user_id,
|
||||||
];
|
];
|
||||||
|
|
||||||
insert('registry-history', [
|
insert('registry-history', [
|
||||||
|
|
48
init.php
Normal file
48
init.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
umask(0077);
|
||||||
|
|
||||||
|
set_error_handler(function ($level, $message, $file = '', $line = 0) {
|
||||||
|
throw new ErrorException($message, 0, $level, $file, $line);
|
||||||
|
});
|
||||||
|
set_exception_handler(function ($e) {
|
||||||
|
error_log($e);
|
||||||
|
http_response_code(500);
|
||||||
|
echo '<h1>Error</h1>An error occured.';
|
||||||
|
});
|
||||||
|
register_shutdown_function(function () { // Also catch fatal errors
|
||||||
|
if (($error = error_get_last()) !== NULL)
|
||||||
|
throw new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ROOT_PATH = __DIR__;
|
||||||
|
define('CONF', parse_ini_file(ROOT_PATH . '/config.ini', true, INI_SCANNER_TYPED));
|
||||||
|
|
||||||
|
define('DB', new PDO('sqlite:' . ROOT_PATH . '/db/servnest.db'));
|
||||||
|
DB->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
DB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
|
foreach (explode(',', preg_replace('/[A-Z0-9]|q=|;|-|\./', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')) as $client_locale)
|
||||||
|
if (in_array($client_locale, array_diff(scandir(ROOT_PATH . '/locales'), ['..', '.']), true)) {
|
||||||
|
$locale = $client_locale;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
define('LOCALE', $locale ?? 'en');
|
||||||
|
putenv('LANG=C.UTF-8');
|
||||||
|
setlocale(LC_MESSAGES, 'C.UTF-8');
|
||||||
|
bindtextdomain('messages', ROOT_PATH . '/locales/' . LOCALE);
|
||||||
|
header('Content-Language: ' . LOCALE);
|
||||||
|
|
||||||
|
const SERVICES_USER = ['reg', 'ns', 'ht'];
|
||||||
|
|
||||||
|
const LF = "\n";
|
||||||
|
|
||||||
|
const PLACEHOLDER_DOMAIN = 'example'; // From RFC2606: Reserved Top Level DNS Names > 2. TLDs for Testing, & Documentation Examples
|
||||||
|
const PLACEHOLDER_IPV6 = '2001:db8::3'; // From RFC3849: IPv6 Address Prefix Reserved for Documentation
|
||||||
|
const PLACEHOLDER_IPV4 = '203.0.113.42'; // From RFC5737: IPv4 Address Blocks Reserved for Documentation
|
||||||
|
|
||||||
|
foreach (array_diff(scandir(ROOT_PATH . '/fn'), ['..', '.']) as $file)
|
||||||
|
require ROOT_PATH . '/fn/' . $file;
|
||||||
|
|
||||||
|
require ROOT_PATH . '/pages.php';
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php // Test that the current setup is working
|
||||||
umask(0077);
|
|
||||||
const ROOT_PATH = __DIR__;
|
require 'init.php';
|
||||||
define('CONF', parse_ini_file(ROOT_PATH . '/config.ini', true, INI_SCANNER_TYPED));
|
|
||||||
|
|
||||||
const SFTP = '/usr/bin/sftp';
|
const SFTP = '/usr/bin/sftp';
|
||||||
const SSHPASS = '/usr/bin/sshpass';
|
const SSHPASS = '/usr/bin/sshpass';
|
||||||
|
@ -13,16 +12,10 @@ const SUFFIX = 'test.servnest.test.';
|
||||||
|
|
||||||
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
const TOR_PROXY = 'socks5h://127.0.0.1:9050';
|
||||||
|
|
||||||
const LF = "\n";
|
exescape([CONF['dns']['kdig_path'], 'torproject.org', 'AAAA'], $output, $return_code);
|
||||||
|
|
||||||
exec(CONF['dns']['kdig_path'] . ' torproject.org AAAA', $output, $return_code);
|
|
||||||
if (preg_match('/^;; Flags: qr rd ra ad;/Dm', implode("\n", $output)) !== 1)
|
if (preg_match('/^;; Flags: qr rd ra ad;/Dm', implode("\n", $output)) !== 1)
|
||||||
exit('Unable to do a DNSSEC-validated DNS query.' . LF);
|
exit('Unable to do a DNSSEC-validated DNS query.' . LF);
|
||||||
|
|
||||||
if (CONF['common']['services']['ns'] === 'rest') {
|
|
||||||
echo 'a';
|
|
||||||
}
|
|
||||||
|
|
||||||
define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt');
|
define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt');
|
||||||
|
|
||||||
function curlTest($address, $post = [], $tor = false) {
|
function curlTest($address, $post = [], $tor = false) {
|
||||||
|
@ -108,7 +101,12 @@ function testReg() {
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ns' => 'ns1.servnest.invalid.',
|
'ns' => 'ns1.servnest.invalid.',
|
||||||
]);
|
]);
|
||||||
exec(CONF['dns']['kdig_path'] . ' @' . CONF['reg']['address'] . ' ' . $domain . ' NS', $output);
|
exescape([
|
||||||
|
CONF['dns']['kdig_path'],
|
||||||
|
'@' . CONF['reg']['address'],
|
||||||
|
$domain,
|
||||||
|
'NS',
|
||||||
|
], $output);
|
||||||
if (preg_match('/[ \t]+ns1\.servnest\.invalid\.$/Dm', implode(LF, $output)) !== 1)
|
if (preg_match('/[ \t]+ns1\.servnest\.invalid\.$/Dm', implode(LF, $output)) !== 1)
|
||||||
exit('Error: /reg/ns: NS record not set' . LF);
|
exit('Error: /reg/ns: NS record not set' . LF);
|
||||||
|
|
||||||
|
@ -156,7 +154,12 @@ function testNs($domain) {
|
||||||
'tag' => 'issue',
|
'tag' => 'issue',
|
||||||
'value' => 'letsencrypt.org',
|
'value' => 'letsencrypt.org',
|
||||||
]);
|
]);
|
||||||
exec(CONF['dns']['kdig_path'] . ' @' . CONF['reg']['address'] . ' ' . $domain . ' CAA', $output);
|
exescape([
|
||||||
|
CONF['dns']['kdig_path'],
|
||||||
|
'@' . CONF['reg']['address'],
|
||||||
|
$domain,
|
||||||
|
'CAA',
|
||||||
|
], $output);
|
||||||
if (preg_match('/^' . preg_quote($domain, '/') . '[ \t]+7200[ \t]+IN[ \t]+CAA[ \t]+0[ \t]+issue[ \t]+"letsencrypt\.org"$/Dm', implode(LF, $output)) !== 1)
|
if (preg_match('/^' . preg_quote($domain, '/') . '[ \t]+7200[ \t]+IN[ \t]+CAA[ \t]+0[ \t]+issue[ \t]+"letsencrypt\.org"$/Dm', implode(LF, $output)) !== 1)
|
||||||
exit('Error: /ns/caa: CAA record not set' . LF);
|
exit('Error: /ns/caa: CAA record not set' . LF);
|
||||||
|
|
||||||
|
@ -165,15 +168,18 @@ function testNs($domain) {
|
||||||
'zone-content' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n"
|
'zone-content' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n"
|
||||||
. '@ 86400 NS ' . CONF['ns']['servers'][0] . "\r\n",
|
. '@ 86400 NS ' . CONF['ns']['servers'][0] . "\r\n",
|
||||||
]);
|
]);
|
||||||
exec(CONF['dns']['kdig_path'] . ' @' . CONF['reg']['address'] . ' aaaa.' . $domain . ' AAAA', $output);
|
exescape([
|
||||||
|
CONF['dns']['kdig_path'],
|
||||||
|
'@' . CONF['reg']['address'],
|
||||||
|
'aaaa.' . $domain,
|
||||||
|
'AAAA',
|
||||||
|
], $output);
|
||||||
if (preg_match('/[ \t]+' . preg_quote(CONF['ht']['ipv6_address'], '/') . '$/Dm', implode(LF, $output)) !== 1)
|
if (preg_match('/[ \t]+' . preg_quote(CONF['ht']['ipv6_address'], '/') . '$/Dm', implode(LF, $output)) !== 1)
|
||||||
exit('Error: /ns/edit: AAAA record not set' . LF);
|
exit('Error: /ns/edit: AAAA record not set' . LF);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testHt($username, $password) {
|
function testHt($username, $password) {
|
||||||
curlTest('/ht/', []);
|
define('TEST_CONTENT', 'test-' . bin2hex(random_bytes(16)));
|
||||||
|
|
||||||
define('TEST_CONTENT', 'test-' . random_bytes(4));
|
|
||||||
|
|
||||||
file_put_contents(sys_get_temp_dir() . '/index.html', TEST_CONTENT);
|
file_put_contents(sys_get_temp_dir() . '/index.html', TEST_CONTENT);
|
||||||
|
|
10
jobs/delete-old-testing.php
Normal file
10
jobs/delete-old-testing.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
require 'init.php';
|
||||||
|
|
||||||
|
const MAX_TESTING_ACCOUNT_AGE = 86400 * 10;
|
||||||
|
|
||||||
|
foreach (query('select', 'users', ['type' => 'testing']) as $account) {
|
||||||
|
$account_age = time() - date_create_immutable_from_format('Y-m-d H:i:s', $account['registration_date'])->getTimestamp();
|
||||||
|
if ($account_age > MAX_TESTING_ACCOUNT_AGE)
|
||||||
|
authDeleteUser($account['id']);
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-05-18 23:15+0200\n"
|
"POT-Creation-Date: 2023-06-15 01:33+0200\n"
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
|
||||||
|
@ -274,17 +274,33 @@ msgstr "Supprimer un accès"
|
||||||
msgid "Delete an existing HTTP access from a subdirectory of the SFTP space"
|
msgid "Delete an existing HTTP access from a subdirectory of the SFTP space"
|
||||||
msgstr "Retirer un accès HTTP existant d'un sous-dossier de l'espace SFTP"
|
msgstr "Retirer un accès HTTP existant d'un sous-dossier de l'espace SFTP"
|
||||||
|
|
||||||
#: router.php:137 view.php:39
|
#: pages.php:197
|
||||||
|
msgid "Manage SSH keys"
|
||||||
|
msgstr "Gérer les clés SSH"
|
||||||
|
|
||||||
|
#: pages.php:198
|
||||||
|
msgid "Choose what SSH key can edit what directory"
|
||||||
|
msgstr "Choisir quelle clé SSH peut modifier quel dossier"
|
||||||
|
|
||||||
|
#: router.php:68
|
||||||
|
msgid "This account doesn't exist anymore. Log out to end this ghost session."
|
||||||
|
msgstr "Ce compte n'existe plus. Déconnectez-vous pour terminer cette session fantôme."
|
||||||
|
|
||||||
|
#: router.php:106 view.php:39
|
||||||
msgid "This service is currently under maintenance. No action can be taken on it until an administrator finishes repairing it."
|
msgid "This service is currently under maintenance. No action can be taken on it until an administrator finishes repairing it."
|
||||||
msgstr "Ce service est en cours de maintenance. Aucune action ne peut être effectuée avant qu'ane administrataire termine de le réparer."
|
msgstr "Ce service est en cours de maintenance. Aucune action ne peut être effectuée avant qu'ane administrataire termine de le réparer."
|
||||||
|
|
||||||
#: router.php:147
|
#: router.php:115
|
||||||
msgid "You need to be logged in to do this."
|
msgid "You need to be logged in to do this."
|
||||||
msgstr "Vous devez être connecté·e à un compte pour faire cela."
|
msgstr "Vous devez être connecté·e à un compte pour faire cela."
|
||||||
|
|
||||||
#: router.php:149
|
#: view.php:19
|
||||||
msgid "This account doesn't exist anymore. Log out to end this ghost session."
|
msgid "You are using a testing account. It may be deleted anytime."
|
||||||
msgstr "Ce compte n'existe plus. Déconnectez-vous pour terminer cette session fantôme."
|
msgstr "Vous utilisez un compte de test. Il risque d'être supprimé n'importe quand."
|
||||||
|
|
||||||
|
#: view.php:19
|
||||||
|
msgid "Read more"
|
||||||
|
msgstr "En savoir plus"
|
||||||
|
|
||||||
#: view.php:21
|
#: view.php:21
|
||||||
msgid "Anonymous"
|
msgid "Anonymous"
|
||||||
|
@ -300,11 +316,16 @@ msgstr "Ce formulaire ne sera pas accepté car il faut %sse connecter%s d'abord.
|
||||||
msgid "%sSource code%s available under %s."
|
msgid "%sSource code%s available under %s."
|
||||||
msgstr "%sCode source%s disponible sous %s."
|
msgstr "%sCode source%s disponible sous %s."
|
||||||
|
|
||||||
#: fn/auth.php:110
|
#: fn/auth.php:95
|
||||||
|
#, php-format
|
||||||
|
msgid "Your account can't be deleted because the %s service is currently unavailable."
|
||||||
|
msgstr "Votre compte ne peut pas être supprimé car le service %s est actuellement indisponible."
|
||||||
|
|
||||||
|
#: fn/auth.php:143
|
||||||
msgid "Account rate limit reached, try again later."
|
msgid "Account rate limit reached, try again later."
|
||||||
msgstr "Limite de taux pour ce compte atteinte, réessayez plus tard."
|
msgstr "Limite de taux pour ce compte atteinte, réessayez plus tard."
|
||||||
|
|
||||||
#: fn/auth.php:135
|
#: fn/auth.php:168
|
||||||
msgid "Global rate limit reached, try again later."
|
msgid "Global rate limit reached, try again later."
|
||||||
msgstr "Limite de taux globale atteinte, réessayez plus tard."
|
msgstr "Limite de taux globale atteinte, réessayez plus tard."
|
||||||
|
|
||||||
|
@ -320,15 +341,15 @@ msgstr "<strong>Erreur de l'utilisataire</strong> : "
|
||||||
msgid "<strong>Server error</strong>: "
|
msgid "<strong>Server error</strong>: "
|
||||||
msgstr "<strong>Erreur du serveur</strong> : "
|
msgstr "<strong>Erreur du serveur</strong> : "
|
||||||
|
|
||||||
#: fn/common.php:129
|
#: fn/common.php:132
|
||||||
msgid "Wrong proof."
|
msgid "Wrong proof."
|
||||||
msgstr "Preuve incorrecte."
|
msgstr "Preuve incorrecte."
|
||||||
|
|
||||||
#: fn/dns.php:63
|
#: fn/dns.php:64
|
||||||
msgid "IP address malformed."
|
msgid "IP address malformed."
|
||||||
msgstr "Adresse IP malformée."
|
msgstr "Adresse IP malformée."
|
||||||
|
|
||||||
#: fn/dns.php:68 fn/ht.php:28
|
#: fn/dns.php:69 fn/ht.php:31
|
||||||
msgid "Domain malformed."
|
msgid "Domain malformed."
|
||||||
msgstr "Domaine malformé."
|
msgstr "Domaine malformé."
|
||||||
|
|
||||||
|
@ -388,11 +409,6 @@ msgid "Account deletion must be confirmed."
|
||||||
msgstr "La suppression du compte doit être confirmée."
|
msgstr "La suppression du compte doit être confirmée."
|
||||||
|
|
||||||
#: pg-act/auth/unregister.php:13
|
#: pg-act/auth/unregister.php:13
|
||||||
#, php-format
|
|
||||||
msgid "Your account can't be deleted because the %s service is currently unavailable."
|
|
||||||
msgstr "Votre compte ne peut pas être supprimé car le service %s est actuellement indisponible."
|
|
||||||
|
|
||||||
#: pg-act/auth/unregister.php:42
|
|
||||||
msgid "Account deleted."
|
msgid "Account deleted."
|
||||||
msgstr "Compte supprimé."
|
msgstr "Compte supprimé."
|
||||||
|
|
||||||
|
@ -445,6 +461,18 @@ msgstr "Ce chemin est déjà pris sur ce service. Utilisez-en un autre."
|
||||||
msgid "Access removed."
|
msgid "Access removed."
|
||||||
msgstr "Accès retiré."
|
msgstr "Accès retiré."
|
||||||
|
|
||||||
|
#: pg-act/ht/keys.php:13
|
||||||
|
msgid "Path is not valid."
|
||||||
|
msgstr "Le chemin n'est pas valide."
|
||||||
|
|
||||||
|
#: pg-act/ht/keys.php:15
|
||||||
|
msgid "Ed25519 public key seems wrongly formatted."
|
||||||
|
msgstr "La clé public Ed25519 semble mal formattée."
|
||||||
|
|
||||||
|
#: pg-act/ht/keys.php:39
|
||||||
|
msgid "SSH keys updated."
|
||||||
|
msgstr "Clés SSH mises à jour."
|
||||||
|
|
||||||
#: pg-act/ns/caa.php:25 pg-act/ns/cname.php:16 pg-act/ns/dname.php:16
|
#: pg-act/ns/caa.php:25 pg-act/ns/cname.php:16 pg-act/ns/dname.php:16
|
||||||
#: pg-act/ns/ip.php:16 pg-act/ns/loc.php:72 pg-act/ns/mx.php:20
|
#: pg-act/ns/ip.php:16 pg-act/ns/loc.php:72 pg-act/ns/mx.php:20
|
||||||
#: pg-act/ns/ns.php:16 pg-act/ns/srv.php:28 pg-act/ns/sshfp.php:25
|
#: pg-act/ns/ns.php:16 pg-act/ns/srv.php:28 pg-act/ns/sshfp.php:25
|
||||||
|
@ -664,28 +692,38 @@ msgstr "Le domaine doit avoir les enregistrements suivants pendant le traitement
|
||||||
#: pg-view/ht/add-dns.php:29 pg-view/ns/form.ns.php:8 pg-view/ns/print.php:32
|
#: pg-view/ht/add-dns.php:29 pg-view/ns/form.ns.php:8 pg-view/ns/print.php:32
|
||||||
#: pg-view/ns/zone-add.php:6 pg-view/reg/ds.php:8 pg-view/reg/glue.php:8
|
#: pg-view/ns/zone-add.php:6 pg-view/reg/ds.php:8 pg-view/reg/glue.php:8
|
||||||
#: pg-view/reg/glue.php:15 pg-view/reg/ns.php:8 pg-view/reg/print.php:2
|
#: pg-view/reg/glue.php:15 pg-view/reg/ns.php:8 pg-view/reg/print.php:2
|
||||||
#: pg-view/reg/print.php:16 pg-view/reg/register.php:7
|
#: pg-view/reg/print.php:16 pg-view/reg/register.php:11
|
||||||
#: pg-view/reg/unregister.php:6
|
#: pg-view/reg/unregister.php:6
|
||||||
msgid "Domain"
|
msgid "Domain"
|
||||||
msgstr "Domaine"
|
msgstr "Domaine"
|
||||||
|
|
||||||
#: pg-view/ht/add-dns.php:31 pg-view/ht/add-onion.php:2
|
#: pg-view/ht/add-dns.php:31 pg-view/ht/add-onion.php:2
|
||||||
#: pg-view/ht/add-subdomain.php:4 pg-view/ht/add-subpath.php:4
|
#: pg-view/ht/add-subdomain.php:8 pg-view/ht/add-subpath.php:8
|
||||||
msgid "Target directory"
|
msgid "Target directory"
|
||||||
msgstr "Dossier ciblé"
|
msgstr "Dossier ciblé"
|
||||||
|
|
||||||
#: pg-view/ht/add-dns.php:40 pg-view/ht/add-onion.php:11
|
#: pg-view/ht/add-dns.php:40 pg-view/ht/add-onion.php:11
|
||||||
#: pg-view/ht/add-subdomain.php:13 pg-view/ht/add-subpath.php:13
|
#: pg-view/ht/add-subdomain.php:17 pg-view/ht/add-subpath.php:17
|
||||||
msgid "Setup access"
|
msgid "Setup access"
|
||||||
msgstr "Créer l'accès"
|
msgstr "Créer l'accès"
|
||||||
|
|
||||||
#: pg-view/ht/add-subdomain.php:2 pg-view/ns/form.ns.php:10
|
#: pg-view/ht/add-subdomain.php:2 pg-view/reg/register.php:6
|
||||||
#: pg-view/reg/glue.php:10 pg-view/reg/register.php:9
|
#, php-format
|
||||||
|
msgid "The subdomain can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters. It can't have an hyphen (%3$s) in first, last or both third and fourth position."
|
||||||
|
msgstr "Le sous-domain peut uniquement contenir %1$s, %2$s et %3$s, et doit être entre 4 et 63 caractères. Il ne peut pas avoir un tiret (%3$s) en première, dernière ou à la fois troisième et quatrième position."
|
||||||
|
|
||||||
|
#: pg-view/ht/add-subdomain.php:6 pg-view/ns/form.ns.php:10
|
||||||
|
#: pg-view/reg/glue.php:10 pg-view/reg/register.php:13
|
||||||
#: pg-view/reg/transfer.php:9
|
#: pg-view/reg/transfer.php:9
|
||||||
msgid "Subdomain"
|
msgid "Subdomain"
|
||||||
msgstr "Sous-domaine"
|
msgstr "Sous-domaine"
|
||||||
|
|
||||||
#: pg-view/ht/add-subpath.php:2
|
#: pg-view/ht/add-subpath.php:2
|
||||||
|
#, php-format
|
||||||
|
msgid "The path can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters."
|
||||||
|
msgstr "Le chemin peut uniquement contenir %1$s, %2$s et %3$s, et doit être entre 4 et 63 caractères."
|
||||||
|
|
||||||
|
#: pg-view/ht/add-subpath.php:6
|
||||||
msgid "Path"
|
msgid "Path"
|
||||||
msgstr "Chemin"
|
msgstr "Chemin"
|
||||||
|
|
||||||
|
@ -795,6 +833,30 @@ msgstr "Approuvé"
|
||||||
msgid "Stable Let's Encrypt certificates"
|
msgid "Stable Let's Encrypt certificates"
|
||||||
msgstr "Vrai certificat Let's Encrypt"
|
msgstr "Vrai certificat Let's Encrypt"
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:2
|
||||||
|
msgid "In addition to your password, you can also access your SFTP space using Ed25519 SSH keys. A key can be granted modification rights to the full space (<code>/</code>) or to any arbitrary subdirectory. A key is always allowed to list any directory content."
|
||||||
|
msgstr "En plus de la clé de passe, c'est également possible d'accéder à l'espace SFTP en utilisant des clés SSH Ed25519. Une clé peut être autorisée à modifier dans tout l'espace (<code>/</code>) ou dans un quelconque sous-dossier spécifique. Une clé est toujours autorisée à lister le contenu de n'importe quel dossier."
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:17
|
||||||
|
msgid "Add new SSH key access"
|
||||||
|
msgstr "Ajouter un nouvel accès par clé SSH"
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:17
|
||||||
|
msgid "SSH key access"
|
||||||
|
msgstr "Accès par clé SSH"
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:19
|
||||||
|
msgid "Public key"
|
||||||
|
msgstr "Clé publique"
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:23
|
||||||
|
msgid "Allowed directory"
|
||||||
|
msgstr "Dossier autorisé"
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:30
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Mettre à jour"
|
||||||
|
|
||||||
#: pg-view/ns/caa.php:3
|
#: pg-view/ns/caa.php:3
|
||||||
msgid "Flag"
|
msgid "Flag"
|
||||||
msgstr "Flag"
|
msgstr "Flag"
|
||||||
|
@ -1118,18 +1180,18 @@ msgid "Nobody can register a domain under these suffixes:"
|
||||||
msgstr "Personne ne peut enregistrer un domain sous ces suffixes :"
|
msgstr "Personne ne peut enregistrer un domain sous ces suffixes :"
|
||||||
|
|
||||||
#: pg-view/reg/register.php:2
|
#: pg-view/reg/register.php:2
|
||||||
msgid "Register a new domain on your account. It must consist of between 4 and 63 letters and digits."
|
msgid "Register a new domain on your account."
|
||||||
msgstr "Enregistrer un nouveau domaine sur son compte. Il doit être composé d'entre 4 et 63 lettres et chiffres."
|
msgstr "Enregistrer un nouveau domaine sur son compte."
|
||||||
|
|
||||||
#: pg-view/reg/register.php:14 pg-view/reg/transfer.php:14
|
#: pg-view/reg/register.php:18 pg-view/reg/transfer.php:14
|
||||||
msgid "Suffix"
|
msgid "Suffix"
|
||||||
msgstr "Suffixe"
|
msgstr "Suffixe"
|
||||||
|
|
||||||
#: pg-view/reg/register.php:27
|
#: pg-view/reg/register.php:31
|
||||||
msgid "Check availability"
|
msgid "Check availability"
|
||||||
msgstr "Vérifier sa disponibilité"
|
msgstr "Vérifier sa disponibilité"
|
||||||
|
|
||||||
#: pg-view/reg/register.php:29
|
#: pg-view/reg/register.php:33
|
||||||
msgid "Register"
|
msgid "Register"
|
||||||
msgstr "Enregistrer"
|
msgstr "Enregistrer"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-05-18 23:15+0200\n"
|
"POT-Creation-Date: 2023-06-15 01:33+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -286,16 +286,32 @@ msgstr ""
|
||||||
msgid "Delete an existing HTTP access from a subdirectory of the SFTP space"
|
msgid "Delete an existing HTTP access from a subdirectory of the SFTP space"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: router.php:137 view.php:39
|
#: pages.php:197
|
||||||
|
msgid "Manage SSH keys"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pages.php:198
|
||||||
|
msgid "Choose what SSH key can edit what directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: router.php:68
|
||||||
|
msgid "This account doesn't exist anymore. Log out to end this ghost session."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: router.php:106 view.php:39
|
||||||
msgid "This service is currently under maintenance. No action can be taken on it until an administrator finishes repairing it."
|
msgid "This service is currently under maintenance. No action can be taken on it until an administrator finishes repairing it."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: router.php:147
|
#: router.php:115
|
||||||
msgid "You need to be logged in to do this."
|
msgid "You need to be logged in to do this."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: router.php:149
|
#: view.php:19
|
||||||
msgid "This account doesn't exist anymore. Log out to end this ghost session."
|
msgid "You are using a testing account. It may be deleted anytime."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: view.php:19
|
||||||
|
msgid "Read more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: view.php:21
|
#: view.php:21
|
||||||
|
@ -312,11 +328,16 @@ msgstr ""
|
||||||
msgid "%sSource code%s available under %s."
|
msgid "%sSource code%s available under %s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fn/auth.php:110
|
#: fn/auth.php:95
|
||||||
|
#, php-format
|
||||||
|
msgid "Your account can't be deleted because the %s service is currently unavailable."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: fn/auth.php:143
|
||||||
msgid "Account rate limit reached, try again later."
|
msgid "Account rate limit reached, try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fn/auth.php:135
|
#: fn/auth.php:168
|
||||||
msgid "Global rate limit reached, try again later."
|
msgid "Global rate limit reached, try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -332,15 +353,15 @@ msgstr ""
|
||||||
msgid "<strong>Server error</strong>: "
|
msgid "<strong>Server error</strong>: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fn/common.php:129
|
#: fn/common.php:132
|
||||||
msgid "Wrong proof."
|
msgid "Wrong proof."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fn/dns.php:63
|
#: fn/dns.php:64
|
||||||
msgid "IP address malformed."
|
msgid "IP address malformed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fn/dns.php:68 fn/ht.php:28
|
#: fn/dns.php:69 fn/ht.php:31
|
||||||
msgid "Domain malformed."
|
msgid "Domain malformed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -400,11 +421,6 @@ msgid "Account deletion must be confirmed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-act/auth/unregister.php:13
|
#: pg-act/auth/unregister.php:13
|
||||||
#, php-format
|
|
||||||
msgid "Your account can't be deleted because the %s service is currently unavailable."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pg-act/auth/unregister.php:42
|
|
||||||
msgid "Account deleted."
|
msgid "Account deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -457,6 +473,18 @@ msgstr ""
|
||||||
msgid "Access removed."
|
msgid "Access removed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-act/ht/keys.php:13
|
||||||
|
msgid "Path is not valid."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-act/ht/keys.php:15
|
||||||
|
msgid "Ed25519 public key seems wrongly formatted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-act/ht/keys.php:39
|
||||||
|
msgid "SSH keys updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: pg-act/ns/caa.php:25 pg-act/ns/cname.php:16 pg-act/ns/dname.php:16
|
#: pg-act/ns/caa.php:25 pg-act/ns/cname.php:16 pg-act/ns/dname.php:16
|
||||||
#: pg-act/ns/ip.php:16 pg-act/ns/loc.php:72 pg-act/ns/mx.php:20
|
#: pg-act/ns/ip.php:16 pg-act/ns/loc.php:72 pg-act/ns/mx.php:20
|
||||||
#: pg-act/ns/ns.php:16 pg-act/ns/srv.php:28 pg-act/ns/sshfp.php:25
|
#: pg-act/ns/ns.php:16 pg-act/ns/srv.php:28 pg-act/ns/sshfp.php:25
|
||||||
|
@ -676,28 +704,38 @@ msgstr ""
|
||||||
#: pg-view/ht/add-dns.php:29 pg-view/ns/form.ns.php:8 pg-view/ns/print.php:32
|
#: pg-view/ht/add-dns.php:29 pg-view/ns/form.ns.php:8 pg-view/ns/print.php:32
|
||||||
#: pg-view/ns/zone-add.php:6 pg-view/reg/ds.php:8 pg-view/reg/glue.php:8
|
#: pg-view/ns/zone-add.php:6 pg-view/reg/ds.php:8 pg-view/reg/glue.php:8
|
||||||
#: pg-view/reg/glue.php:15 pg-view/reg/ns.php:8 pg-view/reg/print.php:2
|
#: pg-view/reg/glue.php:15 pg-view/reg/ns.php:8 pg-view/reg/print.php:2
|
||||||
#: pg-view/reg/print.php:16 pg-view/reg/register.php:7
|
#: pg-view/reg/print.php:16 pg-view/reg/register.php:11
|
||||||
#: pg-view/reg/unregister.php:6
|
#: pg-view/reg/unregister.php:6
|
||||||
msgid "Domain"
|
msgid "Domain"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/ht/add-dns.php:31 pg-view/ht/add-onion.php:2
|
#: pg-view/ht/add-dns.php:31 pg-view/ht/add-onion.php:2
|
||||||
#: pg-view/ht/add-subdomain.php:4 pg-view/ht/add-subpath.php:4
|
#: pg-view/ht/add-subdomain.php:8 pg-view/ht/add-subpath.php:8
|
||||||
msgid "Target directory"
|
msgid "Target directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/ht/add-dns.php:40 pg-view/ht/add-onion.php:11
|
#: pg-view/ht/add-dns.php:40 pg-view/ht/add-onion.php:11
|
||||||
#: pg-view/ht/add-subdomain.php:13 pg-view/ht/add-subpath.php:13
|
#: pg-view/ht/add-subdomain.php:17 pg-view/ht/add-subpath.php:17
|
||||||
msgid "Setup access"
|
msgid "Setup access"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/ht/add-subdomain.php:2 pg-view/ns/form.ns.php:10
|
#: pg-view/ht/add-subdomain.php:2 pg-view/reg/register.php:6
|
||||||
#: pg-view/reg/glue.php:10 pg-view/reg/register.php:9
|
#, php-format
|
||||||
|
msgid "The subdomain can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters. It can't have an hyphen (%3$s) in first, last or both third and fourth position."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/add-subdomain.php:6 pg-view/ns/form.ns.php:10
|
||||||
|
#: pg-view/reg/glue.php:10 pg-view/reg/register.php:13
|
||||||
#: pg-view/reg/transfer.php:9
|
#: pg-view/reg/transfer.php:9
|
||||||
msgid "Subdomain"
|
msgid "Subdomain"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/ht/add-subpath.php:2
|
#: pg-view/ht/add-subpath.php:2
|
||||||
|
#, php-format
|
||||||
|
msgid "The path can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/add-subpath.php:6
|
||||||
msgid "Path"
|
msgid "Path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -807,6 +845,30 @@ msgstr ""
|
||||||
msgid "Stable Let's Encrypt certificates"
|
msgid "Stable Let's Encrypt certificates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:2
|
||||||
|
msgid "In addition to your password, you can also access your SFTP space using Ed25519 SSH keys. A key can be granted modification rights to the full space (<code>/</code>) or to any arbitrary subdirectory. A key is always allowed to list any directory content."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:17
|
||||||
|
msgid "Add new SSH key access"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:17
|
||||||
|
msgid "SSH key access"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:19
|
||||||
|
msgid "Public key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:23
|
||||||
|
msgid "Allowed directory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pg-view/ht/keys.php:30
|
||||||
|
msgid "Update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/ns/caa.php:3
|
#: pg-view/ns/caa.php:3
|
||||||
msgid "Flag"
|
msgid "Flag"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -1130,18 +1192,18 @@ msgid "Nobody can register a domain under these suffixes:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/reg/register.php:2
|
#: pg-view/reg/register.php:2
|
||||||
msgid "Register a new domain on your account. It must consist of between 4 and 63 letters and digits."
|
msgid "Register a new domain on your account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/reg/register.php:14 pg-view/reg/transfer.php:14
|
#: pg-view/reg/register.php:18 pg-view/reg/transfer.php:14
|
||||||
msgid "Suffix"
|
msgid "Suffix"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/reg/register.php:27
|
#: pg-view/reg/register.php:31
|
||||||
msgid "Check availability"
|
msgid "Check availability"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pg-view/reg/register.php:29
|
#: pg-view/reg/register.php:33
|
||||||
msgid "Register"
|
msgid "Register"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -193,5 +193,10 @@ define('PAGES', [
|
||||||
'title' => _('Delete access'),
|
'title' => _('Delete access'),
|
||||||
'description' => _('Delete an existing HTTP access from a subdirectory of the SFTP space'),
|
'description' => _('Delete an existing HTTP access from a subdirectory of the SFTP space'),
|
||||||
],
|
],
|
||||||
|
'keys' => [
|
||||||
|
'title' => _('Manage SSH keys'),
|
||||||
|
'description' => _('Choose what SSH key can edit what directory'),
|
||||||
|
'tokens_account_cost' => 300,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -26,4 +26,3 @@ $_SESSION['type'] = query('select', 'users', ['id' => $id], 'type')[0];
|
||||||
setupDisplayUsername($_POST['username']);
|
setupDisplayUsername($_POST['username']);
|
||||||
|
|
||||||
redir();
|
redir();
|
||||||
|
|
||||||
|
|
|
@ -6,36 +6,7 @@ if (checkPassword($_SESSION['id'], $_POST['current-password']) !== true)
|
||||||
if (!isset($_POST['delete']))
|
if (!isset($_POST['delete']))
|
||||||
output(403, _('Account deletion must be confirmed.'));
|
output(403, _('Account deletion must be confirmed.'));
|
||||||
|
|
||||||
$user_services = explode(',', query('select', 'users', ['id' => $_SESSION['id']], 'services')[0]);
|
authDeleteUser($_SESSION['id']);
|
||||||
|
|
||||||
foreach (SERVICES_USER as $service)
|
|
||||||
if (in_array($service, $user_services, true) AND CONF['common']['services'][$service] !== 'enabled')
|
|
||||||
output(503, sprintf(_('Your account can\'t be deleted because the %s service is currently unavailable.'), '<em>' . PAGES[$service]['index']['title'] . '</em>'));
|
|
||||||
|
|
||||||
if (in_array('reg', $user_services, true))
|
|
||||||
foreach (query('select', 'registry', ['username' => $_SESSION['id']], 'domain') as $domain)
|
|
||||||
regDeleteDomain($domain);
|
|
||||||
|
|
||||||
if (in_array('ns', $user_services, true))
|
|
||||||
foreach (query('select', 'zones', ['username' => $_SESSION['id']], 'zone') as $zone)
|
|
||||||
nsDeleteZone($zone);
|
|
||||||
|
|
||||||
if (in_array('ht', $user_services, true)) {
|
|
||||||
foreach (query('select', 'sites', ['username' => $_SESSION['id']]) as $site)
|
|
||||||
htDeleteSite($site['address'], $site['type']);
|
|
||||||
|
|
||||||
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'], result_code: $code);
|
|
||||||
if ($code !== 0)
|
|
||||||
output(500, 'Can\'t remove Tor keys directory.');
|
|
||||||
|
|
||||||
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id']);
|
|
||||||
|
|
||||||
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['ht_path'] . '/fs/' . $_SESSION['id'], result_code: $code);
|
|
||||||
if ($code !== 0)
|
|
||||||
output(500, 'Can\'t remove user\'s directory.');
|
|
||||||
}
|
|
||||||
|
|
||||||
query('delete', 'users', ['id' => $_SESSION['id']]);
|
|
||||||
|
|
||||||
logout();
|
logout();
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,14 @@ checkAuthToken($matches[1], $matches[2]);
|
||||||
|
|
||||||
rateLimit();
|
rateLimit();
|
||||||
|
|
||||||
exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' certonly' . (($_SESSION['type'] === 'approved') ? '' : ' --test-cert') . ' --domain ' . $_POST['domain'], $output, $returnCode);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
CONF['ht']['certbot_path'],
|
||||||
|
'certonly',
|
||||||
|
'--domain',
|
||||||
|
$_POST['domain'],
|
||||||
|
...(($_SESSION['type'] === 'approved') ? [] : ['--test-cert']),
|
||||||
|
], $output, $returnCode);
|
||||||
if ($returnCode !== 0)
|
if ($returnCode !== 0)
|
||||||
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
|
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,29 @@ if (chmod($torConfFile, 0644) !== true)
|
||||||
output(500, 'Failed to give correct permissions to new Tor configuration file.');
|
output(500, 'Failed to give correct permissions to new Tor configuration file.');
|
||||||
|
|
||||||
// Reload Tor
|
// Reload Tor
|
||||||
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['tor_reload_cmd'], result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'--',
|
||||||
|
...explode(' ', CONF['ht']['tor_reload_cmd']),
|
||||||
|
], result_code: $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Failed to reload Tor.');
|
output(500, 'Failed to reload Tor.');
|
||||||
|
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
|
|
||||||
// Get the hostname generated by Tor
|
// Get the hostname generated by Tor
|
||||||
$onion = exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['cat_path'] . ' ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/hostname', result_code: $code);
|
exescape([
|
||||||
|
CONF['ht']['sudo_path'],
|
||||||
|
'-u',
|
||||||
|
CONF['ht']['tor_user'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['cat_path'],
|
||||||
|
'--',
|
||||||
|
CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/hostname',
|
||||||
|
], $output, $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Unable to read hostname file.');
|
output(500, 'Unable to read hostname file.');
|
||||||
|
$onion = $output[0];
|
||||||
if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
|
if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
|
||||||
output(500, 'No onion address found.');
|
output(500, 'No onion address found.');
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
if (dirsStatuses('subdomain')[$_POST['dir']] !== false)
|
if (dirsStatuses('subdomain')[$_POST['dir']] !== false)
|
||||||
output(403, 'Wrong value for <code>dir</code>.');
|
output(403, 'Wrong value for <code>dir</code>.');
|
||||||
|
|
||||||
if (preg_match('/^[a-z0-9]{1,32}$/D', $_POST['subdomain']) !== 1)
|
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['subdomain']) !== 1)
|
||||||
output(403, _('Invalid domain label.'));
|
output(403, _('Invalid domain label.'));
|
||||||
|
|
||||||
if (query('select', 'sites', ['address' => $_POST['subdomain'], 'type' => 'subdomain']) !== [])
|
if (query('select', 'sites', ['address' => $_POST['subdomain'], 'type' => 'subdomain']) !== [])
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
if (dirsStatuses('subpath')[$_POST['dir']] !== false)
|
if (dirsStatuses('subpath')[$_POST['dir']] !== false)
|
||||||
output(403, 'Wrong value for <code>dir</code>.');
|
output(403, 'Wrong value for <code>dir</code>.');
|
||||||
|
|
||||||
if (preg_match('/^[a-z0-9]{1,32}$/D', $_POST['path']) !== 1)
|
if (preg_match('/' . SUBPATH_REGEX . '/D', $_POST['path']) !== 1)
|
||||||
output(403, _('Invalid path.'));
|
output(403, _('Invalid path.'));
|
||||||
|
|
||||||
if (query('select', 'sites', ['address' => $_POST['path'], 'type' => 'subpath']) !== [])
|
if (query('select', 'sites', ['address' => $_POST['path'], 'type' => 'subpath']) !== [])
|
||||||
|
|
|
@ -6,6 +6,6 @@ if (preg_match('/^(?<type>subpath|subdomain|onion|dns):(?<address>[a-z0-9._-]{1,
|
||||||
if (isset(query('select', 'sites', ['username' => $_SESSION['id'], 'address' => $site['address'], 'type' => $site['type']], 'address')[0]) !== true)
|
if (isset(query('select', 'sites', ['username' => $_SESSION['id'], 'address' => $site['address'], 'type' => $site['type']], 'address')[0]) !== true)
|
||||||
output(403, 'Unavailable value for <code>site</code>.');
|
output(403, 'Unavailable value for <code>site</code>.');
|
||||||
|
|
||||||
htDeleteSite($site['address'], $site['type']);
|
htDeleteSite($site['address'], $site['type'], $_SESSION['id']);
|
||||||
|
|
||||||
output(200, _('Access removed.'));
|
output(200, _('Access removed.'));
|
||||||
|
|
39
pg-act/ht/keys.php
Executable file
39
pg-act/ht/keys.php
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$el_nb = count($_POST['keys']);
|
||||||
|
if ($el_nb < 1 OR $el_nb > 8)
|
||||||
|
output(403, 'Wrong elements number.');
|
||||||
|
|
||||||
|
foreach ($_POST['keys'] as $i => $key) {
|
||||||
|
if (($key['public-key'] ?? '') === '') {
|
||||||
|
unset($_POST['keys'][$i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('#^/[/\p{L}\{M}\p{N}\p{P}\p{S}\p{Zs}]{1,254}$#Du', $key['dir'] ?? '') !== 1)
|
||||||
|
output(403, _('Path is not valid.'));
|
||||||
|
if (preg_match('#' . ED25519_PUBKEY_REGEX . '#D', $key['public-key']) !== 1)
|
||||||
|
output(403, _('Ed25519 public key seems wrongly formatted.'));
|
||||||
|
}
|
||||||
|
$keys = array_values($_POST['keys']);
|
||||||
|
|
||||||
|
rateLimit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB->beginTransaction();
|
||||||
|
|
||||||
|
query('delete', 'ssh-keys', ['username' => $_SESSION['id']]);
|
||||||
|
|
||||||
|
foreach ($keys as $key)
|
||||||
|
insert('ssh-keys', [
|
||||||
|
'key' => $key['public-key'],
|
||||||
|
'username' => $_SESSION['id'],
|
||||||
|
'directory' => $key['dir'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB->commit();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
DB->rollback();
|
||||||
|
output(500, 'Database error.', [$e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
output(200, _('SSH keys updated.'));
|
|
@ -39,22 +39,22 @@ if (isset($_POST['zone-content'])) { // Update zone
|
||||||
|
|
||||||
ratelimit();
|
ratelimit();
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-freeze ' . $_POST['zone'], $output, $return_code);
|
knotc(['zone-freeze', $_POST['zone']], $output, $return_code);
|
||||||
if ($return_code !== 0)
|
if ($return_code !== 0)
|
||||||
output(500, 'Failed to freeze zone file.', $output);
|
output(500, 'Failed to freeze zone file.', $output);
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-flush ' . $_POST['zone'], $output, $return_code);
|
knotc(['zone-flush', $_POST['zone']], $output, $return_code);
|
||||||
if ($return_code !== 0)
|
if ($return_code !== 0)
|
||||||
output(500, 'Failed to flush zone file.', $output);
|
output(500, 'Failed to flush zone file.', $output);
|
||||||
|
|
||||||
if (file_put_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone', $new_zone_content) === false)
|
if (file_put_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone', $new_zone_content) === false)
|
||||||
output(500, 'Failed to write zone file.');
|
output(500, 'Failed to write zone file.');
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-reload ' . $_POST['zone'], $output, $return_code);
|
knotc(['zone-reload', $_POST['zone']], $output, $return_code);
|
||||||
if ($return_code !== 0)
|
if ($return_code !== 0)
|
||||||
output(500, 'Failed to reload zone file.', $output);
|
output(500, 'Failed to reload zone file.', $output);
|
||||||
|
|
||||||
exec(CONF['dns']['knotc_path'] . ' --blocking --timeout 3 zone-thaw ' . $_POST['zone'], $output, $return_code);
|
knotc(['zone-thaw', $_POST['zone']], $output, $return_code);
|
||||||
if ($return_code !== 0)
|
if ($return_code !== 0)
|
||||||
output(500, 'Failed to thaw zone file.', $output);
|
output(500, 'Failed to thaw zone file.', $output);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,13 @@ $_POST['domain'] = formatAbsoluteDomain($_POST['domain']);
|
||||||
if (query('select', 'zones', ['zone' => $_POST['domain']], 'zone') !== [])
|
if (query('select', 'zones', ['zone' => $_POST['domain']], 'zone') !== [])
|
||||||
output(403, _('This zone already exists on the service.'));
|
output(403, _('This zone already exists on the service.'));
|
||||||
|
|
||||||
exec(CONF['dns']['kdig_path'] . ' ' . ltrim(strstr($_POST['domain'], '.'), '.') . ' NS +short' . (CONF['ns']['local_only_check'] ? (' @' . CONF['reg']['address']) : ''), $parentAuthoritatives, $code);
|
exescape([
|
||||||
|
CONF['dns']['kdig_path'],
|
||||||
|
ltrim(strstr($_POST['domain'], '.'), '.'),
|
||||||
|
'NS',
|
||||||
|
'+short',
|
||||||
|
...(CONF['ns']['local_only_check'] ? ['@' . CONF['reg']['address']] : []),
|
||||||
|
], $parentAuthoritatives, $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Unable to query parent name servers.');
|
output(500, 'Unable to query parent name servers.');
|
||||||
if ($parentAuthoritatives === [])
|
if ($parentAuthoritatives === [])
|
||||||
|
@ -13,7 +19,13 @@ if ($parentAuthoritatives === [])
|
||||||
foreach ($parentAuthoritatives as $parentAuthoritative)
|
foreach ($parentAuthoritatives as $parentAuthoritative)
|
||||||
checkAbsoluteDomainFormat($parentAuthoritative);
|
checkAbsoluteDomainFormat($parentAuthoritative);
|
||||||
|
|
||||||
exec(CONF['dns']['kdig_path'] . ' ' . $_POST['domain'] . ' NS @' . (CONF['ns']['local_only_check'] ? CONF['reg']['address'] : $parentAuthoritatives[0]) . ' +noidn', $results);
|
exescape([
|
||||||
|
CONF['dns']['kdig_path'],
|
||||||
|
$_POST['domain'],
|
||||||
|
'NS',
|
||||||
|
'@' . (CONF['ns']['local_only_check'] ? CONF['reg']['address'] : $parentAuthoritatives[0]),
|
||||||
|
'+noidn'
|
||||||
|
], $results);
|
||||||
if (preg_match('/^' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._domain-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
|
if (preg_match('/^' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._domain-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
|
||||||
output(403, _('NS authentication record not found.'));
|
output(403, _('NS authentication record not found.'));
|
||||||
|
|
||||||
|
@ -47,8 +59,8 @@ if (chmod($knotZonePath, 0660) !== true)
|
||||||
output(500, 'Failed to chmod new zone file.');
|
output(500, 'Failed to chmod new zone file.');
|
||||||
|
|
||||||
knotcConfExec([
|
knotcConfExec([
|
||||||
"set 'zone[" . $_POST['domain'] . "]'",
|
['conf-set', 'zone[' . $_POST['domain'] . ']'],
|
||||||
"set 'zone[" . $_POST['domain'] . "].template' 'servnest'",
|
['conf-set', 'zone[' . $_POST['domain'] . '].template', 'servnest'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
output(200, _('Zone created.'));
|
output(200, _('Zone created.'));
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
nsCheckZonePossession($_POST['zone']);
|
nsCheckZonePossession($_POST['zone']);
|
||||||
|
|
||||||
nsDeleteZone($_POST['zone']);
|
nsDeleteZone($_POST['zone'], $_SESSION['id']);
|
||||||
|
|
||||||
output(200, _('Zone deleted.'));
|
output(200, _('Zone deleted.'));
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (
|
if (!in_array($_POST['algo'], ['8', '13', '14', '15', '16'], true))
|
||||||
($_POST['algo'] !== '8')
|
output(403, 'Wrong value for <code>algo</code>.');
|
||||||
AND ($_POST['algo'] !== '13')
|
|
||||||
AND ($_POST['algo'] !== '14')
|
|
||||||
AND ($_POST['algo'] !== '15')
|
|
||||||
AND ($_POST['algo'] !== '16')
|
|
||||||
) output(403, 'Wrong value for <code>algo</code>.');
|
|
||||||
|
|
||||||
$_POST['keytag'] = intval($_POST['keytag']);
|
$_POST['keytag'] = intval($_POST['keytag']);
|
||||||
if ((!preg_match('/^[0-9]{1,6}$/D', $_POST['keytag'])) OR !($_POST['keytag'] >= 1) OR !($_POST['keytag'] <= 65535))
|
if ((preg_match('/^[0-9]{1,6}$/D', $_POST['keytag'])) !== 1 OR !($_POST['keytag'] >= 1) OR !($_POST['keytag'] <= 65535))
|
||||||
output(403, 'Wrong value for <code>keytag</code>.');
|
output(403, 'Wrong value for <code>keytag</code>.');
|
||||||
|
|
||||||
if ($_POST['dt'] !== '2' AND $_POST['dt'] !== '4')
|
if ($_POST['dt'] !== '2' AND $_POST['dt'] !== '4')
|
||||||
output(403, 'Wrong value for <code>dt</code>.');
|
output(403, 'Wrong value for <code>dt</code>.');
|
||||||
|
|
||||||
|
if (preg_match('/^(?:[0-9a-fA-F]{64}|[0-9a-fA-F]{96})$/D', $_POST['key']) !== 1)
|
||||||
|
output(403, 'Wrong value for <code>key</code>.');
|
||||||
|
|
||||||
regCheckDomainPossession($_POST['zone']);
|
regCheckDomainPossession($_POST['zone']);
|
||||||
|
|
||||||
rateLimit();
|
rateLimit();
|
||||||
|
|
|
@ -11,7 +11,13 @@ $domain = formatAbsoluteDomain($_POST['subdomain'] . '.' . $_POST['suffix']);
|
||||||
if (query('select', 'registry', ['username' => $_SESSION['id'], 'domain' => $domain], 'domain') !== [])
|
if (query('select', 'registry', ['username' => $_SESSION['id'], 'domain' => $domain], 'domain') !== [])
|
||||||
output(403, _('The current account already owns this domain.'));
|
output(403, _('The current account already owns this domain.'));
|
||||||
|
|
||||||
exec(CONF['dns']['kdig_path'] . ' ' . $domain . ' NS @' . CONF['reg']['address'] . ' +noidn', $results, $code);
|
exescape([
|
||||||
|
CONF['dns']['kdig_path'],
|
||||||
|
$domain,
|
||||||
|
'NS',
|
||||||
|
'@' . CONF['reg']['address'],
|
||||||
|
'+noidn',
|
||||||
|
], $results, $code);
|
||||||
if ($code !== 0)
|
if ($code !== 0)
|
||||||
output(500, 'Unable to query registry\'s name servers.');
|
output(500, 'Unable to query registry\'s name servers.');
|
||||||
if (preg_match('/^' . preg_quote($domain, '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._transfer-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
|
if (preg_match('/^' . preg_quote($domain, '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._transfer-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
regCheckDomainPossession($_POST['domain']);
|
regCheckDomainPossession($_POST['domain']);
|
||||||
|
|
||||||
regDeleteDomain($_POST['domain']);
|
regDeleteDomain($_POST['domain'], $_SESSION['id']);
|
||||||
|
|
||||||
output(200, _('Domain unregistered.'));
|
output(200, _('Domain unregistered.'));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php displayIndex(); ?>
|
<?php displayIndex(); ?>
|
||||||
|
|
||||||
<h2><?= _('Account type') ?></h2>
|
<h2 id="type"><?= _('Account type') ?></h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<?php
|
<?php
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
<p>
|
||||||
|
<?= sprintf(_('The subdomain can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters. It can\'t have an hyphen (%3$s) in first, last or both third and fourth position.'), '<abbr title="abcdefghijklmnopqrstuvwxyz"><code>a</code>-<code>z</code></abbr>', '<abbr title="0123456789"><code>0</code>-<code>9</code></abbr>', '<code>-</code>') ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<label for="subdomain"><?= _('Subdomain') ?></label><br>
|
<label for="subdomain"><?= _('Subdomain') ?></label><br>
|
||||||
<input required="" placeholder="label" id="subdomain" name="subdomain" type="text"><code>.<?= CONF['ht']['subdomain_domain'] ?></code><br>
|
<input required="" placeholder="label" id="subdomain" name="subdomain" type="text"><code>.<?= CONF['ht']['subdomain_domain'] ?></code><br>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
<p>
|
||||||
|
<?= sprintf(_('The path can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters.'), '<abbr title="abcdefghijklmnopqrstuvwxyz"><code>a</code>-<code>z</code></abbr>', '<abbr title="0123456789"><code>0</code>-<code>9</code></abbr>', '<code>-</code>') ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<label for="path"><?= _('Path') ?></label><br>
|
<label for="path"><?= _('Path') ?></label><br>
|
||||||
<code>https://<?= CONF['ht']['subpath_domain'] ?>/</code><input required="" placeholder="path" id="path" name="path" type="text"><br>
|
<code>https://<?= CONF['ht']['subpath_domain'] ?>/</code><input required="" pattern="<?= SUBPATH_REGEX ?>" placeholder="path" id="path" name="path" type="text"><br>
|
||||||
<label for="dir"><?= _('Target directory') ?></label><br>
|
<label for="dir"><?= _('Target directory') ?></label><br>
|
||||||
<select required="" name="dir" id="dir">
|
<select required="" name="dir" id="dir">
|
||||||
<option value="" disabled="" selected="">—</option>
|
<option value="" disabled="" selected="">—</option>
|
||||||
|
|
31
pg-view/ht/keys.php
Executable file
31
pg-view/ht/keys.php
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
<p>
|
||||||
|
<?= _('In addition to your password, you can also access your SFTP space using Ed25519 SSH keys. A key can be granted modification rights to the full space (<code>/</code>) or to any arbitrary subdirectory. A key is always allowed to list any directory content.') ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<datalist id="dirs">
|
||||||
|
<option value="/"></option>
|
||||||
|
<?php
|
||||||
|
foreach (listFsDirs($_SESSION['id'] ?? '') as $dir)
|
||||||
|
echo ' <option value="/' . $dir . '"></option>' . LF;
|
||||||
|
?>
|
||||||
|
</datalist>
|
||||||
|
<?php
|
||||||
|
foreach (array_slice(array_merge(query('select', 'ssh-keys', ['username' => $_SESSION['id'] ?? '']), [['key' => '', 'username' => '', 'directory' => '/']]), 0, 8) as $i => $ssh_key) {
|
||||||
|
?>
|
||||||
|
<fieldset>
|
||||||
|
<legend><?= ($ssh_key['key'] === '') ? _('Add new SSH key access') : _('SSH key access') ?></legend>
|
||||||
|
<div>
|
||||||
|
<label for="public-key"><?= _('Public key') ?></label><br>
|
||||||
|
<code>ssh-ed15519 <input pattern="<?= ED25519_PUBKEY_REGEX ?>" placeholder="AAAAC3NzaC1lZDI1NTE5AAAAI<?= substr(base64_encode(random_bytes(32)), 0, 43) ?>" id="public-key" name="keys[<?= $i ?>][public-key]" value="<?= $ssh_key['key'] ?>" type="text"></code>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="dir"><?= _('Allowed directory') ?></label><br>
|
||||||
|
<input list="dirs" placeholder="/" value="<?= htmlspecialchars($ssh_key['directory']) ?>" id="dir" name="keys[<?= $i ?>][dir]" type="text">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<input type="submit" value="<?= _('Update') ?>">
|
||||||
|
</form>
|
|
@ -51,7 +51,7 @@ foreach (regListUserDomains() as $domain)
|
||||||
<br>
|
<br>
|
||||||
<label for="key"><?= _('Key') ?></label>
|
<label for="key"><?= _('Key') ?></label>
|
||||||
<br>
|
<br>
|
||||||
<input id="key" required="" name="key" type="text" placeholder="018F25E4A022463478C9E30136EC53771A1704A0F0B3CE5B883AC9C8A6A55D16B638B4DE70662ACA5295D3669E7CADD9">
|
<input id="key" required="" name="key" type="text" pattern="^([0-9a-fA-F]{64}|[0-9a-fA-F]{96})$" placeholder="<?= strtoupper(bin2hex(random_bytes(32))) ?>">
|
||||||
<br>
|
<br>
|
||||||
<input type="submit" value="<?= _('Apply') ?>">
|
<input type="submit" value="<?= _('Apply') ?>">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<p>
|
<p>
|
||||||
<?= _('Register a new domain on your account. It must consist of between 4 and 63 letters and digits.') ?>
|
<?= _('Register a new domain on your account.') ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<?= sprintf(_('The subdomain can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters. It can\'t have an hyphen (%3$s) in first, last or both third and fourth position.'), '<abbr title="abcdefghijklmnopqrstuvwxyz"><code>a</code>-<code>z</code></abbr>', '<abbr title="0123456789"><code>0</code>-<code>9</code></abbr>', '<code>-</code>') ?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
|
59
router.php
59
router.php
|
@ -1,40 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
umask(0077);
|
require 'init.php';
|
||||||
const ROOT_PATH = __DIR__;
|
|
||||||
define('CONF', parse_ini_file(ROOT_PATH . '/config.ini', true, INI_SCANNER_TYPED));
|
|
||||||
|
|
||||||
define('DB', new PDO('sqlite:' . ROOT_PATH . '/db/servnest.db'));
|
|
||||||
DB->exec('PRAGMA foreign_keys = ON;');
|
|
||||||
DB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
foreach (explode(',', preg_replace('/[A-Z0-9]|q=|;|-|\./', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')) as $client_locale)
|
|
||||||
if (in_array($client_locale, array_diff(scandir(ROOT_PATH . '/locales'), ['..', '.']), true)) {
|
|
||||||
$locale = $client_locale;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
define('LOCALE', $locale ?? 'en');
|
|
||||||
putenv('LANG=C.UTF-8');
|
|
||||||
setlocale(LC_MESSAGES, 'C.UTF-8');
|
|
||||||
bindtextdomain('messages', ROOT_PATH . '/locales/' . LOCALE);
|
|
||||||
header('Content-Language: ' . LOCALE);
|
|
||||||
|
|
||||||
const SERVICES_USER = ['reg', 'ns', 'ht'];
|
|
||||||
|
|
||||||
const LF = "\n";
|
|
||||||
|
|
||||||
const PLACEHOLDER_DOMAIN = 'example'; // From RFC2606: Reserved Top Level DNS Names > 2. TLDs for Testing, & Documentation Examples
|
|
||||||
const PLACEHOLDER_IPV6 = '2001:db8::3'; // From RFC3849: IPv6 Address Prefix Reserved for Documentation
|
|
||||||
const PLACEHOLDER_IPV4 = '203.0.113.42'; // From RFC5737: IPv4 Address Blocks Reserved for Documentation
|
|
||||||
|
|
||||||
foreach (array_diff(scandir(ROOT_PATH . '/fn'), ['..', '.']) as $file)
|
|
||||||
require ROOT_PATH . '/fn/' . $file;
|
|
||||||
|
|
||||||
require ROOT_PATH . '/pages.php';
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_URI'] === '/sftpgo-auth.php')
|
|
||||||
return;
|
|
||||||
|
|
||||||
$pageAddress = substr($_SERVER['REQUEST_URI'], strlen(CONF['common']['prefix']) + 1);
|
$pageAddress = substr($_SERVER['REQUEST_URI'], strlen(CONF['common']['prefix']) + 1);
|
||||||
if (strpos($pageAddress, '?') !== false) {
|
if (strpos($pageAddress, '?') !== false) {
|
||||||
|
@ -99,28 +64,32 @@ if (isset($_COOKIE[SESSION_COOKIE_NAME]))
|
||||||
startSession(); // Resume session
|
startSession(); // Resume session
|
||||||
|
|
||||||
if (isset($_SESSION['id'])) {
|
if (isset($_SESSION['id'])) {
|
||||||
|
if (!isset(query('select', 'users', ['id' => $_SESSION['id']], 'id')[0]))
|
||||||
|
output(403, _('This account doesn\'t exist anymore. Log out to end this ghost session.'));
|
||||||
|
|
||||||
// Decrypt display username
|
// Decrypt display username
|
||||||
if (!isset($_COOKIE['display-username-decryption-key']))
|
if (!isset($_COOKIE['display-username-decryption-key']))
|
||||||
output(403, 'The display username decryption key has not been sent.');
|
output(403, 'The display username decryption key has not been sent.');
|
||||||
$decryption_result = htmlspecialchars(sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
|
$decryption_result = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||||
$_SESSION['display-username-cyphertext'],
|
$_SESSION['display-username-cyphertext'],
|
||||||
'',
|
'',
|
||||||
$_SESSION['display-username-nonce'],
|
$_SESSION['display-username-nonce'],
|
||||||
base64_decode($_COOKIE['display-username-decryption-key'])
|
base64_decode($_COOKIE['display-username-decryption-key'])
|
||||||
));
|
);
|
||||||
if ($decryption_result === false)
|
if ($decryption_result === false)
|
||||||
output(403, 'Unable to decrypt display username.');
|
output(403, 'Unable to decrypt display username.');
|
||||||
define('DISPLAY_USERNAME', $decryption_result);
|
define('DISPLAY_USERNAME', htmlspecialchars($decryption_result));
|
||||||
|
|
||||||
// Enable not already enabled services for this user
|
// Enable not already enabled services for this user
|
||||||
$user_services = array_filter(explode(',', query('select', 'users', ['id' => $_SESSION['id']], 'services')[0]));
|
$user_services = array_filter(explode(',', query('select', 'users', ['id' => $_SESSION['id']], 'services')[0]));
|
||||||
if (in_array(SERVICE, SERVICES_USER, true) AND !in_array(SERVICE, $user_services, true) AND CONF['common']['services'][SERVICE] === 'enabled') {
|
foreach (SERVICES_USER as $service)
|
||||||
$user_services[] = SERVICE;
|
if (!in_array($service, $user_services, true) AND CONF['common']['services'][$service] === 'enabled') {
|
||||||
|
$user_services[] = $service;
|
||||||
|
|
||||||
DB->prepare('UPDATE users SET services = :services WHERE id = :id')
|
DB->prepare('UPDATE users SET services = :services WHERE id = :id')
|
||||||
->execute([':services' => implode(',', $user_services), ':id' => $_SESSION['id']]);
|
->execute([':services' => implode(',', $user_services), ':id' => $_SESSION['id']]);
|
||||||
|
|
||||||
if (SERVICE === 'ht')
|
if ($service === 'ht')
|
||||||
htSetupUserFs($_SESSION['id']);
|
htSetupUserFs($_SESSION['id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,12 +111,8 @@ if ($_POST !== []) {
|
||||||
if (!in_array($_SERVER['HTTP_SEC_FETCH_SITE'], ['none', 'same-origin'], true))
|
if (!in_array($_SERVER['HTTP_SEC_FETCH_SITE'], ['none', 'same-origin'], true))
|
||||||
output(403, 'The <code>Sec-Fetch-Site</code> HTTP header must be <code>same-origin</code> or <code>none</code> when submitting a POST request to prevent Cross-Site Request Forgery (<abbr>CSRF</abbr>).');
|
output(403, 'The <code>Sec-Fetch-Site</code> HTTP header must be <code>same-origin</code> or <code>none</code> when submitting a POST request to prevent Cross-Site Request Forgery (<abbr>CSRF</abbr>).');
|
||||||
|
|
||||||
if (PAGE_METADATA['require-login'] ?? true !== false) {
|
if (PAGE_METADATA['require-login'] ?? true AND !isset($_SESSION['id']))
|
||||||
if (isset($_SESSION['id']) !== true)
|
|
||||||
output(403, _('You need to be logged in to do this.'));
|
output(403, _('You need to be logged in to do this.'));
|
||||||
if (isset(query('select', 'users', ['id' => $_SESSION['id']], 'id')[0]) !== true)
|
|
||||||
output(403, _('This account doesn\'t exist anymore. Log out to end this ghost session.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file_exists(ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php'))
|
if (file_exists(ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php'))
|
||||||
require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php';
|
require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php';
|
||||||
|
|
|
@ -1,47 +1,54 @@
|
||||||
<?php
|
<?php // ServNest authenticator for SFTPGo https://github.com/drakkan/sftpgo/blob/main/docs/external-auth.md
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
!DEBUG or ob_start();
|
!DEBUG or ob_start();
|
||||||
|
|
||||||
require 'router.php';
|
require 'init.php';
|
||||||
|
|
||||||
function deny() {
|
function deny($reason) {
|
||||||
!DEBUG or file_put_contents(ROOT_PATH . '/db/debug.txt', ob_get_contents());
|
!DEBUG or file_put_contents(ROOT_PATH . '/db/debug.txt', ob_get_contents() . $reason . LF);
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CONF['common']['services']['ht'] !== 'enabled')
|
if (CONF['common']['services']['ht'] !== 'enabled')
|
||||||
deny();
|
deny('Service not enabled.');
|
||||||
|
|
||||||
$auth_data = json_decode(file_get_contents('php://input'), true, flags: JSON_THROW_ON_ERROR);
|
$auth_data = json_decode(file_get_contents('php://input'), true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
$username = hashUsername($auth_data['username']);
|
$username = hashUsername($auth_data['username']);
|
||||||
|
|
||||||
if (usernameExists($username) !== true)
|
if (usernameExists($username) !== true)
|
||||||
deny();
|
deny('This username doesn\'t exist.');
|
||||||
|
|
||||||
if (!in_array('ht', explode(',', query('select', 'users', ['username' => $username], 'services')[0]), true))
|
$account = query('select', 'users', ['username' => $username])[0];
|
||||||
deny();
|
|
||||||
|
|
||||||
$id = query('select', 'users', ['username' => $username], 'id')[0];
|
if (!in_array('ht', explode(',', $account['services']), true))
|
||||||
|
deny('Service not enabled for this user.');
|
||||||
|
|
||||||
if (checkPassword($id, $auth_data['password']) !== true)
|
const SFTPGO_DENY_PERMS = ['/' => ['list']];
|
||||||
deny();
|
const SFTPGO_ALLOW_PERMS = ['list', 'download', 'upload', 'overwrite', 'delete_files', 'delete_dirs', 'rename_files', 'rename_dirs', 'create_dirs', 'chtimes'];
|
||||||
|
if ($auth_data['password'] !== '') {
|
||||||
|
if (checkPassword($account['id'], $auth_data['password']) !== true)
|
||||||
|
deny('Wrong password.');
|
||||||
|
$permissions['/'] = SFTPGO_ALLOW_PERMS;
|
||||||
|
} else if ($auth_data['public_key'] !== '') {
|
||||||
|
$permissions = SFTPGO_DENY_PERMS;
|
||||||
|
foreach (query('select', 'ssh-keys', ['username' => $account['id']]) as $key)
|
||||||
|
if (hash_equals('ssh-ed25519 ' . $key['key'] . LF, $auth_data['public_key']))
|
||||||
|
$permissions[$key['directory']] = SFTPGO_ALLOW_PERMS;
|
||||||
|
if ($permissions === SFTPGO_DENY_PERMS)
|
||||||
|
deny('No matching SSH key allowed.');
|
||||||
|
} else
|
||||||
|
deny('Unknown authentication method.');
|
||||||
|
|
||||||
echo '
|
echo json_encode([
|
||||||
{
|
'status' => 1,
|
||||||
"status": 1,
|
'username' => $auth_data['username'],
|
||||||
"username": ' . json_encode($auth_data['username']) . ',
|
'home_dir' => CONF['ht']['ht_path'] . '/fs/' . $account['id'],
|
||||||
"home_dir": "' . CONF['ht']['ht_path'] . '/fs/' . $id . '",
|
'quota_size' => ($account['type'] === 'approved') ? CONF['ht']['user_quota_approved'] : CONF['ht']['user_quota_testing'],
|
||||||
"quota_size": ' . ((query('select', 'users', ['id' => $id], 'type')[0] === 'approved') ? CONF['ht']['user_quota_approved'] : CONF['ht']['user_quota_testing']) . ',
|
'permissions' => $permissions,
|
||||||
"permissions": {
|
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||||
"/": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
';
|
|
||||||
|
|
||||||
!DEBUG or file_put_contents(ROOT_PATH . '/db/debug.txt', ob_get_contents());
|
!DEBUG or file_put_contents(ROOT_PATH . '/db/debug.txt', ob_get_contents() . 'accepted');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
|
6
view.php
6
view.php
|
@ -14,11 +14,11 @@ foreach (glob('css/*.css') as $css_path)
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<p>
|
<p class="auth">
|
||||||
<?php if (isset($_SESSION['id'])) { ?>
|
<?php if (isset($_SESSION['id'])) { ?>
|
||||||
<span aria-hidden="true"><?= ($_SESSION['type'] === 'approved') ? '👤' : '⏳' ?> </span><strong><?= (defined('DISPLAY_USERNAME') ? DISPLAY_USERNAME : '<em>?</em>') ?></strong> <a class="auth" href="<?= CONF['common']['prefix'] ?>/auth/logout"><?= _('Log out') ?></a>
|
<span aria-hidden="true"><?= ($_SESSION['type'] === 'approved') ? '👤' : '⏳' ?> </span><strong><?= (defined('DISPLAY_USERNAME') ? DISPLAY_USERNAME : '<em>?</em>') ?></strong> <a href="<?= CONF['common']['prefix'] ?>/auth/logout"><?= _('Log out') ?></a><?= ($_SESSION['type'] === 'testing') ? '<br>' . _('You are using a testing account. It may be deleted anytime.') . ' <a href="' . CONF['common']['prefix'] . '/auth/#type">' . _('Read more') . '</a>' : '' ?>
|
||||||
<?php } else { ?>
|
<?php } else { ?>
|
||||||
<span aria-hidden="true">👻 </span><em><?= _('Anonymous') ?></em> <a class="auth" href="<?= redirUrl('auth/login') ?>"><?= _('Log in') ?></a>
|
<span aria-hidden="true">👻 </span><em><?= _('Anonymous') ?></em> <a href="<?= redirUrl('auth/login') ?>"><?= _('Log in') ?></a>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</p>
|
</p>
|
||||||
<nav>
|
<nav>
|
||||||
|
|
Loading…
Reference in a new issue