#!/usr/bin/php -q
<?php

$NAMED_ROOT = '/usr/daemon/named';
$ZONE_BASE  = "$NAMED_ROOT/zone";

// Create a default named.conf zone stanza for a new domain
function makeDefaultNamedConf($domain) {
  return "zone \"base.dom\" {\n" .
         "    type master;\n" .
         "    file \"/usr/daemon/named/zone/base.dom/base.dom.zone\";\n" .
         "    allow-transfer { none; };\n" .
         "    allow-update { none; };\n" .
         "};\n";
}

// Create a default zone file for a new domain
function makeDefaultZoneFile($domain) {
  $ns     = trim(shell_exec('hostname -f 2>/dev/null')) ?: "ns1.$domain";
  $email  = "hostmaster.$domain";
  $serial = date('Ymd') . '01';
  return "\$ORIGIN $domain.\n" .
         "\$TTL 3600\n" .
         "@ IN SOA $ns. $email. (\n" .
         "    $serial ; serial (YYYYMMDDnn)\n" .
         "    3600    ; refresh\n" .
         "    900     ; retry\n" .
         "    604800  ; expire\n" .
         "    300 )   ; minimum\n\n" .
         "@ IN NS $ns.\n";
}

// Ensure a zone directory has the expected files, creating defaults if missing
function setupZoneDirectory($domain) {
  global $ZONE_BASE;
  $path = "$ZONE_BASE/$domain";

  $fragFile = "$path/named.conf";
  if (!file_exists($fragFile)) {
    file_put_contents($fragFile, makeDefaultNamedConf($domain));
    printf("syncnamed: created default named.conf fragment for %s\n", $domain);
  }

  $zoneFile = "$path/$domain.zone";
  if (!file_exists($zoneFile)) {
    file_put_contents($zoneFile, makeDefaultZoneFile($domain));
    printf("syncnamed: created default zone file for %s\n", $domain);
  }
}

function resyncnamed() {
  global $NAMED_ROOT, $ZONE_BASE;

  $settingsFile = "$NAMED_ROOT/etc/settings.named.conf";
  $newConf      = "$NAMED_ROOT/etc/new.named.conf";
  $namedConf    = "$NAMED_ROOT/etc/named.conf";

  if (!file_exists($settingsFile)) {
    fprintf(STDERR, "syncnamed: error: settings.named.conf not found at %s\n", $settingsFile);
    exit(1);
  }

  copy($settingsFile, $newConf);
  $f = fopen($newConf, 'a+');

  // Include TSIG key files (must appear before zone stanzas)
  $tsigDir = "$NAMED_ROOT/etc/tsig";
  if (is_dir($tsigDir)) {
    $td = opendir($tsigDir);
    $tsigFiles = [];
    while (($tf = readdir($td)) !== false) {
      if (substr($tf, -4) === '.key') $tsigFiles[] = $tf;
    }
    closedir($td);
    sort($tsigFiles);
    foreach ($tsigFiles as $tf) {
      fwrite($f, "\ninclude \"$tsigDir/$tf\";\n");
    }
  }

  // Include ACL files (after TSIG keys, before zones)
  $aclDir = "$NAMED_ROOT/etc/acl";
  if (is_dir($aclDir)) {
    $ad = opendir($aclDir);
    $aclFiles = [];
    while (($af = readdir($ad)) !== false) {
      if (substr($af, -5) === '.conf') $aclFiles[] = $af;
    }
    closedir($ad);
    sort($aclFiles);
    foreach ($aclFiles as $af) {
      fwrite($f, "\ninclude \"$aclDir/$af\";\n");
    }
  }

  $dir = @opendir($ZONE_BASE);
  if ($dir) {
    while (($domain = readdir($dir)) !== false) {
      if ($domain === '.' || $domain === '..') continue;
      $domainPath = "$ZONE_BASE/$domain";
      if (!is_dir($domainPath)) continue;

      setupZoneDirectory($domain);

      $fragFile = "$domainPath/named.conf";
      if (!is_file($fragFile)) {
        printf("syncnamed: no named.conf fragment for %s, skipping\n", $domain);
        continue;
      }

      fwrite($f, "\n// Zone: $domain\n");
      $conf = fopen($fragFile, 'r');
      while (($buf = fgets($conf, 4096)) !== false) {
        fwrite($f, str_replace('base.dom', $domain, rtrim($buf)) . "\n");
      }
      fclose($conf);
    }
    closedir($dir);
  }

  fclose($f);

  // Validate the assembled config
  $output = [];
  $ret    = 0;
  exec("/usr/daemon/named/bin/named-checkconf $newConf 2>&1", $output, $ret);
  if ($ret !== 0) {
    fprintf(STDERR, "syncnamed: named-checkconf failed:\n%s\n", implode("\n", $output));
    fprintf(STDERR, "syncnamed: no changes made\n");
    @unlink($newConf);
    exit(1);
  }

  printf("syncnamed: named-checkconf OK\n");
  copy($newConf, $namedConf);
  @unlink($newConf);

  // Reload named if running
  $pidFile = "$NAMED_ROOT/run/named/named.pid";
  if (file_exists($pidFile)) {
    $pid = (int)trim(file_get_contents($pidFile));
    if ($pid > 0 && file_exists("/proc/$pid")) {
      $out = [];
      exec("/usr/daemon/named/sbin/rndc -c /usr/daemon/named/etc/rndc.conf reload 2>&1", $out, $rc);
      if ($rc === 0) {
        printf("syncnamed: named reloaded\n");
      } else {
        printf("syncnamed: rndc reload: %s\n", implode(' ', $out));
      }
    } else {
      printf("syncnamed: named not running\n");
    }
  } else {
    printf("syncnamed: named not running (no pid file)\n");
  }
}

resyncnamed();
?>
