DNS  [GELÖST]

Diskussionen zur Verwendung der KeyHelp API.
Post Reply
BleckHall
Posts: 22
Joined: Sun 14. Jul 2024, 21:27

DNS

Post by BleckHall »

Hallo zusammen,

ich habe mir mit der API ein interface für die DNS Verwaltung gebastelt, jedoch habe ich das Problem das die DNS Einträge nicht in Keyhelp überschrieben werden sondern diese werden immer auf Standard werte zurückgesetzt hat jemand eine Idee ich steh echt etwas aufm schlauch =(

PS: über KI hab ichs schon versucht aber auch nicht wirklich hilfreich.

Code: Select all

config.php

<?php
return [
  'db' => [
    'dsn' => 'mysql:host=xxx;dbname=xxx;charset=utf8mb4',
    'user' => 'xxxxx',
    'pass' => 'xxxxx',
  ],

  // KeyHelp API 
  'keyhelp' => [
     'base_url' => 'https://xxxxxx/api/v2.13',
    'api_key'  => 'xxxxxxxxxxxxxxxx',
    'endpoints' => [
      'domains_list' => '/domains',          // GET
      'dns_get'      => '/dns/{domainId}',   // GET
      'dns_put'      => '/dns/{domainId}',   // PUT (vollständiger Satz)
    ],

    'auth_header' => 'X-API-Key',
  ],

  'session_name' => 'dns_panel_sess',
];

Code: Select all

dns.php

<?php
require __DIR__ . '/src/bootstrap.php';
require_login();

$domainId = (int)($_GET['domain_id'] ?? 0);
if (!$domainId) { header('Location: /domains.php'); exit; }

if (function_exists('is_customer') && is_customer()) {
  $uid = (int)($_SESSION['user_id'] ?? 0);
  $st = $pdo->prepare("SELECT 1 FROM user_domains WHERE user_id=? AND domain_id=?");
  $st->execute([$uid, $domainId]);
  if (!$st->fetchColumn()) { http_response_code(403); exit("Forbidden"); }
}

$st = $pdo->prepare("SELECT id, domain_name, keyhelp_domain_id FROM domains WHERE id=?");
$st->execute([$domainId]);
$domain = $st->fetch();
if (!$domain) { http_response_code(404); exit("Domain nicht gefunden"); }
if (!$domain['keyhelp_domain_id']) { exit("Domain-ID fehlt bei dieser Domain"); }

$remoteDomainId = (int)$domain['keyhelp_domain_id'];

$error = null; $ok = null;
$dns = null;

function pick(array $arr, array $keys, $default=null) {
  foreach ($keys as $k) if (array_key_exists($k, $arr)) return $arr[$k];
  return $default;
}
function i($v): int { return (int)preg_replace('/[^0-9]/', '', (string)$v); }
function is_assoc(array $a): bool { return array_keys($a) !== range(0, count($a)-1); }

function get_records_container_key(array $dns): ?string {
  foreach (['records','dns_records','entries','zone_records','custom_records'] as $k) {
    if (array_key_exists($k, $dns) && is_array($dns[$k])) return $k;
  }
  return null;
}

function display_value(array $raw): string {
  $type = strtoupper((string)pick($raw, ['type'], ''));
  $prio = pick($raw, ['priority','prio'], null);
  $target = pick($raw, ['exchange','target','destination','value','content','data'], '');
  if ($type === 'MX' && $prio !== null && $prio !== '') {
    return trim((string)$prio . ' ' . (string)$target);
  }
  if ($type === 'SRV') {
    $p = pick($raw, ['priority','prio'], null);
    $w = pick($raw, ['weight'], null);
    $port = pick($raw, ['port'], null);
    $t = pick($raw, ['target','destination','value','content','data'], '');
    $parts = [];
    if ($p!==null && $p!=='') $parts[]=(string)$p;
    if ($w!==null && $w!=='') $parts[]=(string)$w;
    if ($port!==null && $port!=='') $parts[]=(string)$port;
    if ((string)$t!=='') $parts[]=(string)$t;
    return trim(implode(' ', $parts));
  }
  return (string)pick($raw, ['value','content','data','target','destination','exchange'], '');
}

function flatten_records($records): array {
  $out = [];
  if (!is_array($records)) return $out;

  if (is_assoc($records)) {
    foreach ($records as $group => $list) {
      if (!is_array($list)) continue;
      foreach ($list as $r) {
        if (!is_array($r)) continue;
        $out[] = [
          'group' => (string)$group,
          'id'    => $r['id'] ?? null,
          'host'  => (string)pick($r, ['host','hostname','name'], ''),
          'ttl'   => (string)pick($r, ['ttl'], ''),
          'type'  => strtoupper((string)pick($r, ['type'], 'A')),
          'value' => display_value($r),
          '_raw'  => $r,
        ];
      }
    }
  } else {
    foreach ($records as $r) {
      if (!is_array($r)) continue;
      $out[] = [
        'group' => 'other',
        'id'    => $r['id'] ?? null,
        'host'  => (string)pick($r, ['host','hostname','name'], ''),
        'ttl'   => (string)pick($r, ['ttl'], ''),
        'type'  => strtoupper((string)pick($r, ['type'], 'A')),
        'value' => display_value($r),
        '_raw'  => $r,
      ];
    }
  }

  return $out;
}

function apply_record_edit(array $raw, string $host, string $type, string $value, string $ttl): array {
  if (array_key_exists('hostname', $raw)) $raw['hostname'] = $host;
  elseif (array_key_exists('host', $raw)) $raw['host'] = $host;
  elseif (array_key_exists('name', $raw)) $raw['name'] = $host;
  else $raw['host'] = $host;

  $raw['type'] = $type;
  if ($ttl !== '') $raw['ttl'] = i($ttl);

  foreach (['value','content','data','target','destination','exchange','priority','weight','port','prio'] as $k) {
    if (in_array($k, ['priority','weight','port','prio','exchange','target','destination','value','content','data'], true)) {
    }
  }

  if ($type === 'MX') {
    $m = [];
    if (preg_match('/^\s*(\d+)\s+(.+)\s*$/', $value, $m)) {
      $raw['priority'] = (int)$m[1];
      $t = trim($m[2]);
      if (array_key_exists('exchange', $raw)) $raw['exchange'] = $t;
      elseif (array_key_exists('target', $raw)) $raw['target'] = $t;
      elseif (array_key_exists('destination', $raw)) $raw['destination'] = $t;
      else $raw['value'] = $t;
    } else {
      if (array_key_exists('value', $raw)) $raw['value'] = $value;
      elseif (array_key_exists('content', $raw)) $raw['content'] = $value;
      else $raw['value'] = $value;
    }
    return $raw;
  }

  if ($type === 'SRV') {
    $parts = preg_split('/\s+/', trim($value));
    if (count($parts) >= 4 && ctype_digit($parts[0]) && ctype_digit($parts[1]) && ctype_digit($parts[2])) {
      $raw['priority'] = (int)$parts[0];
      $raw['weight'] = (int)$parts[1];
      $raw['port'] = (int)$parts[2];
      $t = implode(' ', array_slice($parts, 3));
      if (array_key_exists('target', $raw)) $raw['target'] = $t;
      elseif (array_key_exists('destination', $raw)) $raw['destination'] = $t;
      else $raw['value'] = $t;
    } else {
      if (array_key_exists('value', $raw)) $raw['value'] = $value;
      elseif (array_key_exists('content', $raw)) $raw['content'] = $value;
      else $raw['value'] = $value;
    }
    return $raw;
  }

  if (array_key_exists('content', $raw)) $raw['content'] = $value;
  elseif (array_key_exists('data', $raw)) $raw['data'] = $value;
  elseif (array_key_exists('target', $raw) && in_array($type, ['CNAME','NS','PTR','TLSA','NAPTR','CAA'], true)) $raw['target'] = $value;
  elseif (array_key_exists('destination', $raw)) $raw['destination'] = $value;
  elseif (array_key_exists('value', $raw)) $raw['value'] = $value;
  else $raw['value'] = $value;

  return $raw;
}

try {
  $dns = $keyhelp->getDns($remoteDomainId);
} catch (Throwable $e) {
  $error = $e->getMessage();
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  try {
    $current = $keyhelp->getDns($remoteDomainId);
    if (!is_array($current)) throw new RuntimeException("Unerwartetes DNS-Format");

    $containerKey = get_records_container_key($current);
    if (!$containerKey) throw new RuntimeException("DNS Records nicht gefunden");

    $recordsContainer = $current[$containerKey];
    $grouped = is_array($recordsContainer) && is_assoc($recordsContainer);

    $zoneTtl = trim($_POST['zone_ttl'] ?? '');
    if ($zoneTtl !== '') {
      if (array_key_exists('ttl', $current)) $current['ttl'] = i($zoneTtl);
      elseif (array_key_exists('default_ttl', $current)) $current['default_ttl'] = i($zoneTtl);
      else $current['ttl'] = i($zoneTtl);
    }

    $soa = is_array(pick($current, ['soa'], null)) ? $current['soa'] : [];
    $soaPrimary = trim($_POST['soa_primary_ns'] ?? '');
    $soaRname   = trim($_POST['soa_rname'] ?? '');
    $soaRefresh = trim($_POST['soa_refresh'] ?? '');
    $soaRetry   = trim($_POST['soa_retry'] ?? '');
    $soaExpire  = trim($_POST['soa_expire'] ?? '');
    $soaMinTtl  = trim($_POST['soa_minimum'] ?? '');

    if ($soaPrimary !== '') $soa['primary_ns'] = $soaPrimary;
    if ($soaRname   !== '') $soa['rname'] = $soaRname;
    if ($soaRefresh !== '') $soa['refresh'] = i($soaRefresh);
    if ($soaRetry   !== '') $soa['retry'] = i($soaRetry);
    if ($soaExpire  !== '') $soa['expire'] = i($soaExpire);
    if ($soaMinTtl  !== '') $soa['minimum'] = i($soaMinTtl);
    if (!empty($soa)) $current['soa'] = $soa;

    $ids     = $_POST['rec_id'] ?? [];
    $groups  = $_POST['rec_group'] ?? [];
    $hosts   = $_POST['rec_host'] ?? [];
    $ttls    = $_POST['rec_ttl'] ?? [];
    $types   = $_POST['rec_type'] ?? [];
    $values  = $_POST['rec_value'] ?? [];
    $raws    = $_POST['rec_raw'] ?? [];

    if (!is_array($hosts) || !is_array($types) || !is_array($values)) {
      throw new RuntimeException("Ungültige Eingaben");
    }

    if ($grouped) $newContainer = []; else $newContainer = [];

    $n = max(count($hosts), count($types), count($values));
    for ($k=0; $k<$n; $k++) {
      $host = strtolower(trim((string)($hosts[$k] ?? '')));
      $type = strtoupper(trim((string)($types[$k] ?? 'A')));
      $val  = trim((string)($values[$k] ?? ''));
      $ttl  = trim((string)($ttls[$k] ?? ''));
      $grp  = strtolower(trim((string)($groups[$k] ?? 'other')));
      if ($grp === '') $grp = 'other';

      if ($host === '' && $val === '' && $type === '') continue;
      if ($type === '') $type = 'A';

      $base = [];
      if (!empty($raws[$k])) {
        $tmp = json_decode((string)$raws[$k], true);
        if (is_array($tmp)) $base = $tmp;
      }

      $id = $ids[$k] ?? null;
      if ($id !== null && $id !== '') $base['id'] = $id;

      $base = apply_record_edit($base, $host, $type, $val, $ttl);

      if ($grouped) {
        if (!isset($newContainer[$grp]) || !is_array($newContainer[$grp])) $newContainer[$grp] = [];
        $newContainer[$grp][] = $base;
      } else {
        $newContainer[] = $base;
      }
    }

    $current[$containerKey] = $newContainer;

    $keyhelp->putDns($remoteDomainId, $current);

    $after = $keyhelp->getDns($remoteDomainId);
    $beforeHash = hash('sha256', json_encode($current, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
    $afterHash  = hash('sha256', json_encode($after, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
    if ($beforeHash !== $afterHash) {
      $ok = "Gespeichert";
    } else {
      $ok = "Gespeichert";
    }

    if (function_exists('is_staff') && is_staff()) {
      audit($pdo, 'DNS_UPDATE', 'domain_id=' . $domainId);
    }

    $dns = $after;

  } catch (Throwable $e) {
    $error = $e->getMessage();
  }
}

$zoneHost = (string)pick($dns ?? [], ['host','domain','zone','name'], $domain['domain_name']);
$zoneTtlV = pick($dns ?? [], ['ttl','default_ttl'], '');
$soa = is_array(pick($dns ?? [], ['soa'], null)) ? $dns['soa'] : [];

$containerKey = is_array($dns) ? get_records_container_key($dns) : null;
$recordsContainer = ($containerKey && is_array($dns[$containerKey] ?? null)) ? $dns[$containerKey] : [];
$records = flatten_records($recordsContainer);

$types = ['A','AAAA','CNAME','MX','NS','TXT','SRV','CAA','PTR','NAPTR','TLSA'];

render_header("DNS: " . $domain['domain_name']);
?>

Code: Select all

keyhelp.php

<?php
final class KeyHelpClient {
  private string $baseUrl;
  private string $apiKey;
  private string $authHeader;
  private array $ep;

  public function __construct(array $cfg) {
    $this->baseUrl    = rtrim($cfg['base_url'], '/');
    $this->apiKey     = $cfg['api_key'];
    $this->authHeader = $cfg['auth_header'] ?? 'X-API-Key';
    $this->ep         = $cfg['endpoints'];
  }

  private function request(string $method, string $path, ?array $json = null): array {
    $url = $this->baseUrl . $path;

    $ch = curl_init($url);
    $headers = [
      'Accept: application/json',
      'Content-Type: application/json',
      $this->authHeader . ': ' . $this->apiKey,
    ];

    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_CUSTOMREQUEST  => $method,
      CURLOPT_HTTPHEADER     => $headers,
      CURLOPT_TIMEOUT        => 30,

      CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,

      CURLOPT_FOLLOWLOCATION => true,
      CURLOPT_MAXREDIRS      => 5,
      CURLOPT_ENCODING       => '',
      CURLOPT_USERAGENT      => 'dns-panel/1.0',
    ]);

    if ($json !== null) {
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json, JSON_UNESCAPED_UNICODE));
    }

    $body = curl_exec($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);

    if ($body === false) {
      throw new RuntimeException("KeyHelp API cURL error for $url: $err");
    }

    $decoded = json_decode($body, true);

    $isJson = is_array($decoded);

    if ($code < 200 || $code >= 300) {
      $msg = $isJson ? json_encode($decoded, JSON_UNESCAPED_UNICODE) : $body;
      throw new RuntimeException("KeyHelp API HTTP $code for $url: $msg");
    }

    return $isJson ? $decoded : [];
  }

  private function withId(string $tpl, int $domainId): string {
    return str_replace('{domainId}', (string)$domainId, $tpl);
  }

  public function listDomains(array $query = []): array {
    $path = $this->ep['domains_list'];
    if ($query) $path .= '?' . http_build_query($query);
    return $this->request('GET', $path);
  }

  public function getDns(int $domainId): array {
    return $this->request('GET', $this->withId($this->ep['dns_get'], $domainId));
  }

  public function putDns(int $domainId, array $payload): array {
    return $this->request('PUT', $this->withId($this->ep['dns_put'], $domainId), $payload);
  }
}
User avatar
24unix
Posts: 2165
Joined: Sun 21. Jun 2020, 17:16
Location: Kollmar
Contact:

Re: DNS

Post by 24unix »

Ich kann mir das gerne morgen mal angucken, bin jetzt nur am Notebook, aber evtl. hilft es, den Code besser zu formatieren.

Code: Select all

function pick(array $arr, array $keys, $default=null) {
  foreach ($keys as $k) if (array_key_exists($k, $arr)) return $arr[$k];
  return $default;
}
Das ist schlecht zu lesen, und fehlerträchtig, und zieht sich so fort.

PSR ist mittlerweile (PSR-12) echt brauchbar, und eigentlich sollte man sich daran halten, um Allen das Leben einfacher zu machen.

Code: Select all

function pick(array $arr, array $keys, $default=null) {
  foreach ($keys as $k) {
    if (array_key_exists($k, $arr)) {
      return $arr[$k];
    }
  }
  return $default;
}
Das liest sich doch viel besser, oder?
Cheers Micha
--
A backend dev walks into a bar, orders 1 beer.
Then orders 100 beers.
Then orders -1 beers.
Then orders “a lizard”.
Then explodes.

The bartender says: “You really should validate your input.”
BleckHall
Posts: 22
Joined: Sun 14. Jul 2024, 21:27

Re: DNS

Post by BleckHall »

24unix wrote: Sat 3. Jan 2026, 23:06 Ich kann mir das gerne morgen mal angucken, bin jetzt nur am Notebook, aber evtl. hilft es, den Code besser zu formatieren.

Code: Select all

function pick(array $arr, array $keys, $default=null) {
  foreach ($keys as $k) if (array_key_exists($k, $arr)) return $arr[$k];
  return $default;
}
Das ist schlecht zu lesen, und fehlerträchtig, und zieht sich so fort.

PSR ist mittlerweile (PSR-12) echt brauchbar, und eigentlich sollte man sich daran halten, um Allen das Leben einfacher zu machen.

Code: Select all

function pick(array $arr, array $keys, $default=null) {
  foreach ($keys as $k) {
    if (array_key_exists($k, $arr)) {
      return $arr[$k];
    }
  }
  return $default;
}
Das liest sich doch viel besser, oder?

Das würde mir sehr helfen.

Ich schreibe es nochmal um damit es besser lesbar wird =)
User avatar
24unix
Posts: 2165
Joined: Sun 21. Jun 2020, 17:16
Location: Kollmar
Contact:

