DynDNS per API

awado

New Member
Hi, wollte mal das allseits beliebte Thema nochmal aufgreifen, nachdem es ziemlich still geworden ist dazu. In einem korrekt aufgesetzten Demo-Setup habe ich versucht, die Infos der anderen, teils schon recht alten Threads, zusammenzuführen. Kurz zum Setup: Es besteht aus einer VM mit ISPConfig unter Debian 10. Die angelegten Domains sind über Internet erreichbar inkl. Let's Encrypt Wildcard-Zertifikat (über eine zweite VM mit nignx Reverse Proxy inkl. korrekter IP-Adressübergabe der aufrufenden Clients).

Was geht: Ich kann per URL auf einem LAN- oder WAN-Client einen bestehenden DNS Record updaten.
Was nicht geht: Ich kann über diesen Client keinen neuen Record erstellen lassen. Why?

Das Script habe ich komplett neu geschrieben, angeleitet von den zwei github-Versionen, die aber schon eine gewisse Zeit auf dem Buckel haben. (Bitte habt Nachsehen, bin absoluter PHP-Neuling.)

https://github.com/superlinuxero/ddns-update-for-ispconfig
https://github.com/DIXINFOR/ddns-update-for-ispconfig (aus dem Google Cache)

PHP:
<?php
require_once('log.inc.php');

// ISPConfig URL for REMOTE API
$soap_location = 'https://localhost:8080/remote/index.php';
$soap_uri = 'https://localhost:8080/remote/';
$zone = "meinedomain.tld."; // Punkt am Ende!!!

$log = new log("dyndns");
$log->debug("Session started by ".$_SERVER["REMOTE_ADDR"]);

if(isset($_GET["user"]) && !empty($_GET["user"])){
    $user=$_GET["user"];
} else {
    $log->debug("Kein USERNAME!");
    echo "badagent";
    exit;
}

if(isset($_GET["pwd"]) && !empty($_GET["pwd"])){
    $pwd=$_GET["pwd"];
} else {
    $log->debug("Kein PASSWORD!");
    echo "badagent";
    exit;
}

if(isset($_GET["ddomain"]) && !empty($_GET["ddomain"])){
    $ddomain=$_GET["ddomain"];
} else {
    $log->debug("Keine DDOMAIN!");
    echo "dnserr";
    exit;
}

// TODO: Auf verbotene Sub-Domains prüfen! (ns1, ns2, www etc.) -> echo "abuse";

if(isset($_GET["ip"]) && !empty($_GET["ip"])){
    $ip=$_GET["ip"];
} else {
    $ip=$_SERVER["HTTP_X_FORWARDED_FOR"];
}

if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    $log->debug("Keine valide IPv4-Adresse!");
    echo "dnserr";
    exit;
}

$log->debug("USER = ".$user.", PWD = ".$pwd.", DDOMAIN = ".$ddomain.", IP = ".$ip);

$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    )
));

$client = new SoapClient(null, array('location' => $soap_location,
        'uri' => $soap_uri,
        'trace' => 1,
        'exceptions' => 1,
        'stream_context' => $context));
