EPP server update, all info commands work

Added Funds 1.0 EPP extension to show the registrar balance
This commit is contained in:
Pinga 2023-08-17 12:40:42 +03:00
parent 36fda7f3db
commit a1c13577b8
5 changed files with 225 additions and 67 deletions

View file

@ -64,6 +64,7 @@ CREATE TABLE IF NOT EXISTS `registry`.`registrar` (
`creditLimit` decimal(8,2) NOT NULL default '0.00', `creditLimit` decimal(8,2) NOT NULL default '0.00',
`creditThreshold` decimal(8,2) NOT NULL default '0.00', `creditThreshold` decimal(8,2) NOT NULL default '0.00',
`thresholdType` enum('fixed','percent') NOT NULL default 'fixed', `thresholdType` enum('fixed','percent') NOT NULL default 'fixed',
`currency` varchar(5) NOT NULL,
`crdate` datetime NOT NULL, `crdate` datetime NOT NULL,
`update` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),

View file

@ -63,6 +63,7 @@ CREATE TABLE registry.registrar (
"creditlimit" decimal(8,2) NOT NULL default '0.00', "creditlimit" decimal(8,2) NOT NULL default '0.00',
"creditthreshold" decimal(8,2) NOT NULL default '0.00', "creditthreshold" decimal(8,2) NOT NULL default '0.00',
"thresholdtype" varchar CHECK ("thresholdtype" IN ( 'fixed','percent' )) NOT NULL default 'fixed', "thresholdtype" varchar CHECK ("thresholdtype" IN ( 'fixed','percent' )) NOT NULL default 'fixed',
"currency" varchar(5) NOT NULL,
"crdate" timestamp without time zone NOT NULL, "crdate" timestamp without time zone NOT NULL,
"update" TIMESTAMP , "update" TIMESTAMP ,
primary key ("id"), primary key ("id"),

View file

@ -28,7 +28,7 @@ class EppWriter {
'create_host' => '_create_host', 'create_host' => '_create_host',
'delete_host' => '_common', 'delete_host' => '_common',
'info_host' => '_info_host', 'info_host' => '_info_host',
'info_balance' => '_info_balance', 'info_funds' => '_info_funds',
'update_host' => '_common', 'update_host' => '_common',
'poll' => '_poll', 'poll' => '_poll',
'unknown' => '_common', 'unknown' => '_common',
@ -861,7 +861,11 @@ class EppWriter {
if (isset($resp['status']) && count($resp['status'])) { if (isset($resp['status']) && count($resp['status'])) {
foreach ($resp['status'] as $s) { foreach ($resp['status'] as $s) {
if (isset($s[1]) && isset($s[2])) { if (isset($s[1]) && isset($s[2])) {
$writer->writeElement('host:status', $s[2], ['s' => $s[0], 'lang' => $s[1]]); $writer->startElement('host:status');
$writer->writeAttribute('s', $s[0]);
$writer->writeAttribute('lang', $s[1]);
$writer->text($s[2]);
$writer->endElement();
} else { } else {
$writer->startElement('host:status'); $writer->startElement('host:status');
$writer->writeAttribute('s', $s[0]); $writer->writeAttribute('s', $s[0]);
@ -869,20 +873,25 @@ class EppWriter {
} }
} }
} }
if (isset($resp['addr']) && !empty($resp['addr'])) {
foreach ($resp['addr'] as $a) { foreach ($resp['addr'] as $a) {
$writer->writeElement('host:addr', $a[1], ['ip' => 'v' . $a[0]]); $writer->startElement('host:addr');
$writer->writeAttribute('ip', 'v' . $a[0]);
$writer->text($a[1]);
$writer->endElement();
}
} }
$writer->writeElement('host:clID', $resp['clID']); $writer->writeElement('host:clID', $resp['clID']);
$writer->writeElement('host:crID', $resp['crID']); $writer->writeElement('host:crID', $resp['crID']);
$writer->writeElement('host:crDate', $resp['crDate']); $writer->writeElement('host:crDate', gmdate('Y-m-d\TH:i:s\.0\Z', strtotime($resp['crDate'])));
if (isset($resp['upID'])) { if (isset($resp['upID'])) {
$writer->writeElement('host:upID', $resp['upID']); $writer->writeElement('host:upID', $resp['upID']);
} }
if (isset($resp['upDate'])) { if (isset($resp['upDate'])) {
$writer->writeElement('host:upDate', $resp['upDate']); $writer->writeElement('host:upDate', gmdate('Y-m-d\TH:i:s\.0\Z', strtotime($resp['upDate'])));
} }
if (isset($resp['trDate'])) { if (isset($resp['trDate'])) {
$writer->writeElement('host:trDate', $resp['trDate']); $writer->writeElement('host:trDate', gmdate('Y-m-d\TH:i:s\.0\Z', strtotime($resp['trDate'])));
} }
$writer->endElement(); // End of 'host:infData' $writer->endElement(); // End of 'host:infData'
$writer->endElement(); // End of 'resData' $writer->endElement(); // End of 'resData'
@ -891,28 +900,29 @@ class EppWriter {
$this->_postamble($writer, $resp); $this->_postamble($writer, $resp);
} }
private function _info_balance($writer, $resp) { private function _info_funds($writer, $resp) {
$this->_preamble($writer, $resp); $this->_preamble($writer, $resp);
if ($this->epp_success($resp['resultCode'])) { if ($this->epp_success($resp['resultCode'])) {
$writer->startElement('resData'); $writer->startElement('resData');
$writer->startElement('balance:infData'); $writer->startElement('funds:infData');
$writer->writeAttribute('xmlns:balance', 'http://www.verisign.com/epp/balance-1.0'); $writer->writeAttribute('xmlns:funds', 'https://namingo.org/epp/funds-1.0');
$writer->writeAttribute('xsi:schemaLocation', 'http://www.verisign.com/epp/balance-1.0 balance-1.0.xsd'); $writer->writeAttribute('xsi:schemaLocation', 'https://namingo.org/epp/funds-1.0 funds-1.0.xsd');
$writer->writeElement('balance:creditLimit', $resp['creditLimit']); $writer->writeElement('funds:balance', $resp['funds']);
$writer->writeElement('balance:balance', $resp['balance']); $writer->writeElement('funds:currency', $resp['currency']);
$writer->writeElement('balance:availableCredit', $resp['availableCredit']); $writer->writeElement('funds:availableCredit', $resp['availableCredit']);
$writer->writeElement('funds:creditLimit', $resp['creditLimit']);
$writer->startElement('balance:creditThreshold'); $writer->startElement('funds:creditThreshold');
if ($resp['thresholdType'] === 'fixed') { if ($resp['thresholdType'] === 'fixed') {
$writer->writeElement('balance:fixed', $resp['creditThreshold']); $writer->writeElement('funds:fixed', $resp['creditThreshold']);
} elseif ($resp['thresholdType'] === 'percent') { } elseif ($resp['thresholdType'] === 'percent') {
$writer->writeElement('balance:percent', $resp['creditThreshold']); $writer->writeElement('funds:percent', $resp['creditThreshold']);
} }
$writer->endElement(); // End of 'balance:creditThreshold' $writer->endElement(); // End of 'funds:creditThreshold'
$writer->endElement(); // End of 'balance:infData' $writer->endElement(); // End of 'funds:infData'
$writer->endElement(); // End of 'resData' $writer->endElement(); // End of 'resData'
} }

View file

@ -5,13 +5,14 @@ function processContactInfo($conn, $db, $xml) {
$clTRID = (string) $xml->command->clTRID; $clTRID = (string) $xml->command->clTRID;
// Validation for contact ID // Validation for contact ID
if (!ctype_alnum($contactID) || strlen($contactID) > 255) { $invalid_identifier = validate_identifier($contactID);
if ($invalid_identifier) {
sendEppError($conn, 2005, 'Invalid contact ID'); sendEppError($conn, 2005, 'Invalid contact ID');
return; return;
} }
try { try {
$stmt = $db->prepare("SELECT * FROM contact WHERE id = :id"); $stmt = $db->prepare("SELECT * FROM contact WHERE identifier = :id");
$stmt->execute(['id' => $contactID]); $stmt->execute(['id' => $contactID]);
$contact = $stmt->fetch(PDO::FETCH_ASSOC); $contact = $stmt->fetch(PDO::FETCH_ASSOC);
@ -23,12 +24,12 @@ function processContactInfo($conn, $db, $xml) {
// Fetch authInfo // Fetch authInfo
$stmt = $db->prepare("SELECT * FROM contact_authInfo WHERE contact_id = :id"); $stmt = $db->prepare("SELECT * FROM contact_authInfo WHERE contact_id = :id");
$stmt->execute(['id' => $contactID]); $stmt->execute(['id' => $contact['id']]);
$authInfo = $stmt->fetch(PDO::FETCH_ASSOC); $authInfo = $stmt->fetch(PDO::FETCH_ASSOC);
// Fetch status // Fetch status
$stmt = $db->prepare("SELECT * FROM contact_status WHERE contact_id = :id"); $stmt = $db->prepare("SELECT * FROM contact_status WHERE contact_id = :id");
$stmt->execute(['id' => $contactID]); $stmt->execute(['id' => $contact['id']]);
$statuses = $stmt->fetchAll(PDO::FETCH_ASSOC); $statuses = $stmt->fetchAll(PDO::FETCH_ASSOC);
$statusArray = []; $statusArray = [];
@ -38,7 +39,7 @@ function processContactInfo($conn, $db, $xml) {
// Fetch postal_info // Fetch postal_info
$stmt = $db->prepare("SELECT * FROM contact_postalInfo WHERE contact_id = :id"); $stmt = $db->prepare("SELECT * FROM contact_postalInfo WHERE contact_id = :id");
$stmt->execute(['id' => $contactID]); $stmt->execute(['id' => $contact['id']]);
$postals = $stmt->fetchAll(PDO::FETCH_ASSOC); $postals = $stmt->fetchAll(PDO::FETCH_ASSOC);
$postalArray = []; $postalArray = [];
@ -62,7 +63,7 @@ function processContactInfo($conn, $db, $xml) {
'resultCode' => 1000, 'resultCode' => 1000,
'msg' => 'Command completed successfully', 'msg' => 'Command completed successfully',
'id' => $contact['id'], 'id' => $contact['id'],
'roid' => $contact['identifier'], 'roid' => 'C_'.$contact['identifier'],
'status' => $statusArray, 'status' => $statusArray,
'postal' => $postalArray, 'postal' => $postalArray,
'voice' => $contact['voice'], 'voice' => $contact['voice'],
@ -87,11 +88,93 @@ function processContactInfo($conn, $db, $xml) {
} }
} }
function processHostInfo($conn, $db, $xml) {
$hostName = $xml->command->info->children('urn:ietf:params:xml:ns:host-1.0')->info->name;
$clTRID = (string) $xml->command->clTRID;
// Validation for host name
if (!preg_match('/^([A-Z0-9]([A-Z0-9-]{0,61}[A-Z0-9]){0,1}\\.){1,125}[A-Z0-9]([A-Z0-9-]{0,61}[A-Z0-9])$/i', $hostName) && strlen($hostName) > 254) {
sendEppError($conn, 2005, 'Invalid host name');
return;
}
try {
$stmt = $db->prepare("SELECT * FROM host WHERE name = :name");
$stmt->execute(['name' => $hostName]);
$host = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$host) {
sendEppError($conn, 2303, 'Object does not exist');
return;
}
// Fetch addresses
$stmt3 = $db->prepare("SELECT `addr`, `ip` FROM `host_addr` WHERE `host_id` = :id");
$stmt3->execute(['id' => $host['id']]);
$addresses = $stmt3->fetchAll(PDO::FETCH_ASSOC);
$addrArray = [];
foreach($addresses as $addr) {
$addrArray[] = [$addr['ip'] === 'v4' ? 4 : 6, $addr['addr']];
}
// Fetch status
$stmt = $db->prepare("SELECT * FROM host_status WHERE host_id = :id");
$stmt->execute(['id' => $host['id']]);
$statuses = $stmt->fetchAll(PDO::FETCH_ASSOC);
$statusArray = [];
foreach($statuses as $status) {
$statusArray[] = [$status['status']];
}
// Check for 'linked' status
$stmt2 = $db->prepare("SELECT domain_id FROM domain_host_map WHERE host_id = :id LIMIT 1");
$stmt2->execute(['id' => $host['id']]);
$domainData = $stmt2->fetch(PDO::FETCH_ASSOC);
if ($domainData) {
$statusArray[] = ['linked'];
}
$response = [
'command' => 'info_host',
'clTRID' => $clTRID,
'svTRID' => generateSvTRID(),
'resultCode' => 1000,
'msg' => 'Command completed successfully',
'name' => $host['name'],
'roid' => 'H_'.$host['id'],
'status' => $statusArray,
'addr' => $addrArray,
'clID' => getRegistrarClid($db, $host['clid']),
'crID' => getRegistrarClid($db, $host['crid']),
'crDate' => $host['crdate'],
'upID' => getRegistrarClid($db, $host['upid']),
'upDate' => $host['update'],
'trDate' => $host['trdate']
];
$epp = new EPP\EppWriter();
$xml = $epp->epp_writer($response);
sendEppResponse($conn, $xml);
} catch (PDOException $e) {
sendEppError($conn, 2400, 'Database error');
}
}
function processDomainInfo($conn, $db, $xml) { function processDomainInfo($conn, $db, $xml) {
$domainName = $xml->command->info->children('urn:ietf:params:xml:ns:domain-1.0')->info->name; $domainName = $xml->command->info->children('urn:ietf:params:xml:ns:domain-1.0')->info->name;
$clTRID = (string) $xml->command->clTRID; $clTRID = (string) $xml->command->clTRID;
// Validation for domain name // Validation for domain name
$invalid_label = validate_label($domainName, $db);
if ($invalid_label) {
sendEppError($conn, 2005, 'Invalid domain name');
return;
}
if (!filter_var($domainName, FILTER_VALIDATE_DOMAIN)) { if (!filter_var($domainName, FILTER_VALIDATE_DOMAIN)) {
sendEppError($conn, 2005, 'Invalid domain name'); sendEppError($conn, 2005, 'Invalid domain name');
return; return;
@ -150,7 +233,7 @@ function processDomainInfo($conn, $db, $xml) {
'resultCode' => 1000, 'resultCode' => 1000,
'msg' => 'Command completed successfully', 'msg' => 'Command completed successfully',
'name' => $domain['name'], 'name' => $domain['name'],
'roid' => $domain['id'], 'roid' => 'D_'.$domain['id'],
'status' => $statusArray, 'status' => $statusArray,
'registrant' => $domain['registrant'], 'registrant' => $domain['registrant'],
'contact' => $transformedContacts, 'contact' => $transformedContacts,
@ -174,3 +257,44 @@ function processDomainInfo($conn, $db, $xml) {
sendEppError($conn, 2400, 'Database error'); sendEppError($conn, 2400, 'Database error');
} }
} }
function processFundsInfo($conn, $db, $xml, $clid) {
$clTRID = (string) $xml->command->clTRID;
try {
$stmt = $db->prepare("SELECT accountBalance, creditLimit, creditThreshold, thresholdType, currency FROM registrar WHERE clid = :id");
$stmt->execute(['id' => $clid]);
$funds = $stmt->fetch(PDO::FETCH_ASSOC);
$creditBalance = ($funds['accountBalance'] < 0) ? -$funds['accountBalance'] : 0;
$availableCredit = $funds['creditLimit'] - $creditBalance;
$availableCredit = number_format($availableCredit, 2, '.', '');
if (!$funds) {
sendEppError($conn, 2303, 'Registrar does not exist');
return;
}
$response = [
'command' => 'info_funds',
'clTRID' => $clTRID,
'svTRID' => generateSvTRID(),
'resultCode' => 1000,
'msg' => 'Command completed successfully',
'funds' => $funds['accountBalance'],
'currency' => $funds['currency'],
'availableCredit' => $availableCredit,
'creditLimit' => $funds['creditLimit'],
'creditThreshold' => $funds['creditThreshold'],
'thresholdType' => $funds['thresholdType']
];
$epp = new EPP\EppWriter();
$xml = $epp->epp_writer($response);
sendEppResponse($conn, $xml);
} catch (PDOException $e) {
sendEppError($conn, 2400, 'Database error');
}
}

View file

@ -198,6 +198,28 @@ $server->handle(function (Connection $conn) use ($table, $db) {
break; break;
} }
case isset($xml->command->info) && isset($xml->command->info->children('urn:ietf:params:xml:ns:host-1.0')->info):
{
$data = $table->get($connId);
if (!$data || $data['logged_in'] !== 1) {
sendEppError($conn, 2202, 'Authorization error');
$conn->close();
}
processHostInfo($conn, $db, $xml);
break;
}
case isset($xml->command->info) && isset($xml->command->info->children('https://namingo.org/epp/funds-1.0')->info):
{
$data = $table->get($connId);
if (!$data || $data['logged_in'] !== 1) {
sendEppError($conn, 2202, 'Authorization error');
$conn->close();
}
processFundsInfo($conn, $db, $xml, $data['clid']);
break;
}
default: default:
{ {
sendEppError($conn, 2102, 'Unrecognized command'); sendEppError($conn, 2102, 'Unrecognized command');