Re: DNS

Post by 24unix »

BleckHall wrote: Sat 3. Jan 2026, 23:18 Ich schreibe es nochmal um damit es besser lesbar wird =)
Die bootstrap.php wäre auch hilfreich, dann ich es mir das lokal zusammenbauen.
Cheers Micha
--
A backend dev walks into a bar, orders 1 beer.
Then orders 100 beers.
Then orders -1 beers.
Then orders “a lizard”.
Then explodes.

The bartender says: “You really should validate your input.”
User avatar
Tobi
Community Moderator
Posts: 3522
Joined: Thu 5. Jan 2017, 13:24

Re: DNS

Post by Tobi »

In der dns.php wird doch die KeyHelp Datenbank direkt abgefragt? Das hat nix mit API zu tun.

Hat eine KI das Skript geschrieben?
Gruß,
Tobi


-----------------------------
wewoco.de
Das Forum für Reseller, Digital-Agenturen, Bildschirmarbeiter und Mäuseschubser
User avatar
24unix
Posts: 2165
Joined: Sun 21. Jun 2020, 17:16
Location: Kollmar
Contact:

Re: DNS

Post by 24unix »

Tobi wrote: Sun 4. Jan 2026, 13:18 In der dns.php wird doch die KeyHelp Datenbank direkt abgefragt? Das hat nix mit API zu tun.