try {      
    if($session_id = $client->login($user,$pwd)){
        $log->debug('Login OK. Session ID ='.$session_id);
   
        // Prüfen, ob Record schon existiert und merken, wenn ja.
        $record_exists=false;
        $id=0;
        do {
            $id++;
            $dns_record = $client->dns_a_get($session_id, $id);
            $record_exists = ($ddomain==$dns_record["name"]);
        } while (!empty($dns_record) && !$record_exists);
       
        if($record_exists){
            if($dns_record["type"] == "A"){
                if ($dns_record["data"] != $ip){
                    $dns_record["data"] = $ip;
                    $dns_record["serial"] = (int)$dns_record["serial"]+1;
                       
//                    TODO: Korrekte SN ermitteln nach Datum + hochzählen.
                       
                    $affected_rows = $client->dns_a_update($session_id, $client_id, $id, $dns_record);
                    $log->debug("Affected Rows = ".$affected_rows.", neue SN = ".$dns_record["serial"]);
                    echo "good";
                } else {
                    echo "nochg";
                }
            } else {
                $log->debug("DNS-Record existiert bereits aber falscher Typ!");
                echo "dnserr";
                exit;
            }
        } else {
            $log->debug("DNS-Record existiert noch nicht.");
            $params = array(
                'server_id' => $dns_record["server_id"],
                'zone' => $dns_record["zone"],
                'name' => $ddomain,
                'type' => 'A',
                'data' => $ip,
                'aux' => '0',
                'ttl' => '3600',
                'active' => 'y',
                'stamp' => date("Y-m-d h:i:s"),
                'serial' => '1'
            );

            // Hier hakt's! Why???
           
            $id = $client->dns_a_add($session_id, $client_id, $params);
            $dns_record = $client->dns_zone_get($session_id, $id);
            $affected_rows = $client->dns_zone_update($session_id, $client_id, $id, $dns_record);
            $log->debug("Number of records that have been changed in the database: ".$affected_rows);
            echo "good";
        }
    } else { echo "badauth"; }
} catch (SoapFault $e) {
    echo $client->__getLastResponse();
    die('SOAP Error: '.$e->getMessage());
}

// TODO: Logouts der Sessions.

?>
 

awado

New Member
Danke für die flotte Reaktion. Fehler sehe ich eben keinen. Drum bin ich etwas ratlos. Nach 'dns_a_add' wird die id tatsächlich hochgezählt. Somit gehe ich davon aus, dass das an sich geklappt hat. Rufe ich die URL ein zweites mal auf, wird mir auch angezeigt, dass der Record schon existiert. Aber im ISPC-Backend sehe ich ihn nicht. 'affected_rows' bleibt auch leer.
 

awado

New Member
Aha! Ich sehe diese Records auch in der MySQL-Datenbank. Allerdings mit der falschen ServerID und Zone. Wie werden die beiden korrekt ermittelt? Wenn ich sie manuell im Script setze, klappt alles.
 

Till

Administrator
Geh mal den Code Deines scriptes Schritt für Schritt durch, Du hast einen if branch für den Fall dass es keinen record gibt, und in dem if branch nutzt Du dann aber den nicht existierenden record als Quelle für die server_id und die Zone. Ich zitiere den Teil mal:

