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

function filecheck($path,$data="") {
  $name=substr($path,25);
  if (!($info=@stat($path))) {
    printf("Creating file %s\n",$path);
    $f=fopen($path,"w+");
    fwrite($f,$data);
    fclose($f);
    chown($path,503);
    chgrp($path,406);
    printf("Missing File %s, created\n",$name);
    return;
  }
/*   if ($info["uid"]!=503 || $info["gid"]!=406) { */
/*     printf("Setting OwnerShip of %s to mail:mail\n",$name); */
/*     return; */
/*     chown($path,503); */
/*     chgrp($path,406); */
/*   } */
}

function directorycheck($path) {
  $name=substr($path,25);
  if (!($info=@stat($path))) {
    printf("making directory %s\n",$path);
    mkdir($path,0755);
    chown($path,8);
    chgrp($path,12);
    printf("Missing Directory %s, created\n",$name);
    return;
  }
/*   if ($info["uid"]!=8 || $info["gid"]!=12) { */
/*     printf("Setting OwnerShip of %s to mail:mail\n",$name); */
/*     chown($path,8); */
/*     chgrp($path,12); */
/*   } */
}

function setupdirectories($path) {
  directorycheck($path."/log");
  directorycheck($path."/html");
  directorycheck($path."/graph");
  directorycheck($path."/etc");
/*   directorycheck($path."/bin"); */
  $config="<VirtualHost *:80>\n".
    "DocumentRoot /usr/daemon/httpd/domain/base.dom/html\n".
    "  ServerName base.dom\n".
    "  TransferLog /usr/daemon/httpd/domain/base.dom/log/access.log\n".
    "  #AddType application/x-httpd-php .htp\n".
    "  <Directory \"/usr/daemon/httpd/domain/base.dom/html\">\n".
    "    Options FollowSymLinks MultiViews\n".
    "    AllowOverride None\n".
    "    Require all granted\n".
    "  </Directory>\n".
    "</VirtualHost>\n\n";

  $config=
    "<VirtualHost *:80>\n".
    "  DocumentRoot /usr/daemon/httpd/domain/base.dom/html\n".
    "  ServerName base.dom\n".
    "  TransferLog /usr/daemon/httpd/domain/base.dom/log/access.log\n".
    "  #AddType application/x-httpd-php .htp\n".
    "  AddType application/x-httpd-php .php\n".
    "  <Directory \"/usr/daemon/httpd/domain/base.dom/html\">\n".
    "    Options FollowSymLinks MultiViews\n".
    "    AllowOverride None\n".
    "    Require all granted\n".
    "  </Directory>\n".
    "  <Files ~ \"\.phpt$|~$|ChangeLog\">\n".
    "    Order allow,deny\n".
    "    Deny from all\n".
    "  </Files>\n".
    "  #RewriteEngine on\n".
    "  #RewriteRule ^(.*[.])ver[0-9]+[.]([a-zA-z]+)$ $1$2\n".
    "  #RemoteIPHeader X-Forwarded-For\n".
    "  #RemoteIPInternalProxy 10.3.1.0/24\n".
    "  #SSLEngine on\n".
    "  #SSLOptions +StrictRequire\n".
    "  #<Directory />\n".
    "    #SSLRequireSSL\n".
    "  #</Directory>\n".
    "  #SSLProtocol -all +TLSv1 +TLSv1.1 +TLSv1.2 -SSLv2 -SSLv3\n".
    "  #SSLCipherSuite HIGH:MEDIUM:!aNULL:+SHA1:+SHA256:+MD5:+HIGH:+MEDIUM:!RC4\n".
    "  #SSLSessionCacheTimeout 600\n".
    "  #SSLVerifyClient none\n".
    "  #SSLProxyEngine off\n".
    "  #SSLCertificateFile /usr/daemon/httpd/letsencrypt/live/base.dom/cert.pem\n".
    "  #SSLCertificateKeyFile /usr/daemon/httpd/letsencrypt/live/base.dom/privkey.pem\n".
    "  #SSLCACertificateFile /usr/daemon/httpd/letsencrypt/live/base.dom/chain.pem\n".
    "</VirtualHost>\n\n";
  
 filecheck($path."/etc/httpd.conf",$config);
}