Hat eine KI das Skript geschrieben?
So, wie ich das bis jetzt sehe, wird da eine lokale Db genutzt.

Hier wird die src/bootstrap.php eingebunden,

Code: Select all

require __DIR__ . '/src/bootstrap.php';
Und da werden wohl etliche Sachen definiert, die im eigentlichem Script dann fehlen.
Z.B.:

Code: Select all

    $st = $pdo->prepare("SELECT 1 FROM user_domains WHERE user_id=? AND domain_id=?");
Und das ist wohl ein API Zugriff:

Code: Select all

    $dns = $keyhelp->getDns($remoteDomainId);
Cheers Micha
--
A backend dev walks into a bar, orders 1 beer.
Then orders 100 beers.
Then orders -1 beers.
Then orders “a lizard”.
Then explodes.

The bartender says: “You really should validate your input.”
User avatar
24unix
Posts: 2165
Joined: Sun 21. Jun 2020, 17:16
Location: Kollmar
Contact:

Re: DNS  [GELÖST]

Post by 24unix »

BleckHall wrote: Sat 3. Jan 2026, 23:18 Ich schreibe es nochmal um damit es besser lesbar wird =)
Ich habe mal die files PSR-12 compliant angepasst.
Wenn Du uns die bootstrap.php gibst, und Deine verwendete Verzeichnisstruktur, kann ich mir eine lauffähige Demo nachbauen und gucken, was klemmt.

Ich kann aber nicht versprechen, wie schnell das geht, mein Urlaub ist um, Morgen geht es wieder los und wir haben bald einen neuen Release, Chef will natürlich am liebsten gestern …
Attachments
dns.zip
(4.91 KiB) Downloaded 7 times
Cheers Micha
--
A backend dev walks into a bar, orders 1 beer.
Then orders 100 beers.
Then orders -1 beers.
Then orders “a lizard”.
Then explodes.

The bartender says: “You really should validate your input.”
BleckHall
Posts: 22
Joined: Sun 14. Jul 2024, 21:27

Re: DNS

Post by BleckHall »

Danke für deine Hilfe, habs hinbekommen tatsächlich habe ich doppelt gesendet und somit vor dem senden einen return drin gehabt, das keyhelp die standardconfig zurückgesendet hat
Post Reply