PHP:
} else {
            $log->debug("DNS-Record existiert noch nicht.");
            $params = array(
                'server_id' => $dns_record["server_id"],
                'zone' => $dns_record["zone"],
 

awado

New Member
Ja, da is der Hund drin. :confused: Ich sehe auch grad, dass der Ansatz mit dem Durchzählen per ID in die Hose geht, weil die Record ID immer weiter zählt und somit keine Löschungen reflektiert. "dns_rr_get_all_by_zone" ist der korrekte Ansatz. Den versuche ich mal nach dem Mittagessen.
 

awado

New Member
Ich bekomme beim Updaten den Fehler "Primary ID fehlt!". In der Beschreibung der API steht:
$affected_rows = $client->dns_a_update($session_id, $client_id, $id, $dns_record);
Das scheint dieses $id zu sein, also die ID des DNS Records?
 
Zuletzt bearbeitet:

awado

New Member
Jaein. Die war bei mir schon korrekt gesetzt, auch zum Test manuell, hard coded. Das Problem liegt woanders. Weiß nicht, ob das jetzt ein Feature oder ein Bug ist: Habe nun die Records mit "dns_rr_get_all_by_zone()" ausgelesen. Man erhält dann ein Array mit den Records. Dort habe ich dann das entsprechende Subarray des gewünschten Records geändert und dieses geänderte Subarray im Aufruf übergeben. Das war stets erfolglos, trotz korrekter ID. Ich hab Dir den Code mal drin gelassen, damit das deutlicher wird. Mit einem manuellem Array geht's jetzt.

PHP:
<?php
// Stand: 3.5.2020
// Derzeit nur für IPv4. Datei liegt im Webroot als /dyn/index.php.

// Update-Aufruf, optional mit IP:
// https://www.meinedomain.tld/dyn/?user=kunde&pwd=kunden-PW&ddomain=test2
// https://www.meinedomain.tld/dyn/?user=kunde&pwd=kunden-PW&ddomain=test2&ip=4.5.6.7

// ISPConfig URLs für REMOTE API

$soap_location = 'https://localhost:8080/remote/index.php';
$soap_uri = 'https://localhost:8080/remote/';

// Weitere Werte aus der betroffenen Zone, zu entnehmen aus der MySQL-DB.

$zone = "meinedomain.tld."; // Punkt am Ende!!!
$server_id = 1;
$zone_id = 2;
//$client_id = 0;
$exceptions = array('www','mail','ns','ns1','ns2','ns3');

// Logging

class log {
    private $file;
   
    function __construct($name) { $this->file = str_replace('.', '_', $name).".log" ; }
   
    function debug($log) {
        $log = date('Y-m-d H:i:s')." ".$log."\n";
        $fid = fopen($this->file,"a+");
        fseek($fid, 0, SEEK_END);
        fputs($fid, $log);
        fclose($fid);  
    }
   
    function clean() {
        $fid = fopen($this->file,"a+");
        ftruncate($fid,0);
        fclose($fid);
    }
}

$log = new log("dyndns");
$log->debug("Session started by ".$_SERVER["REMOTE_ADDR"]);

// Prüfen, ob alles korrekt übergeben wurde und in Variablen kopieren.

if(empty($_GET["user"]) || empty($_GET["pwd"]) || empty($_GET["ddomain"])) {
    echo "badauth";
    exit;
}

$user=$_GET["user"];
$pwd=$_GET["pwd"];
$ddomain=$_GET["ddomain"];

if(in_array($ddomain,$exceptions)) {
    echo "abuse";
    exit;
}

// Falls IP übergeben wurde, diese übernehmen. (Zu Testzwecken!)

if(isset($_GET["ip"]) && !empty($_GET["ip"])){
    $ip=$_GET["ip"];
} else {
    $ip=$_SERVER["HTTP_X_FORWARDED_FOR"];
}

// Gültige IPv4?

if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    $log->debug("Keine valide IPv4-Adresse!");
    echo "dnserr";
    exit;
}

$log->debug("USER = ".$user.", PWD = ".$pwd.", DDOMAIN = ".$ddomain.", IP = ".$ip);

// Session starten.

$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    )
));

$client = new SoapClient(null, array('location' => $soap_location,
    'uri' => $soap_uri,
    'trace' => 1,
    'exceptions' => 1,
    'stream_context' => $context));