function stripdbname($db) {
  if ($pos=strrpos($db,".")) {
    $db=substr($db,0,$pos);
  }
  if ($pos=strpos($db,".")) {
    $db=substr($db,$pos+1);
  }
  return($db);
}

function getmysqldblist($httproot) {
  $dir=@opendir($httproot);
  if (!$dir) {
    printf("No apache directory %s.  Update canceled.\n",$httproot);
    exit;
  }
  while($file=readdir($dir)) {
    if ($file==".." || $file==".")
      continue;
    $domaindir=sprintf("%s/%s",$httproot,$file);
    $dirname=sprintf("%s/%s/mysql",$httproot,$file);
    if (is_link($dirname) || is_link($domaindir))
      continue;
    if (is_dir($dirname)) {
      $db=stripdbname($file);
      $dir2=@opendir($dirname);
      while($file2=readdir($dir2)) {
	if ($file2==".." || $file2==".")
	  continue;
	$dbdir=sprintf("%s/%s/mysql/%s",$httproot,$file,$file2);
	if (!is_dir($dbdir))
	  continue;
	if ($file2=="base" || $file2=="base.mysql")
	  $linkname=$db;
	else
	  $linkname=$file2;
	if ($links[$linkname]) {
	  printf("Error: mysql database declared twice\n%s\n%s\nNo mysql changes made\n",$links[$linkname],$dbdir);
	  exit;
	}
	$links[$linkname]=$dbdir;
      }
    }
  }
  return($links);
}

function getmysqllinks($httproot,$mysqlroot) {
  $dir=@opendir($mysqlroot);
  while($file=readdir($dir)) {
    if ($file==".." || $file==".")
      continue;
    $dirname=sprintf("%s/%s",$mysqlroot,$file);
    if (!is_link($dirname))
      continue;
    $target=readlink($dirname);
    $len=strlen($httproot);
    if ($httproot!=substr($target,0,$len))
      continue;
    $db=substr($target,$len+1);
    $db=substr($db,0,strpos($db,"/"));
    $db=stripdbname($db);
    $links[$db]=$target;
  }
  return($links);
}

function resyncmysql($httproot,$mysqlroot) {
  $httpdlinks=getmysqldblist($httproot);
  $mysqllinks=getmysqllinks($httproot,$mysqlroot);
  reset($mysqllinks);
  while(list($key,$val)=each($mysqllinks)) {
    $dir1=$mysqllinks[$key];
    $dir2=$httpdlinks[$key];
    if ($dir1 && $dir1==$dir2)
      continue;
    $link=sprintf("%s/%s",$mysqlroot,$key);
    if (!is_link($link)) {
      printf("Code Error: removing %s, aborting.\n",$link);
      exit;
    }
    printf("removing database link %s for %s\n",$key,$link);
    unlink($link);
    $flag=1;
  }  
  reset($httpdlinks);
  while(list($key,$val)=each($httpdlinks)) {
    $dir1=$httpdlinks[$key];
    $dir2=$mysqllinks[$key];
    if ($dir1 && $dir1==$dir2)
      continue;
    printf("adding mysql database %s at %s\n",$key,$dir1);
    $link=sprintf("%s/%s",$mysqlroot,$key);
    if (is_dir($link)) {
      printf("Error: can not create link at %s, already in use.  Aborting\n",$link);
      exit;
    }
    symlink($dir1,$link);
    $flag=1;
  }  
  if ($flag) {
    printf("mysql databases changed, requesting server reload\n");
  }
}

/**
 * Build HMAC-SHA256 API auth headers for a JSON body.
 * Used when syncnginx.conf / synccert.conf contains api_key=...
 */
function api_headers(string $apiKey, string $method, string $urlPath, string $jsonBody): array {
  $ts        = time();
  $bodyHash  = hash('sha256', $jsonBody);
  $canonical = "{$ts}\n{$method}\n{$urlPath}\n{$bodyHash}";
  $sig       = hash_hmac('sha256', $canonical, $apiKey);
  return [
    'Content-Type: application/json',
    'X-API-Key: '       . $apiKey,
    'X-API-Timestamp: ' . $ts,
    'X-API-Signature: ' . $sig,
  ];
}

/**
 * Make an HMAC-authenticated API call to the backup server.
 */
