<?php

$ipAddress = $_SERVER['REMOTE_ADDR'];
$hostName = $_REQUEST['hostname'];
$userName = "";
$userPassword = "";

if (!isset($hostName)) {
    die("missing parameter hostname");
}

if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
    $userName = $_SERVER['PHP_AUTH_USER'];
    $userPassword = $_SERVER['PHP_AUTH_PW'];
} else {
    authFail();
}

try {
    $db = new PDO("sqlite:/var/lib/powerdns/pdns.sqlite3");
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    if (!checkUserPassword($db, $hostName, $userName, $userPassword)) {
        error_log("invalid user / password");
        authFail();
    }
    updateHostnameRecord($db, $ipAddress, $hostName);
    updateSerial($db);
} catch (Exception $exception) {
    header("HTTP/1.0 500 Internal Server Error");
    print("Internal Server Error, check logs\n");
    error_log(get_class($exception) . ': ' . $exception->getMessage());
    error_log($exception->getFile(). ', line '.$exception->getLine());
    die();
}
error_log("$hostName -> $ipAddress");
print("Success.\n");

function authFail()
{
    header('WWW-Authenticate: Basic realm="updater"');
    header('HTTP/1.0 401 Unauthorized');
    exit(1);
}

/**
 * @param PDO $db
 * @param $hostName
 * @param $userName
 * @param $userPassword
 * @return bool
 */
function checkUserPassword(PDO $db, $hostName, $userName, $userPassword): bool
{
    $dbh = $db->prepare("SELECT users.id AS id,users.password AS password FROM hostnames LEFT JOIN users ON hostnames.userid=users.id WHERE hostname=:hostname");
    $dbh->bindparam(':hostname', $hostName);
    $dbh->execute();
    $dbr = $dbh->fetch(PDO::FETCH_LAZY);

    if (!isset($dbr['id']) || !isset($dbr['password']) ||
        $dbr['id'] !== $userName || !password_verify($userPassword, $dbr['password'])) {
        return false;
    }
    return true;
}

/**
 * @param PDO $db
 * @param $ipAddress
 * @param $hostName
 * @throws Exception
 */
function updateHostnameRecord(PDO $db, $ipAddress, $hostName)
{
    $dbh = $db->prepare("UPDATE records SET content=:content WHERE name=:name");
    $dbh->bindparam(':content', $ipAddress);
    $dbh->bindparam(':name', $hostName);

    if (!$dbh->execute() || $dbh->rowCount() !== 1) {
        throw new Exception('Could not update hostname "' . $hostName . '" to ip address ' . $ipAddress);
    }
}

/**
 * @param PDO $db
 * @throws Exception
 */
function updateSerial(PDO $db)
{
    $dbh = $db->prepare("SELECT content FROM records WHERE type='SOA'");
    $dbh->setFetchMode(PDO::FETCH_ASSOC);
    $dbh->execute();
    $row = $dbh->fetch();

    if (!isset($row['content'])) {
        throw new Exception('could not find SOA record');
    }

    // match SOA string against regular expression
    $result = preg_match("/([^\s]+) ([^\s]+) (\d+) (\d+) (\d+) (\d+) (\d+)/", $row['content'], $match);
    if (!$result) {
        throw new Exception('SOA record not matching regex! ' . $row['content']);
    }

    // increase serial (3rd parameter) by 1
    $newSoaString = sprintf("%s %s %d %d %d %d %d", $match[1], $match[2], $match[3] + 1, $match[4], $match[5], $match[6], $match[7]);

    // store new SOA string
    $dbh = $db->prepare("update records set content=:content where type='SOA'");
    $dbh->bindparam(':content', $newSoaString);

    if (!$dbh->execute() || $dbh->rowCount() !== 1) {
        throw new Exception('Could not update SOA to "' . $newSoaString . '"');
    }
}