try {      
    if($session_id = $client->login($user,$pwd)){
        $log->debug('Login OK. Session ID = '.$session_id);
        $dns_records = $client->dns_rr_get_all_by_zone($session_id, $zone_id);

        // Record schon vorhanden?
        if($record_id = array_search($ddomain,array_column($dns_records,'name'))) {
            // Record bereits vorhanden.
            $log->debug('Record vohanden: '.print_r($dns_records[$record_id],true));
            if($dns_records[$record_id]['type'] != 'A') {
                $log->debug("Kein A Record!");
                echo "dnserr";
                $client->logout($session_id);
                exit;
            }
            // IP geändert?
            if($ip == $dns_records[$record_id]['data']) {
                // IP ist gleich geblieben.
                echo "nochg";
            } else {
                // IP hat sich geändert.
                // Serial korrekt hochzählen.
                $old_serial = $dns_records[$record_id]['serial'];
                if(substr($old_serial,0,8) == date("Ymd")) {
                    $new_serial = $old_serial+1;
                } else {
                    $new_serial = date("Ymd01");
                }
                $params = array(
                    'server_id' => $server_id,
                    'zone' => $zone_id,
                    'name' => $ddomain,
                    'type' => 'A',
                    'data' => $ip,
                    'aux' => '0',
                    'ttl' => '3600',
                    'active' => 'y',
                    'stamp' => date("Y-m-d h:i:s"),
                    'serial' => $new_serial
                );
//                Nimmt man den ausgelesenen Record, geht's nicht (affected_rows = 0!!!):
//                $dns_records[$record_id]['data'] = $ip;
//                $dns_records[$record_id]['serial'] = $dns_records[$record_id]['serial']+1;
//                $dns_records[$record_id]['stamp'] = date("Y-m-d h:i:s");
                $log->debug('Record geändert: '.print_r($dns_records[$record_id],true));
                $affected_rows = $client->dns_a_update($session_id, $client_id, $dns_records[$record_id]['id'], $params);
                $log->debug("Affected Rows = ".$affected_rows);
                echo "good";
            }
        } else {
            // Record noch nicht vorhanden.
            $log->debug('Record neu.');
            $params = array(
                'server_id' => $server_id,
                'zone' => $zone_id,
                'name' => $ddomain,
                'type' => 'A',
                'data' => $ip,
                'aux' => '0',
                'ttl' => '3600',
                'active' => 'y',
                'stamp' => date("Y-m-d h:i:s"),
                'serial' => date("Ymd01")
            );
            $record_id = $client->dns_a_add($session_id, $client_id, $params);
            $log->debug("Neue ID = ".$record_id);
            echo "good";
        }
        // Fertig. Abmelden.
        if($client->logout($session_id)) {
            $log->debug('Logged out.');
        }

    } else { echo "badauth"; }
} catch (SoapFault $e) {
    echo $client->__getLastResponse();
    die('SOAP Error: '.$e->getMessage());
}
?>

Vielleicht freut sich der ein oder andere ja über die Lösung.
 

Till

Administrator
Man erhält dann ein Array mit den Records. Dort habe ich dann das entsprechende Subarray des gewünschten Records geändert und dieses geänderte Subarray im Aufruf übergeben. Das war stets erfolglos, trotz korrekter ID.

Du musst Du halt beim programmieren ansehen was Du da übergibst, z.B. mit print_r() ausgeben, wenn es das ist was von der Funktion gefordert ist, dann geht es, andernfalls geht es nicht. Hat also nichts mit bug oder feature zu tun sondern dass Du wenn Du API's verwendest und etwas nicht geht prüfen musst was Du da eigentlich übergibst. Ob Du dabei ein arry übernimmst oder es zusammenbaust istd er Funktion egal, es kommt nur darauf an, was drin ist.
 

Till

Administrator
Nur das was im GIT ist, aber die im master ist nicht die aktuellste Version, die aktuellen Release Versionen sind der stable-3.1 branch.
 

xxfog

Member
Das hab ich auch gemacht. Gibt es irgendwo eine ausführliche Doku zur API? Ich das hier gefunden: https://git.ispconfig.org/ispconfig/ispconfig3/-/tree/master/remoting_client/examples
Hallo, Wie steht es denn um die Funktion?
Hast du ein fertiges Script hinbekommen?
Falls ja: würdest du es teilen?

mich finde es wirklich schade, dass die DDNS Funktion für Clients nicht Bestandteil von ISPConfig ist.
Habe so viele Kunden die gern ihr NAS/Heimnetzwerk über eine subdomain wie Home.kundendomain.tld erreichen wollen.

aber leider wird auch mein feature request seit 9 Jahren nicht angefasst. Ich vermute, dass der Programmiersufwand gar nicht so hoch ist, für jemanden der tief im Code steckt.
Gruß Steffan
 

Werbung

Top