function backup_api_call(array $cfg, string $method, string $path, array $body = []): ?array {
  $apiKey  = $cfg['api_key'] ?? '';
  $baseUrl = rtrim($cfg['url'] ?? '', '/');
  if (!$baseUrl || !$apiKey) return null;

  $fullUrl  = $baseUrl . $path;
  $jsonBody = ($method !== 'GET' && !empty($body)) ? json_encode($body) : '';
  $urlPath  = parse_url($fullUrl, PHP_URL_PATH);
  $headers  = api_headers($apiKey, $method, $urlPath, $jsonBody);

  $ch = curl_init($fullUrl);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
  curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  if ($jsonBody !== '') curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonBody);

  $result = curl_exec($ch);
  $err    = curl_error($ch);
  curl_close($ch);

  if ($err) {
    printf("syncbackup: curl error %s %s: %s\n", $method, $path, $err);
    return null;
  }
  return json_decode($result, true) ?: null;
}

/**
 * For each domain with an etc/backup.conf, register modules on the backup server,
 * obtain SSH keys, write per-module backup scripts, and schedule them via cron.d.
 *
 * Global config: /usr/daemon/httpd/etc/syncbackup.conf
 *   url=http://<backup-ip>/api/
 *   api_key=<client-management-key>
 *   client=<this-container-name>
 *   ssh_host=<backup-ip>
 *   ssh_user=bkp_<client>        (default: bkp_<client>)
 *   schedule=0 2 * * *           (default: 2am daily)
 *
 * Per-domain: /usr/daemon/httpd/domain/<fqdn>/etc/backup.conf
 *   module=mysite
 *   dirs=/path/to/html:/path/to/etc
 *   due_hours=48
 */
