$openness) if ($openness === 'all') define('SUFFIX', $suffix); if (!defined('SUFFIX')) exit('Unable to start tests: no suffix open to registration'); if (kdig(name: 'nlnet.nl', type: 'AAAA')['AD'] !== 1) exit('DNS queries don\'t seem to be DNSSEC-validated.' . LF); define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt'); function curlTest(string $address, array $post = [], bool $tor = false): string { $req = curl_init(); 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, CONF['check']['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 OR $result === false) { var_dump($result); var_dump(curl_error($req)); throw new Exception($address . ' test failed with status code ' . $status_code); } return $result; } $username = 'check-' . bin2hex(random_bytes(16)); $password = bin2hex(random_bytes(16)); curlTest('/auth/register', [ 'username' => $username, 'password' => $password, ]); $new_password = bin2hex(random_bytes(16)); curlTest('/auth/password', [ 'current-password' => $password, 'new-password' => $new_password, ]); $password = $new_password; curlTest('/auth/register', [ 'username' => $username . '2', 'password' => $password, ]); curlTest('/auth/logout'); curlTest('/auth/login', [ 'username' => $username, 'password' => $password, ]); curlTest('/auth/username', [ 'current-password' => $password, 'new-username' => $username . '3', ]); curlTest('/auth/username', [ 'current-password' => $password, 'new-username' => $username, ]); echo 'Created account with username "' . $username . '" and password "' . $password . '".' . LF; function testReg(): string { global $username, $password; $subdomain = bin2hex(random_bytes(16)); curlTest('/reg/register', [ 'subdomain' => $subdomain, 'suffix' => SUFFIX, 'action' => 'register', ]); $domain = $subdomain . '.' . SUFFIX; curlTest('/reg/ns', [ 'action' => 'add', 'domain' => $domain, 'ns' => 'ns1.servnest.invalid.', ]); $results = kdig(name: $domain, type: 'NS', server: CONF['reg']['address']); if (($results['authorityRRs'][0]['rdataNS'] ?? NULL) !== 'ns1.servnest.invalid.') exit('Error: /reg/ns: NS record not set' . LF); curlTest('/reg/ns', [ 'action' => 'delete', 'domain' => $domain, 'ns' => 'ns1.servnest.invalid.', ]); { // Domain transfer curlTest('/auth/logout'); curlTest('/auth/login', [ 'username' => $username . '2', 'password' => $password, ]); preg_match('#\(?[0-9a-z-]{16,128}\._transfer-verification\.' . preg_quote(CONF['common']['public_domains'][0], '#') . '\.)\#', curlTest('/reg/transfer'), $matches); curlTest('/auth/logout'); curlTest('/auth/login', [ 'username' => $username, 'password' => $password, ]); curlTest('/reg/ns', [ 'action' => 'add', 'domain' => $domain, 'ns' => $matches['token'], ]); curlTest('/auth/logout'); curlTest('/auth/login', [ 'username' => $username . '2', 'password' => $password, ]); curlTest('/reg/transfer', [ 'subdomain' => $subdomain, 'suffix' => SUFFIX, 'ns' => $matches['token'], ]); $username = $username . '2'; } return $domain; } function testNs(string $domain): void { foreach (CONF['ns']['servers'] as $ns) curlTest('/reg/ns', [ 'action' => 'add', 'domain' => $domain, 'ns' => $ns, ]); preg_match('#\(?[0-9a-z-]{16,128}\._domain-verification\.' . preg_quote(CONF['common']['public_domains'][0], '#') . '\.)\#', curlTest('/ns/zone-add'), $matches); curlTest('/reg/ns', [ 'action' => 'add', 'domain' => $domain, 'ns' => $matches['token'], ]); curlTest('/ns/zone-add', [ 'domain' => $domain, ]); curlTest('/reg/ns', [ 'action' => 'delete', 'domain' => $domain, 'ns' => $matches['token'], ]); curlTest('/ns/caa', [ 'action' => 'add', 'subdomain' => '@', 'zone' => $domain, 'ttl-value' => '2', 'ttl-multiplier' => '3600', 'flag' => '0', 'tag' => 'issue', 'value' => 'letsencrypt.org', ]); $results = kdig(name: $domain, type: 'CAA', server: CONF['reg']['address']); if (($results['answerRRs'][0]['TTL'] ?? NULL) !== 7200) exit('Error: /ns/caa: wrong TTL' . LF); if (($results['answerRRs'][0]['rdataCAA'] ?? NULL) !== '0 issue "letsencrypt.org" ') exit('Error: /ns/caa: CAA record not set' . LF); curlTest('/ns/edit', [ 'domain' => $domain, 'records' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n" . '@ 86400 NS ' . CONF['ns']['servers'][0] . "\r\n", ]); $results = kdig(name: 'aaaa.' . $domain, type: 'AAAA', server: CONF['reg']['address']); if (($results['answerRRs'][0]['rdataAAAA'] ?? NULL) !== CONF['ht']['ipv6_address']) exit('Error: /ns/edit: AAAA record not set' . LF); } function testHt(string $username, string $password): void { define('TEST_CONTENT', 'test-' . bin2hex(random_bytes(16))); 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(CONF['check']['sshpass_path'] . ' ' . CONF['check']['sftp_path'] . ' -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, ]); } { $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, ]); } { $html = curlTest('/ht/add-onion', [ 'dir' => '_site0-', ]); if (preg_match('#\http\://(?[0-9a-z]{56})\.onion/\#D', $html, $matches) !== 1) exit('Can\'t find onion address.' . LF); sleep(5); // Onion services are not immediately reachable 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', ]); } } $domain = testReg(); testNs($domain); testHt($username, $password); curlTest('/auth/unregister', [ 'current-password' => $password, 'delete' => 'on', ]); unlink(COOKIE_FILE); echo 'OK' . LF;