Bläddra i källkod

More exhaustive check.php

Miraty 2 år sedan
förälder
incheckning
b93ff0c26f
4 ändrade filer med 168 tillägg och 74 borttagningar
  1. 6 0
      DOCS/configuration.md
  2. 159 72
      check.php
  3. 1 0
      config.ini
  4. 2 2
      pg-act/ns/zone-add.php

+ 6 - 0
DOCS/configuration.md

@@ -97,6 +97,12 @@ Filesystem path to the `kzonecheck` binary. Used to check sent plaintext zonefil
 
 Administrator email address published in every SOA record. Ends with a `.`, `@` is replaced by a `.`, an hypothetical `.` in the first part of the address is escaped using a `\` before, thus `contact.admin@servnest.example` becomes `contact\.admin.servnest.example.`
 
+### `local_only_check`
+
+Check for records on the local registry name server when adding a zone.
+
+Development feature, should not be enabled for a public server.
+
 ## `[ht]`
 
 ### `ht_path`

+ 159 - 72
check.php

@@ -1,11 +1,14 @@
 <?php
+umask(0077);
 const ROOT_PATH = __DIR__;
 define('CONF', parse_ini_file(ROOT_PATH . '/config.ini', true, INI_SCANNER_TYPED));
 
 const SFTP = '/usr/bin/sftp';
 const SSHPASS = '/usr/bin/sshpass';
 
-const URL = 'https://servnest.test:42443';
+const HTTPS_PORT = '42443';
+const CORE_DOMAIN = 'servnest.test';
+const CORE_URL = 'https://' . CORE_DOMAIN . ':' . HTTPS_PORT;
 const SUFFIX = 'test.servnest.test.';
 
 const TOR_PROXY = 'socks5h://127.0.0.1:9050';
@@ -20,38 +23,42 @@ if (CONF['common']['services']['ns'] === 'rest') {
 	echo 'a';
 }
 
-const COMMON_OPT = [
-	CURLOPT_POST => true,
-	CURLOPT_SSL_VERIFYPEER => false,
-	CURLOPT_SSL_VERIFYHOST => 0,
-	CURLOPT_RETURNTRANSFER => true,
-	CURLOPT_HEADER => true,
-	CURLOPT_HTTPHEADER => [
-		'Sec-Fetch-Site: none',
-	],
-];
+define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt');
 
-define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . random_bytes(16) . '.txt');
-
-function curlTest($path, $args) {
+function curlTest($address, $post = [], $tor = false) {
 	$req = curl_init();
-	curl_setopt_array($req, COMMON_OPT);
-	curl_setopt_array($req, [
-		CURLOPT_URL => URL . $path,
-		CURLOPT_COOKIEFILE => COOKIE_FILE,
-		CURLOPT_COOKIEJAR => COOKIE_FILE,
-		CURLOPT_POSTFIELDS => $args,
-	]);
+	curl_setopt($req, CURLOPT_RETURNTRANSFER, true);
+
+	if (str_starts_with($address, '/'))
+		curl_setopt_array($req, [
+			CURLOPT_POST => true,
+			CURLOPT_HEADER => true,
+			CURLOPT_HTTPHEADER => [
+				'Sec-Fetch-Site: none',
+			],
+			CURLOPT_URL => CORE_URL . $address,
+			CURLOPT_COOKIEFILE => COOKIE_FILE,
+			CURLOPT_COOKIEJAR => COOKIE_FILE,
+			CURLOPT_POSTFIELDS => $post,
+		]);
+	else
+		curl_setopt($req, CURLOPT_URL, $address);
+
+	if ($tor)
+		curl_setopt($req, CURLOPT_PROXY, TOR_PROXY);
+	else
+		curl_setopt($req, CURLOPT_SSL_VERIFYPEER, false);
+
+	$result = curl_exec($req);
 	$status_code = curl_getinfo($req, CURLINFO_RESPONSE_CODE);
-	if ($status_code >= 400) {
+	if ($status_code >= 400 OR $result === false) {
 		var_dump(curl_exec($req));
-		exit($path . ' test failed with status code ' . $status_code . LF);
+		var_dump(curl_error($req));
+		exit($address . ' test failed with status code ' . $status_code . LF);
 	}
-	return curl_exec($req);
+	return $result;
 }
 
-// AUTH TEST
-
 $username = 'check-' . bin2hex(random_bytes(16));
 $password = bin2hex(random_bytes(16));
 
@@ -78,7 +85,6 @@ curlTest('/auth/username', [
 	'current-password' => $password,
 	'new-username' => $username . '2',
 ]);
-
 curlTest('/auth/username', [
 	'current-password' => $password,
 	'new-username' => $username,
@@ -86,68 +92,149 @@ curlTest('/auth/username', [
 
 echo 'Created account with username "' . $username . '" and password "' . $password . '".' . LF;
 
-// REG TEST
+function testReg() {
+	$subdomain = bin2hex(random_bytes(16));
 
-$subdomain = bin2hex(random_bytes(16));
-var_dump($subdomain);
-curlTest('/reg/register', [
-	'subdomain' => $subdomain,
-	'suffix' => SUFFIX,
-	'action' => 'register',
-]);
+	curlTest('/reg/register', [
+		'subdomain' => $subdomain,
+		'suffix' => SUFFIX,
+		'action' => 'register',
+	]);
 
-curlTest('/reg/ns', [
-	'action' => 'add',
-	'domain' => $subdomain . '.' . SUFFIX,
-	'ns' => 'ns1.servnest.invalid.',
-]);
+	$domain = $subdomain . '.' . SUFFIX;
 
-exec(CONF['dns']['kdig_path'] . ' @' . CONF['reg']['address'] . ' ' . $subdomain . '.' . SUFFIX . ' NS', $output2, $return_code);
-if (preg_match('/[ \t]+ns1.servnest.invalid.$/Dm', implode(LF, $output2)) !== 1)
-	exit('Error: /reg/ns: NS record not set');
+	curlTest('/reg/ns', [
+		'action' => 'add',
+		'domain' => $domain,
+		'ns' => 'ns1.servnest.invalid.',
+	]);
+	exec(CONF['dns']['kdig_path'] . ' @' . CONF['reg']['address'] . ' ' . $domain . ' NS', $output);
+	if (preg_match('/[ \t]+ns1\.servnest\.invalid\.$/Dm', implode(LF, $output)) !== 1)
+		exit('Error: /reg/ns: NS record not set' . LF);
+
+	curlTest('/reg/ns', [
+		'action' => 'delete',
+		'domain' => $domain,
+		'ns' => 'ns1.servnest.invalid.',
+	]);
 
-// HT TEST
+	return $domain;
+}
 
-curlTest('/ht/', []);
+function testNs($domain) {
+	foreach (CONF['ns']['servers'] as $ns)
+		curlTest('/reg/ns', [
+			'action' => 'add',
+			'domain' => $domain,
+			'ns' => $ns,
+		]);
+
+	preg_match('#\<code\>(?<token>[0-9a-z-]{16,128}\._domain-verification\.' . preg_quote(CORE_DOMAIN) . '\.)\</code\>#', curlTest('/ns/zone-add', []), $matches);
+	curlTest('/reg/ns', [
+		'action' => 'add',
+		'domain' => $domain,
+		'ns' => $matches['token'],
+	]);
 
-define('TEST_CONTENT', 'test-' . random_bytes(4));
+	curlTest('/ns/zone-add', [
+		'domain' => $domain,
+	]);
 
-file_put_contents(sys_get_temp_dir() . '/index.html', TEST_CONTENT);
+	curlTest('/reg/ns', [
+		'action' => 'delete',
+		'domain' => $domain,
+		'ns' => $matches['token'],
+	]);
 
-file_put_contents(sys_get_temp_dir() . '/exec.txt', 'mkdir /_site0-
+	curlTest('/ns/caa', [
+		'action' => 'add',
+		'subdomain' => '@',
+		'zone' => $domain,
+		'ttl-value' => '2',
+		'ttl-multiplier' => '3600',
+		'flag' => '0',
+		'tag' => 'issue',
+		'value' => 'letsencrypt.org',
+	]);
+	exec(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)
+		exit('Error: /ns/caa: CAA record not set' . LF);
+
+	curlTest('/ns/edit', [
+		'zone' => $domain,
+		'zone-content' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n"
+			. '@ 86400 NS ' . CONF['ns']['servers'][0] . "\r\n",
+	]);
+	exec(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)
+		exit('Error: /ns/edit: AAAA record not set' . LF);
+}
+
+function testHt($username, $password) {
+	curlTest('/ht/', []);
+
+	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() . '/exec.txt', 'mkdir /_site0-
 put ' . sys_get_temp_dir() . '/index.html /_site0-/index.html
 exit
 ');
 
-$process = proc_open(SSHPASS . ' ' . SFTP . ' -o BatchMode=no -b ' . sys_get_temp_dir() . '/exec.txt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P ' . CONF['ht']['public_sftp_port'] . ' ' . $username . '@' . CONF['ht']['sftp_domain'], [0 => ['pipe', 'r']], $pipes);
-if (is_resource($process) !== true)
-	exit('Can\'t spawn sftp with sshpass.');
-fwrite($pipes[0], $password);
-fclose($pipes[0]);
-if (proc_close($process) !== 0)
-	exit('File not sent successfully.');
+	$process = proc_open(SSHPASS . ' ' . SFTP . ' -o BatchMode=no -b ' . sys_get_temp_dir() . '/exec.txt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P ' . CONF['ht']['public_sftp_port'] . ' ' . $username . '@' . CONF['ht']['sftp_domain'], [0 => ['pipe', 'r']], $pipes);
+	if (is_resource($process) !== true)
+		exit('Can\'t spawn sftp with sshpass.' . LF);
+	fwrite($pipes[0], $password);
+	fclose($pipes[0]);
+	if (proc_close($process) !== 0)
+		exit('File not sent successfully.' . LF);
+
+	{
+		$ht_subpath = bin2hex(random_bytes(16));
+		curlTest('/ht/add-subpath', [
+			'path' => $ht_subpath,
+			'dir' => '_site0-',
+		]);
+		if (curlTest('https://' . CONF['ht']['subpath_domain'] . ':' . HTTPS_PORT . '/' . $ht_subpath . '/') !== TEST_CONTENT)
+			exit('Unexpected subpath response' . LF);
+		curlTest('/ht/del', [
+			'site' => 'subpath:' . $ht_subpath,
+		]);
+	}
 
-$html = curlTest('/ht/add-onion', [
-	'dir' => '_site0-'
-]);
-if (preg_match('#\<code\>http\://(?<onion>[0-9a-z]{56})\.onion/\</code\>#D', $html, $matches) !== 1)
-	exit('Can\'t find onion address.');
+	{
+		$ht_subdomain = 'test3';
+		curlTest('/ht/add-subdomain', [
+			'subdomain' => $ht_subdomain,
+			'dir' => '_site0-',
+		]);
+		if (curlTest('https://' . $ht_subdomain . '.' . CONF['ht']['subpath_domain'] . ':' . HTTPS_PORT . '/') !== TEST_CONTENT)
+			exit('Unexpected subpath response' . LF);
+		curlTest('/ht/del', [
+			'site' => 'subdomain:' . $ht_subdomain,
+		]);
+	}
 
-sleep(10);
+	{
+		$html = curlTest('/ht/add-onion', [
+			'dir' => '_site0-',
+		]);
+		if (preg_match('#\<code\>http\://(?<onion>[0-9a-z]{56})\.onion/\</code\>#D', $html, $matches) !== 1)
+			exit('Can\'t find onion address.' . LF);
+		sleep(5);
+		if (curlTest('http://' . $matches['onion'] . '.onion/', tor: true) !== TEST_CONTENT)
+			exit('Unexpected onion service response (' . $matches['onion'] . '.onion)' . LF);
+		curlTest('/ht/del', [
+			'site' => 'onion:' . $matches['onion'] . '.onion',
+		]);
+	}
 
-$req = curl_init();
-curl_setopt_array($req, [
-	CURLOPT_PROXY => TOR_PROXY,
-	CURLOPT_URL => 'http://' . $matches['onion'] . '.onion/',
-	CURLOPT_RETURNTRANSFER => true,
-]);
-var_dump(curl_error($req));
-if (curl_exec($req) !== TEST_CONTENT) {
-	var_dump(curl_exec($req));
-	exit('Unexpected onion service response (status:' . $status_code . ') (' . $matches['onion'] . '.onion)' . LF);
 }
 
-// STOP
+$domain = testReg();
+testNs($domain);
+testHt($username, $password);
 
 curlTest('/auth/unregister', [
 	'current-password' => $password,

+ 1 - 0
config.ini

@@ -27,6 +27,7 @@ servers[] = "ns1.servnest.test."
 servers[] = "ns2.servnest.test."
 kzonecheck_path = "/usr/bin/kzonecheck"
 public_soa_email = "hostmaster.invalid."
+local_only_check = false
 
 [ht]
 ht_path = "/srv/servnest/ht"

+ 2 - 2
pg-act/ns/zone-add.php

@@ -5,13 +5,13 @@ $_POST['domain'] = formatAbsoluteDomain($_POST['domain']);
 if (query('select', 'zones', ['zone' => $_POST['domain']], 'zone') !== [])
 	output(403, _('This zone already exists on the service.'));
 
-exec(CONF['dns']['kdig_path'] . ' ' . ltrim(strstr($_POST['domain'], '.'), '.') . ' NS +short', $parentAuthoritatives);
+exec(CONF['dns']['kdig_path'] . ' ' . ltrim(strstr($_POST['domain'], '.'), '.') . ' NS +short' . (CONF['ns']['local_only_check'] ? (' @' . CONF['reg']['address']) : ''), $parentAuthoritatives);
 if ($parentAuthoritatives === [])
 	output(403, _('Parent zone\'s name servers not found.'));
 foreach ($parentAuthoritatives as $parentAuthoritative)
 	checkAbsoluteDomainFormat($parentAuthoritative);
 
-exec(CONF['dns']['kdig_path'] . ' ' . $_POST['domain'] . ' NS @' . $parentAuthoritatives[0] . ' +noidn', $results);
+exec(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)
 	output(403, _('NS authentication record not found.'));