function posttosyncbackup(string $confFile): void {
  $cfgFile = '/usr/daemon/httpd/etc/syncbackup.conf';
  if (!file_exists($cfgFile)) return;

  $cfg = [];
  foreach (file($cfgFile) as $line) {
    $line = trim($line);
    if ($line === '' || $line[0] === '#') continue;
    if (preg_match('/^(\w+)\s*=\s*(.+)$/', $line, $m)) $cfg[$m[1]] = $m[2];
  }

  $url = $cfg['url'] ?? '';
  if (empty($url)) return;

  $client   = $cfg['client']   ?? gethostname();
  $sshHost  = $cfg['ssh_host'] ?? parse_url($url, PHP_URL_HOST);
  $sshUser  = $cfg['ssh_user'] ?? "bkp_$client";
  $schedule = $cfg['schedule'] ?? '0 2 * * *';
  $keyDir   = '/usr/daemon/httpd/etc/backup-keys';
  $logFile  = '/var/log/backup.log';

  if (!is_dir($keyDir)) mkdir($keyDir, 0700, true);

  $domainBase = '/usr/daemon/httpd/domain';
  $dh = @opendir($domainBase);
  if (!$dh) return;

  while (($domain = readdir($dh)) !== false) {
    if ($domain === '.' || $domain === '..') continue;
    if (is_link("$domainBase/$domain")) continue;
    $backupConf = "$domainBase/$domain/etc/backup.conf";
    if (!file_exists($backupConf)) continue;

    $bcfg = [];
    foreach (file($backupConf) as $line) {
      $line = trim($line);
      if ($line === '' || $line[0] === '#') continue;
      if (preg_match('/^(\w+)\s*=\s*(.+)$/', $line, $m)) $bcfg[$m[1]] = $m[2];
    }

    $module   = $bcfg['module']    ?? preg_replace('/[^a-z0-9_-]/', '-', strtolower($domain));
    $dirsRaw  = $bcfg['dirs']      ?? '';
    $dueHours = (int)($bcfg['due_hours'] ?? 48);

    if (empty($dirsRaw)) {
      printf("syncbackup: %s: no dirs configured in backup.conf\n", $domain);
      continue;
    }
    $dirs = array_values(array_filter(array_map('trim', explode(':', $dirsRaw))));

    // Ensure module exists on backup server
    $modList  = backup_api_call($cfg, 'GET', "/clients/$client/modules");
    $modNames = array_column($modList['modules'] ?? [], 'name');

    if (!in_array($module, $modNames, true)) {
      $r = backup_api_call($cfg, 'POST', "/clients/$client/modules",
                           ['name' => $module, 'due_hours' => $dueHours]);
      if (!$r || isset($r['error'])) {
        printf("syncbackup: %s: failed to create module '%s': %s\n",
               $domain, $module, $r['error'] ?? 'no response');
        continue;
      }
      printf("syncbackup: created module '%s' for %s\n", $module, $domain);
    } else {
      backup_api_call($cfg, 'PUT', "/clients/$client/modules/$module", ['due_hours' => $dueHours]);
    }

    // Ensure backup SSH key exists (private key saved locally)
    $pemFile = "$keyDir/$module.pem";
    if (!file_exists($pemFile)) {
      $r = backup_api_call($cfg, 'POST', "/clients/$client/keys",
                           ['type' => 'backup', 'module' => $module]);
      if (!$r || isset($r['error'])) {
        printf("syncbackup: %s: failed to issue backup key: %s\n",
               $domain, $r['error'] ?? 'no response');
        continue;
      }
      $privKey = $r['private_key'] ?? '';
      if (empty($privKey)) {
        printf("syncbackup: %s: no private key in response for module '%s'\n", $domain, $module);
        continue;
      }
      file_put_contents($pemFile, $privKey);
      chmod($pemFile, 0600);
      chown($pemFile, 'httpd');
      printf("syncbackup: installed backup key for module '%s'\n", $module);
    }

    // Write per-module backup script
    $scriptFile = "/usr/daemon/httpd/etc/backup-$module.sh";
    $scriptLines = [
      "#!/bin/bash",
      "# Auto-generated by synchttpd syncbackup — do not edit manually",
      "# module: $module  domain: $domain",
      "RSYNC=/usr/daemon/rsync/bin/rsync",
      "KEY=" . escapeshellarg($pemFile),
      'SSH_OPTS="-i $KEY -o StrictHostKeyChecking=no -o BatchMode=yes"',
      "",
    ];
    foreach ($dirs as $dir) {
      $subdir = basename(rtrim($dir, '/'));
      $scriptLines[] = "\"\$RSYNC\" -az --delete"
        . " -e \"ssh \$SSH_OPTS\""
        . " " . escapeshellarg(rtrim($dir, '/') . '/')
        . " " . escapeshellarg("$sshUser@$sshHost:/$subdir/");
    }
    $scriptLines[] = "";
    $scriptLines[] = "echo \"backup complete: \$(date)\"";
    file_put_contents($scriptFile, implode("\n", $scriptLines) . "\n");
    chmod($scriptFile, 0750);
    chown($scriptFile, 'httpd');

    // Write cron.d entry (idempotent — overwrites each synchttpd run)
    $cronName    = 'backup-' . preg_replace('/[^a-z0-9]/', '-', strtolower($module));
    $cronFile    = "/etc/cron.d/$cronName";
    $cronContent = "# Backup module '$module' ($domain) — managed by synchttpd\n"
                 . "SHELL=/bin/bash\n"
                 . "$schedule httpd /bin/bash " . escapeshellarg($scriptFile)
                 . " >> " . escapeshellarg($logFile) . " 2>&1\n";
    file_put_contents($cronFile, $cronContent);
    chmod($cronFile, 0644);

    printf("syncbackup: configured backup for '%s' (%d dirs, schedule: %s)\n",
           $module, count($dirs), $schedule);
  }
  closedir($dh);
}

