Files
wp-multisite-waas/dependencies/amphp/dns/lib/UnixConfigLoader.php
2024-11-30 18:24:12 -07:00

159 lines
6.5 KiB
PHP

<?php
namespace WP_Ultimo\Dependencies\Amp\Dns;
use WP_Ultimo\Dependencies\Amp\Failure;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use function WP_Ultimo\Dependencies\Amp\call;
class UnixConfigLoader implements ConfigLoader
{
const MAX_NAMESERVERS = 3;
const MAX_DNS_SEARCH = 6;
const MAX_TIMEOUT = 30 * 1000;
const MAX_ATTEMPTS = 5;
const MAX_NDOTS = 15;
const DEFAULT_TIMEOUT = 5 * 1000;
const DEFAULT_ATTEMPTS = 2;
const DEFAULT_NDOTS = 1;
const DEFAULT_OPTIONS = ["timeout" => self::DEFAULT_TIMEOUT, "attempts" => self::DEFAULT_ATTEMPTS, "ndots" => self::DEFAULT_NDOTS, "rotate" => \false];
private $path;
private $hostLoader;
public function __construct(string $path = "/etc/resolv.conf", HostLoader $hostLoader = null)
{
$this->path = $path;
$this->hostLoader = $hostLoader ?? new HostLoader();
}
protected function readFile(string $path) : Promise
{
\set_error_handler(function (int $errno, string $message) use($path) {
throw new ConfigException("Could not read configuration file '{$path}' ({$errno}) {$message}");
});
try {
// Blocking file access, but this file should be local and usually loaded only once.
$fileContent = \file_get_contents($path);
} catch (ConfigException $exception) {
return new Failure($exception);
} finally {
\restore_error_handler();
}
return new Success($fileContent);
}
public final function loadConfig() : Promise
{
return call(function () {
$nameservers = [];
$searchList = [];
$options = self::DEFAULT_OPTIONS;
$haveLocaldomainEnv = \false;
/* Allow user to override the local domain definition. */
if ($localdomain = \getenv("LOCALDOMAIN")) {
/* Set search list to be blank-separated strings from rest of
env value. Permits users of LOCALDOMAIN to still have a
search list, and anyone to set the one that they want to use
as an individual (even more important now that the rfc1535
stuff restricts searches). */
$searchList = $this->splitOnWhitespace($localdomain);
$haveLocaldomainEnv = \true;
}
$fileContent = (yield $this->readFile($this->path));
$lines = \explode("\n", $fileContent);
foreach ($lines as $line) {
$line = \preg_split('#\\s+#', $line, 2);
if (\count($line) !== 2) {
continue;
}
list($type, $value) = $line;
if ($type === "nameserver") {
if (\count($nameservers) === self::MAX_NAMESERVERS) {
continue;
}
$value = \trim($value);
$ip = @\inet_pton($value);
if ($ip === \false) {
continue;
}
if (isset($ip[15])) {
// IPv6
$nameservers[] = "[" . $value . "]:53";
} else {
// IPv4
$nameservers[] = $value . ":53";
}
} elseif ($type === "domain" && !$haveLocaldomainEnv) {
// LOCALDOMAIN env overrides config
$searchList = $this->splitOnWhitespace($value);
} elseif ($type === "search" && !$haveLocaldomainEnv) {
// LOCALDOMAIN env overrides config
$searchList = $this->splitOnWhitespace($value);
} elseif ($type === "options") {
$option = $this->parseOption($value);
if (\count($option) === 2) {
$options[$option[0]] = $option[1];
}
}
}
$hosts = (yield $this->hostLoader->loadHosts());
if (\count($searchList) === 0) {
$hostname = \gethostname();
$dot = \strpos(".", $hostname);
if ($dot !== \false && $dot < \strlen($hostname)) {
$searchList = [\substr($hostname, $dot)];
}
}
if (\count($searchList) > self::MAX_DNS_SEARCH) {
$searchList = \array_slice($searchList, 0, self::MAX_DNS_SEARCH);
}
$resOptions = \getenv("RES_OPTIONS");
if ($resOptions) {
foreach ($this->splitOnWhitespace($resOptions) as $option) {
$option = $this->parseOption($option);
if (\count($option) === 2) {
$options[$option[0]] = $option[1];
}
}
}
$config = new Config($nameservers, $hosts, $options["timeout"], $options["attempts"]);
return $config->withSearchList($searchList)->withNdots($options["ndots"])->withRotationEnabled($options["rotate"]);
});
}
private function splitOnWhitespace(string $names) : array
{
return \preg_split("#\\s+#", \trim($names));
}
private function parseOption(string $option) : array
{
$optline = \explode(':', $option, 2);
list($name, $value) = $optline + [1 => null];
switch ($name) {
case "timeout":
$value = (int) $value;
if ($value < 0) {
return [];
// don't overwrite option value
}
// The value for this option is silently capped to 30s
return ["timeout", (int) \min($value * 1000, self::MAX_TIMEOUT)];
case "attempts":
$value = (int) $value;
if ($value < 0) {
return [];
// don't overwrite option value
}
// The value for this option is silently capped to 5
return ["attempts", (int) \min($value, self::MAX_ATTEMPTS)];
case "ndots":
$value = (int) $value;
if ($value < 0) {
return [];
// don't overwrite option value
}
// The value for this option is silently capped to 15
return ["ndots", (int) \min($value, self::MAX_NDOTS)];
case "rotate":
return ["rotate", \true];
}
return [];
}
}