From 13d8b7f87eff5840930641d204734a1d1763aead Mon Sep 17 00:00:00 2001 From: Jacob Kiers Date: Sat, 21 Aug 2021 13:31:31 +0200 Subject: [PATCH] Update zone file generation Signed-off-by: Jacob Kiers --- publish-zones.php | 376 +++++++++++++++++++----- zones/db.hod.experiments.jacobkiers.net | 19 +- 2 files changed, 315 insertions(+), 80 deletions(-) diff --git a/publish-zones.php b/publish-zones.php index e68679e..063f7a9 100644 --- a/publish-zones.php +++ b/publish-zones.php @@ -1,116 +1,325 @@ filePath = $file; $this->fileName = basename(dirname($file)).'/'.basename($file); $this->data = file_get_contents($file); - $this->hash = md5($this->data); $this->parseMetadata(); } - public function toRecords(): string + /** + * @return Record[] + */ + private function toRecords(): array { - $chunks = str_split(base64_encode($this->data), 250); + $chunks = $this->calculateChunks(); + $hash = $this->calculateHash(); - $records = []; - $records[] = "; {$this->getDnsName()}"; - $records[] = $this->getDnsName().$this->buildMiddle().'"'.$this->buildIndexString(count($chunks)).'"'; - - foreach($chunks as $id => $chunk) - { + $records = [$this->buildMetadataRecord(count($chunks), $hash)]; + foreach ($chunks as $id => $chunk) { $records[] = $this->buildRecord($id, $chunk); } + return $records; + } + + public function __toString(): string + { $result = ""; - foreach ($records as $record) - { - $result .= $record . PHP_EOL; + + foreach ($this->toRecords() as $record) { + $result .= $record.EOL; } return $result; } - private function getDnsName(): string + public function getDnsName(): string { return str_replace(['/', '\\', '.'], '-', $this->fileName); } - private function buildIndexString(int $chunkCount): string + private function buildMetadataRecord(int $chunkCount, string $hash): Record + { + return (new Record()) + ->setDnsName($this->getDnsName()) + ->setTtl(self::METADATA_TTL) + ->setContent($this->buildIndexString($chunkCount, $hash)); + } + + private function buildIndexString(int $chunkCount, $hash): string { $metadata = base64_encode(json_encode($this->metadata)); - return "t={$this->mimeType()};c={$chunkCount};h={$this->hash};m={$metadata}"; + + return '"'."t={$this->mimeType()};c={$chunkCount};ha=sha-1;h={$hash};m={$metadata}".'"'; } - private function buildMiddle(): string + private function buildRecord(int $id, string $data): Record { - return "\t".self::TTL."\tIN\tTXT\t"; + $name = "$id.{$this->calculateHash()}"; + + return (new Record()) + ->setContent($data) + ->setDnsName($name) + ->setTtl(self::RECORD_TTL); } - private function buildRecord(int $id, string $data): string - { - $name = "$id.{$this->hash}"; - - return "$name".$this->buildMiddle().'"'.$data.'"'; - } - - private function mimeType() + private function mimeType(): string { $extension = pathinfo($this->fileName, PATHINFO_EXTENSION); - switch($extension) - { + switch ($extension) { case "js": return "text/javascript"; case "md": return "text/markdown"; - + default: - return mime_content_type($this->file); + return mime_content_type($this->filePath); } } - private function parseMetadata() + private function parseMetadata(): void { - if ($this->mimeType() != "text/markdown") return []; - - $frontmatter_chars = "+++"; - - $end_of_frontmatter = strpos($this->data, $frontmatter_chars, 2); - if (false === $end_of_frontmatter) { - var_dump($end_of_frontmatter); - return []; + if ($this->mimeType() != "text/markdown") { + return; } - $frontmatter = substr( + $front_matter_chars = "+++"; + + $front_matter_end = strpos($this->data, $front_matter_chars, 2); + if (false === $front_matter_end) { + return; + } + + $front_matter = substr( $this->data, - strlen($frontmatter_chars) + 1, - $end_of_frontmatter - strlen($frontmatter_chars) - 2 + strlen($front_matter_chars) + 1, + $front_matter_end - strlen($front_matter_chars) - 2 ); - $this->data = trim(substr($this->data, $end_of_frontmatter + strlen($frontmatter_chars))).PHP_EOL; + $this->data = trim(substr($this->data, $front_matter_end + strlen($front_matter_chars))).EOL; - $lines = explode(PHP_EOL, $frontmatter); - foreach($lines as $line) { + $lines = explode(EOL, $front_matter); + foreach ($lines as $line) { $eq_pos = strpos($line, "="); $key = trim(substr($line, 0, $eq_pos - 1)); $value = trim(substr($line, $eq_pos + 1)); $this->metadata[$key] = $value; } } + + private function calculateHash(): string + { + return sha1($this->data); + } + + private function calculateChunks(): array + { + return str_split(base64_encode($this->data), self::RECORD_LENGTH); + } } -function update_serial_line(string $line) +class Record +{ + private string $dnsName; + private int $ttl; + private string $content; + + public function setDnsName(string $dnsName): self + { + $this->dnsName = $dnsName; + + return $this; + } + + public function setTtl(int $ttl): self + { + $this->ttl = $ttl; + + return $this; + } + + public function setContent(string $content): self + { + $this->content = $content; + + return $this; + } + + public function getRecord(): string + { + return $this->dnsName + ."\t".$this->ttl + ."\tIN" + ."\tTXT\t" + .$this->content; + } + + public function __toString(): string + { + return $this->getRecord(); + } +} + +class StartOfAuthority +{ + public function __construct( + private string $origin, + private string $serial, + private string $masterDnsServer, + private string $domainContact, + private int $slaveRefreshInterval, + private int $slaveRetryInterval, + private int $slaveCopyExpireTime, + private int $nxDomainCacheTime + ) { + } + + public function doNotUserMasterDnsServer(): self + { + $servers = $this->dnsServers; + $this->dnsServers = array_filter( + $servers, + function ($server) { + return $server !== $this->masterDnsServer; + } + ); + + return $this; + } + + public function increaseSerial(): self + { + $matches = []; + preg_match('#(\d{8})(\d{2})#', $this->serial, $matches); + list (, $day, $count) = $matches; + + $today = date("Ymd"); + if ($today === $day) { + $count++; + } else { + $count = 0; + } + + $this->serial = sprintf('%1$s%2$02d', $today, $count);; + + return $this; + } + + public function __toString(): string + { + return <<origin ; The zone of this zone file +@ IN SOA $this->masterDnsServer $this->domainContact ( + $this->serial ; serial + $this->slaveRefreshInterval ; slave refresh interval + $this->slaveRetryInterval ; slave retry interval + $this->slaveCopyExpireTime ; slave copy expire time + $this->nxDomainCacheTime ; NXDOMAIN cache time +) + +; +; domain name servers +; +@ IN NS $this->masterDnsServer +EOF; + } + + public static function fromString(string $zonefile): self + { + $matches = []; + $found = 1 === preg_match( + '#\$ORIGIN\s+(?P[a-z.]+)[^@]+@\s+IN\s+SOA\s+(?P[a-z.]+)\s+(?P[a-z.]+)\s+\(\D+\s+(?P\d+)\D+(?P\d+)\D+(?P\d+)\D+(?P\d+)\D+(?P\d+)[^\)]+\)#im', + $zonefile, + $matches + ); + + if (!$found) { + throw new \Exception("Could not find the SOA record."); + } + + return new self( + $matches['origin'], + $matches['serial'], + $matches['dns'], + $matches['contact'], + (int)$matches['srefresh'], + (int)$matches['sretry'], + (int)$matches['sexpire'], + (int)$matches['nxcache'] + ); + } + +} + +class ZoneFile +{ + private StartOfAuthority $soa; + /** @var ContentFile[] */ + private array $files = []; + private int $defaultTTL = 300; + + public function __construct( + private string $file + ) { + $this->soa = StartOfAuthority::fromString(file_get_contents($this->file)); + } + + public function addFile(ContentFile ...$files): self + { + foreach ($files as $file) { + $this->files[] = $file; + } + + return $this; + } + + public function setDefaultTTL(int $defaultTTL): self + { + $this->defaultTTL = $defaultTTL; + + return $this; + } + + public function __toString(): string + { + $this->soa->increaseSerial(); + + $zone_file = "\$TTL\t{$this->defaultTTL}\t; Default TTL".EOL.EOL; + $zone_file .= $this->soa->__toString().EOL.EOL.EOL; + $zone_file .= ";;; START BLOG RECORDS"; + + foreach ($this->files as $file) { + $zone_file .= EOL.EOL.EOL; + $zone_file .= '; '.$file->getDnsName().EOL; + $zone_file .= $file; + } + + $zone_file .= EOL; + + return $zone_file; + } +} + +function update_serial_line(string $line): string { $matches = []; preg_match("#(\s+)(\d{10})(.*)#", $line, $matches); @@ -118,7 +327,7 @@ function update_serial_line(string $line) $today = date("Ymd"); $day = substr($serial, 0, 8); - $nr = (int) substr($serial, -2); + $nr = (int)substr($serial, -2); if ($day === $today) { $nr++; @@ -132,41 +341,68 @@ function update_serial_line(string $line) return $start.$new_serial.$last; } -$zone_file = "zones/db.hod.experiments.jacobkiers.net"; -if (!file_exists($zone_file)) { - fwrite(STDERR, "The zone file {$zone_file} does not exist!"); +$zone_file_path = "zones/db.hod.experiments.jacobkiers.net"; +if (!file_exists($zone_file_path)) { + fwrite(STDERR, "The zone file {$zone_file_path} does not exist!"); exit(1); } - +/* $lines = file($zone_file); -foreach($lines as $index => &$line) -{ +$index = 0; +foreach ($lines as $index => &$line) { if (str_contains($line, "; serial")) { $matches = []; preg_match("#(\s+)(\d{10})(.*)#", $line, $matches); list($_, $start, $serial, $last) = $matches; - $line = update_serial_line($line).PHP_EOL; + $line = update_serial_line($line).EOL; + } + if (str_starts_with($line, ";; START BLOG RECORDS")) { + break; } - if (str_starts_with($line, ";; START BLOG RECORDS")) break; } -$zone_file_contents = implode("", array_slice($lines, 0, $index+1)); + + +$zone_file_contents = implode("", array_slice($lines, 0, $index + 1)); echo $zone_file_contents; $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/content")); -foreach ($it as $file) -{ - if ($file->isDir()) continue; - if (str_contains($file->getPathname(), "ignore")) continue; +foreach ($it as $file) { + if ($file->isDir()) { + continue; + } + if (str_contains($file->getPathname(), "ignore")) { + continue; + } - $bootstrap = new Content($file->getPathname()); - $zone_file_contents .= PHP_EOL.$bootstrap->toRecords().PHP_EOL; + $content = new ContentFile($file->getPathname()); + $zone_file_contents .= EOL.$content->__toString().EOL; } -$zone_file_contents .= PHP_EOL; +$zone_file_contents .= EOL; -#echo $zone_file_contents; +echo $zone_file_contents; -file_put_contents($zone_file, $zone_file_contents); \ No newline at end of file +#file_put_contents($zone_file, $zone_file_contents); +*/ + +$zone_file = new ZoneFile($zone_file_path); + +$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/content")); +foreach ($it as $file) { + if ($file->isDir()) { + continue; + } + if (str_contains($file->getPathname(), "ignore")) { + continue; + } + + $content = new ContentFile($file->getPathname()); + $zone_file->addFile($content); +} + +$zone_file_contents = $zone_file->__toString(); +echo $zone_file_contents; +file_put_contents($zone_file_path, $zone_file_contents); \ No newline at end of file diff --git a/zones/db.hod.experiments.jacobkiers.net b/zones/db.hod.experiments.jacobkiers.net index 8f760a6..24916f1 100644 --- a/zones/db.hod.experiments.jacobkiers.net +++ b/zones/db.hod.experiments.jacobkiers.net @@ -1,14 +1,13 @@ -; -$TTL 5m ; Default TTL -@ IN SOA experiments.jacobkiers.net. postmaster.kie.rs. ( - 2021082004 ; serial - 1h ; slave refresh interval - 15m ; slave retry interval - 1w ; slave copy expire time - 1h ; NXDOMAIN cache time - ) +$TTL 300 ; Default TTL -$ORIGIN hod.experiments.jacobkiers.net. +$ORIGIN hod.experiments.jacobkiers.net. ; The zone of this zone file +@ IN SOA experiments.jacobkiers.net. postmaster.jacobkiers.net. ( + 2021082109 ; serial + 3600 ; slave refresh interval + 900 ; slave retry interval + 604800 ; slave copy expire time + 3600 ; NXDOMAIN cache time +) ; ; domain name servers