function posttosyncnginx($confFile) {
  $cfgFile = '/usr/daemon/httpd/etc/syncnginx.conf';
  if (!file_exists($cfgFile)) return;
  $cfg = [];
  foreach (file($cfgFile) as $line) {
    $line = trim($line);
    if ($line === '' || $line[0] === '#') continue;
    if (preg_match('/^(\w+)\s*=\s*(.+)$/', $line, $m)) {
      $cfg[$m[1]] = $m[2];
    }
  }
  $url = $cfg['url'] ?? '';
  if (empty($url)) return;
  $apiKey = $cfg['api_key'] ?? '';
  $token  = $cfg['token']   ?? '';
  $ip     = $cfg['ip']      ?? trim(shell_exec("hostname -I | awk '{print $1}'"));
  $config = file_get_contents($confFile);

  // Gather SSL certs for vhosts that have RemoteIPHeader
  $certs = [];
  preg_match_all('/^<VirtualHost[^>]*>(.*?)<\/VirtualHost>/smi', $config, $vmatches);
  foreach ($vmatches[1] as $vhost) {
    if (!preg_match('/RemoteIPHeader/i', $vhost)) continue;
    if (!preg_match('/^\s*ServerName\s+(\S+)/mi', $vhost, $sm)) continue;
    $sn = preg_replace('/:\d+$/', '', trim($sm[1]));
    $privkey   = null;
    $fullchain = null;
    if (preg_match('/^\s*SSLCertificateKeyFile\s+(\S+)/mi', $vhost, $km)) {
      $kf = trim($km[1]);
      if (file_exists($kf)) $privkey = base64_encode(file_get_contents($kf));
    }
    if (preg_match('/^\s*SSLCertificateFile\s+(\S+)/mi', $vhost, $cm)) {
      $cf  = trim($cm[1]);
      $dir = dirname($cf);
      if (file_exists("$dir/fullchain.pem")) {
        $fullchain = base64_encode(file_get_contents("$dir/fullchain.pem"));
      } elseif (file_exists($cf)) {
        if (preg_match('/^\s*SSLCACertificateFile\s+(\S+)/mi', $vhost, $chm) && file_exists(trim($chm[1]))) {
          $fullchain = base64_encode(file_get_contents($cf) . file_get_contents(trim($chm[1])));
        } else {
          $fullchain = base64_encode(file_get_contents($cf));
        }
      }
    }
    if ($privkey !== null && $fullchain !== null) {
      $certs[$sn] = ['privkey' => $privkey, 'fullchain' => $fullchain];
    }
  }

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
  curl_setopt($ch, CURLOPT_TIMEOUT, 10);

  if ($apiKey !== '') {
    // New HMAC-signed API endpoint
    $apiUrl  = preg_replace('/\/syncnginx\.php$/i', '/api/sync', $url);
    $body    = json_encode(['config' => $config, 'ip' => $ip, 'certs' => $certs ?: new stdClass()]);
    $urlPath = parse_url($apiUrl, PHP_URL_PATH);
    curl_setopt($ch, CURLOPT_URL, $apiUrl);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    curl_setopt($ch, CURLOPT_HTTPHEADER, api_headers($apiKey, 'POST', $urlPath, $body));
  } else {
    // Legacy form-POST to syncnginx.php
    $post = ['config' => $config, 'ip' => $ip];
    if (!empty($certs)) $post['certs'] = $certs;
    if ($token !== '') $post['token'] = $token;
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
  }

  $result = curl_exec($ch);
  $err    = curl_error($ch);
  curl_close($ch);

  if ($err) {
    printf("syncnginx: curl error: %s\n", $err);
  } else {
    $data = json_decode($result, true);
    if (($data['status'] ?? $data['ok'] ?? null) !== null
        && ($data['status'] === 'ok' || $data['ok'] === true)) {
      printf("syncnginx: ok, proxied domains: %s\n", implode(', ', array_keys($data['proxied'] ?? [])));
    } else {
      printf("syncnginx: response: %s\n", $result);
    }
  }
}

function posttocertserver($confFile) {
  $cfgFile = '/usr/daemon/httpd/etc/synccert.conf';
  if (!file_exists($cfgFile)) return;
  $cfg = [];
  foreach (file($cfgFile) as $line) {
    $line = trim($line);
    if ($line === '' || $line[0] === '#') continue;
    if (preg_match('/^(\w+)\s*=\s*(.+)$/', $line, $m)) $cfg[$m[1]] = $m[2];
  }
  $url = $cfg['url'] ?? '';
  if (empty($url)) return;
  $apiKey    = $cfg['api_key']          ?? '';
  $token     = $cfg['token']            ?? '';
  $renewDays = (int)($cfg['cert_renew_days'] ?? 30);

  $config = file_get_contents($confFile);

  // Find all SSL-enabled vhosts (those with SSLCertificateFile)
  preg_match_all('/^<VirtualHost[^>]*>(.*?)<\/VirtualHost>/smi', $config, $vmatches);
  foreach ($vmatches[1] as $vhost) {
    if (!preg_match('/SSLCertificateFile/i', $vhost)) continue;
    if (!preg_match('/^\s*ServerName\s+(\S+)/mi', $vhost, $sm)) continue;
    $sn = preg_replace('/:\d+$/', '', trim($sm[1]));

    // Resolve cert path (base.dom is already replaced in the assembled config)
    $needsCert = true;
    if (preg_match('/^\s*SSLCertificateFile\s+(\S+)/mi', $vhost, $cm)) {
      $certPath = trim($cm[1]);
      if (file_exists($certPath)) {
        $certInfo = openssl_x509_parse(file_get_contents($certPath));
        if ($certInfo && isset($certInfo['validTo_time_t'])) {
          $daysLeft = ($certInfo['validTo_time_t'] - time()) / 86400;
          $needsCert = $daysLeft <= $renewDays;
        }
      }
    }

    if (!$needsCert) {
      printf("synccert: %s cert valid, skipping\n", $sn);
      continue;
    }

    printf("synccert: requesting cert for %s\n", $sn);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    // Long timeout: certbot waits for DNS propagation
    curl_setopt($ch, CURLOPT_TIMEOUT, 300);

    if ($apiKey !== '') {
      // New HMAC-signed API endpoint
      $apiUrl  = preg_replace('/\/certrequest\.php$/i', '/api/certs', $url);
      $body    = json_encode(['domain' => $sn]);
      $urlPath = parse_url($apiUrl, PHP_URL_PATH);
      curl_setopt($ch, CURLOPT_URL, $apiUrl);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
      curl_setopt($ch, CURLOPT_HTTPHEADER, api_headers($apiKey, 'POST', $urlPath, $body));
    } else {
      // Legacy form-POST
      $post = ['domain' => $sn];
      if ($token !== '') $post['token'] = $token;
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
    }

    $result = curl_exec($ch);
    $err    = curl_error($ch);
    curl_close($ch);

    if ($err) {
      printf("synccert: curl error for %s: %s\n", $sn, $err);
      continue;
    }

    $data = json_decode($result, true);
    $ok   = ($data['status'] ?? null) === 'ok' || isset($data['cert']);
    if (!$ok) {
      $msg = $data['message'] ?? $data['error'] ?? $result;
      printf("synccert: error for %s: %s\n", $sn, $msg);
      continue;
    }

    // Save cert files to letsencrypt/live/<domain>/
    $liveDir = "/usr/daemon/httpd/letsencrypt/live/$sn";
    if (!is_dir($liveDir)) mkdir($liveDir, 0755, true);

    if (!empty($data['cert']))      file_put_contents("$liveDir/cert.pem",      base64_decode($data['cert']));
    if (!empty($data['privkey']))   file_put_contents("$liveDir/privkey.pem",   base64_decode($data['privkey']));
    if (!empty($data['fullchain'])) file_put_contents("$liveDir/fullchain.pem", base64_decode($data['fullchain']));
    // chain.pem = fullchain for compatibility
    if (!empty($data['fullchain'])) file_put_contents("$liveDir/chain.pem",     base64_decode($data['fullchain']));

    chmod("$liveDir/privkey.pem", 0640);

    printf("synccert: cert saved for %s (expires %s, SANs: %s)\n",
      $sn,
      $data['expiry'] ?? 'unknown',
      implode(', ', $data['sans'] ?? [$sn])
    );
  }
}

function generate_self_signed_cert($domain, $liveDir) {
  if (!is_dir($liveDir)) mkdir($liveDir, 0755, true);
  $keyFile  = "$liveDir/privkey.pem";
  $certFile = "$liveDir/cert.pem";
  $cmd = sprintf(
    'openssl req -x509 -nodes -days 3650 -newkey rsa:2048 ' .
    '-keyout %s -out %s -subj %s 2>&1',
    escapeshellarg($keyFile),
    escapeshellarg($certFile),
    escapeshellarg("/CN=$domain")
  );
  exec($cmd, $out, $ret);
  if ($ret !== 0) {
    printf("ssl: openssl failed: %s\n", implode("\n", $out));
    return false;
  }
  copy($certFile, "$liveDir/fullchain.pem");
  copy($certFile, "$liveDir/chain.pem");
  chmod($keyFile, 0640);
  printf("ssl: generated self-signed cert for %s\n", $domain);
  return true;
}

function ensure_ssl_cert($domain) {
  $liveDir  = "/usr/daemon/httpd/letsencrypt/live/$domain";
  $certFile = "$liveDir/cert.pem";
  $keyFile  = "$liveDir/privkey.pem";

  // Use existing cert if still valid for more than 1 day
  if (file_exists($certFile) && file_exists($keyFile)) {
    $info = openssl_x509_parse(file_get_contents($certFile));
    if ($info && isset($info['validTo_time_t']) && $info['validTo_time_t'] > time() + 86400) {
      printf("ssl: existing cert for %s is valid\n", $domain);
      return true;
    }
  }

  // Try cert server if configured
  $cfgFile = '/usr/daemon/httpd/etc/synccert.conf';
  if (file_exists($cfgFile)) {
    $cfg = [];
    foreach (file($cfgFile) as $line) {
      $line = trim($line);
      if ($line === '' || $line[0] === '#') continue;
      if (preg_match('/^(\w+)\s*=\s*(.+)$/', $line, $m)) $cfg[$m[1]] = $m[2];
    }
    $url    = $cfg['url']     ?? '';
    $apiKey = $cfg['api_key'] ?? '';
    if (!empty($url)) {
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
      curl_setopt($ch, CURLOPT_TIMEOUT, 300);
      if ($apiKey !== '') {
        $apiUrl  = preg_replace('/\/certrequest\.php$/i', '/api/certs', $url);
        $body    = json_encode(['domain' => $domain]);
        $urlPath = parse_url($apiUrl, PHP_URL_PATH);
        curl_setopt($ch, CURLOPT_URL, $apiUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        curl_setopt($ch, CURLOPT_HTTPHEADER, api_headers($apiKey, 'POST', $urlPath, $body));
      } else {
        $post = ['domain' => $domain];
        if (!empty($cfg['token'])) $post['token'] = $cfg['token'];
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
      }
      $result = curl_exec($ch);
      $err    = curl_error($ch);
      curl_close($ch);
      if ($err) {
        printf("ssl: cert server unreachable (%s), falling back to self-signed\n", $err);
      } else {
        $data = json_decode($result, true);
        $ok   = ($data['status'] ?? null) === 'ok' || isset($data['cert']);
        if ($ok) {
          if (!is_dir($liveDir)) mkdir($liveDir, 0755, true);
          if (!empty($data['cert']))      file_put_contents("$liveDir/cert.pem",      base64_decode($data['cert']));
          if (!empty($data['privkey']))   file_put_contents("$liveDir/privkey.pem",   base64_decode($data['privkey']));
          if (!empty($data['fullchain'])) file_put_contents("$liveDir/fullchain.pem", base64_decode($data['fullchain']));
          if (!empty($data['fullchain'])) file_put_contents("$liveDir/chain.pem",     base64_decode($data['fullchain']));
          chmod("$liveDir/privkey.pem", 0640);
          printf("ssl: cert obtained from cert server for %s (expires %s)\n",
            $domain, $data['expiry'] ?? 'unknown');
          return true;
        }
        printf("ssl: cert server error for %s: %s, falling back to self-signed\n",
          $domain, $data['message'] ?? $data['error'] ?? $result);
      }
    }
  } else {
    printf("ssl: no synccert.conf configured, using self-signed\n");
  }

  return generate_self_signed_cert($domain, $liveDir);
}

function enable_ssl_vhost($domain) {
  $confFile = "/usr/daemon/httpd/domain/$domain/etc/httpd.conf";
  if (!file_exists($confFile)) {
    printf("ssl: no httpd.conf found for %s\n", $domain);
    return false;
  }

  if (!ensure_ssl_cert($domain)) {
    printf("ssl: could not obtain cert for %s\n", $domain);
    return false;
  }

  $conf = file_get_contents($confFile);

  // Update existing 443 block if present
  if (preg_match('/<VirtualHost[^>]*:443[^>]*>/i', $conf)) {
    printf("ssl: 443 vhost already present for %s\n", $domain);
    return true;
  }

  $ssl_block =
    "\n<VirtualHost *:443>\n" .
    "  DocumentRoot /usr/daemon/httpd/domain/base.dom/html\n" .
    "  ServerName base.dom\n" .
    "  TransferLog /usr/daemon/httpd/domain/base.dom/log/access.log\n" .
    "  AddType application/x-httpd-php .php\n" .
    "  <Directory \"/usr/daemon/httpd/domain/base.dom/html\">\n" .
    "    Options FollowSymLinks MultiViews\n" .
    "    AllowOverride None\n" .
    "    Require all granted\n" .
    "  </Directory>\n" .
    "  RemoteIPHeader X-Forwarded-For\n" .
    "  SSLEngine on\n" .
    "  SSLCertificateFile /usr/daemon/httpd/letsencrypt/live/base.dom/cert.pem\n" .
    "  SSLCertificateKeyFile /usr/daemon/httpd/letsencrypt/live/base.dom/privkey.pem\n" .
    "  SSLCACertificateFile /usr/daemon/httpd/letsencrypt/live/base.dom/chain.pem\n" .
    "</VirtualHost>\n";

  file_put_contents($confFile, $conf . $ssl_block);
  printf("ssl: added 443 vhost for %s\n", $domain);
  return true;
}

function resyncapache($root) {
  copy("/usr/daemon/httpd/conf/settings.httpd.conf","/usr/daemon/httpd/conf/new.httpd.conf");
  $f=fopen("/usr/daemon/httpd/conf/new.httpd.conf","a+");

  $dir=@opendir($basedir="/usr/daemon/httpd/domain");
  if (!$dir) {
    printf("No /usr/daemon/httpd/domain.  Update canceled.\n");
    exit;
  }
  
  while($file=readdir($dir)) {
    if ($file==".." || $file==".")
      continue;
    setupdirectories($basedir."/".$file);
    $fname=sprintf("/usr/daemon/httpd/domain/%s/etc/httpd.conf",$file);
    if (!is_file($fname)) {
      printf("Did not find a config segment for %s.  No changes made.\n",$file);
      exit;
    }
    fwrite($f,sprintf("\n#From file %s\n",$fname));
    $conf=fopen($fname,"r");
    while($buf=fgets($conf,2048)) {
      fwrite($f,sprintf("%s\n",str_replace("base.dom",$file,rtrim($buf))));
    }
/*     system(sprintf("cat $fname | sed s/base.dom/$file/ >> /usr/daemon/httpd/conf/new.httpd.conf")); */
    fclose($conf);
  } 
  fclose($f);
  closedir($dir);
  
  system("/usr/daemon/httpd/bin/httpd -t -f /usr/daemon/httpd/conf/new.httpd.conf");
  
/*  $p=popen("/usr/sbin/httpd -t -f /usr/daemon/httpd/conf/new.httpd.conf 2>&1","r"); */
  $p=popen("/usr/daemon/httpd/bin/httpd -t -f /usr/daemon/httpd/conf/new.httpd.conf 2>&1","r");
  if (!$p) {
    printf("Error: install check failed to run httpd -t\n");
    exit;
  }

  $buf=trim(fgets($p,2048));
  if ($buf=="Syntax OK") {
    printf("Current configuration has been accepted\n");
    copy("/usr/daemon/httpd/conf/new.httpd.conf","/usr/daemon/httpd/conf/httpd.conf");
    system("killall -1 httpd");
    posttocertserver("/usr/daemon/httpd/conf/httpd.conf");
    posttosyncnginx("/usr/daemon/httpd/conf/httpd.conf");
    posttosyncbackup("/usr/daemon/httpd/conf/httpd.conf");
  } else {
    printf("Due to errors, no changes have been\n");
  }
}

if (!$GLOBALS["_SQTEST_"]) {
  $ssl_domain = null;
  for ($i = 1; $i < $argc; $i++) {
    if ($argv[$i] === '--ssl' && isset($argv[$i + 1])) {
      $ssl_domain = $argv[++$i];
    }
  }
  if ($ssl_domain !== null) {
    if (enable_ssl_vhost($ssl_domain)) {
      resyncapache("/usr/daemon/httpd/domain");
    }
  } else {
    resyncapache("/usr/daemon/httpd/domain");
  }
}

?>
