diff --git a/database/registry,mariadb.sql b/database/registry,mariadb.sql index 622b671..e8e7ec9 100644 --- a/database/registry,mariadb.sql +++ b/database/registry,mariadb.sql @@ -64,6 +64,7 @@ CREATE TABLE IF NOT EXISTS `registry`.`registrar` ( `creditLimit` 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', + `currency` varchar(5) NOT NULL, `crdate` datetime NOT NULL, `update` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), diff --git a/database/registry.postgres.sql b/database/registry.postgres.sql index dd7fa2e..005462f 100644 --- a/database/registry.postgres.sql +++ b/database/registry.postgres.sql @@ -63,6 +63,7 @@ CREATE TABLE registry.registrar ( "creditlimit" 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', + "currency" varchar(5) NOT NULL, "crdate" timestamp without time zone NOT NULL, "update" TIMESTAMP , primary key ("id"), diff --git a/epp/EppWriter.php b/epp/EppWriter.php index bb5c75f..7033c37 100644 --- a/epp/EppWriter.php +++ b/epp/EppWriter.php @@ -28,7 +28,7 @@ class EppWriter { 'create_host' => '_create_host', 'delete_host' => '_common', 'info_host' => '_info_host', - 'info_balance' => '_info_balance', + 'info_funds' => '_info_funds', 'update_host' => '_common', 'poll' => '_poll', 'unknown' => '_common', @@ -861,7 +861,11 @@ class EppWriter { if (isset($resp['status']) && count($resp['status'])) { foreach ($resp['status'] as $s) { 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 { $writer->startElement('host:status'); $writer->writeAttribute('s', $s[0]); @@ -869,20 +873,25 @@ class EppWriter { } } } - foreach ($resp['addr'] as $a) { - $writer->writeElement('host:addr', $a[1], ['ip' => 'v' . $a[0]]); + if (isset($resp['addr']) && !empty($resp['addr'])) { + foreach ($resp['addr'] as $a) { + $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: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'])) { $writer->writeElement('host:upID', $resp['upID']); } 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'])) { - $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 'resData' @@ -891,28 +900,29 @@ class EppWriter { $this->_postamble($writer, $resp); } - private function _info_balance($writer, $resp) { + private function _info_funds($writer, $resp) { $this->_preamble($writer, $resp); if ($this->epp_success($resp['resultCode'])) { $writer->startElement('resData'); - $writer->startElement('balance:infData'); - $writer->writeAttribute('xmlns:balance', 'http://www.verisign.com/epp/balance-1.0'); - $writer->writeAttribute('xsi:schemaLocation', 'http://www.verisign.com/epp/balance-1.0 balance-1.0.xsd'); + $writer->startElement('funds:infData'); + $writer->writeAttribute('xmlns:funds', 'https://namingo.org/epp/funds-1.0'); + $writer->writeAttribute('xsi:schemaLocation', 'https://namingo.org/epp/funds-1.0 funds-1.0.xsd'); - $writer->writeElement('balance:creditLimit', $resp['creditLimit']); - $writer->writeElement('balance:balance', $resp['balance']); - $writer->writeElement('balance:availableCredit', $resp['availableCredit']); + $writer->writeElement('funds:balance', $resp['funds']); + $writer->writeElement('funds:currency', $resp['currency']); + $writer->writeElement('funds:availableCredit', $resp['availableCredit']); + $writer->writeElement('funds:creditLimit', $resp['creditLimit']); - $writer->startElement('balance:creditThreshold'); + $writer->startElement('funds:creditThreshold'); if ($resp['thresholdType'] === 'fixed') { - $writer->writeElement('balance:fixed', $resp['creditThreshold']); + $writer->writeElement('funds:fixed', $resp['creditThreshold']); } 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' } diff --git a/epp/epp-info.php b/epp/epp-info.php index c03f979..caee743 100644 --- a/epp/epp-info.php +++ b/epp/epp-info.php @@ -5,13 +5,14 @@ function processContactInfo($conn, $db, $xml) { $clTRID = (string) $xml->command->clTRID; // 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'); return; } try { - $stmt = $db->prepare("SELECT * FROM contact WHERE id = :id"); + $stmt = $db->prepare("SELECT * FROM contact WHERE identifier = :id"); $stmt->execute(['id' => $contactID]); $contact = $stmt->fetch(PDO::FETCH_ASSOC); @@ -20,15 +21,15 @@ function processContactInfo($conn, $db, $xml) { sendEppError($conn, 2303, 'Object does not exist'); return; } - + // Fetch authInfo $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); // Fetch status $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); $statusArray = []; @@ -38,7 +39,7 @@ function processContactInfo($conn, $db, $xml) { // Fetch postal_info $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); $postalArray = []; @@ -56,26 +57,26 @@ function processContactInfo($conn, $db, $xml) { } $response = [ - 'command' => 'info_contact', - 'clTRID' => $clTRID, - 'svTRID' => generateSvTRID(), - 'resultCode' => 1000, - 'msg' => 'Command completed successfully', - 'id' => $contact['id'], - 'roid' => $contact['identifier'], - 'status' => $statusArray, - 'postal' => $postalArray, - 'voice' => $contact['voice'], - 'fax' => $contact['fax'], - 'email' => $contact['email'], - 'clID' => getRegistrarClid($db, $contact['clid']), - 'crID' => getRegistrarClid($db, $contact['crid']), - 'crDate' => $contact['crdate'], - 'upID' => getRegistrarClid($db, $contact['upid']), - 'upDate' => $contact['update'], - 'authInfo' => 'valid', - 'authInfo_type' => $authInfo['authtype'], - 'authInfo_val' => $authInfo['authinfo'] + 'command' => 'info_contact', + 'clTRID' => $clTRID, + 'svTRID' => generateSvTRID(), + 'resultCode' => 1000, + 'msg' => 'Command completed successfully', + 'id' => $contact['id'], + 'roid' => 'C_'.$contact['identifier'], + 'status' => $statusArray, + 'postal' => $postalArray, + 'voice' => $contact['voice'], + 'fax' => $contact['fax'], + 'email' => $contact['email'], + 'clID' => getRegistrarClid($db, $contact['clid']), + 'crID' => getRegistrarClid($db, $contact['crid']), + 'crDate' => $contact['crdate'], + 'upID' => getRegistrarClid($db, $contact['upid']), + 'upDate' => $contact['update'], + 'authInfo' => 'valid', + 'authInfo_type' => $authInfo['authtype'], + 'authInfo_val' => $authInfo['authinfo'] ]; $epp = new EPP\EppWriter(); @@ -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) { $domainName = $xml->command->info->children('urn:ietf:params:xml:ns:domain-1.0')->info->name; $clTRID = (string) $xml->command->clTRID; // 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)) { sendEppError($conn, 2005, 'Invalid domain name'); return; @@ -107,7 +190,7 @@ function processDomainInfo($conn, $db, $xml) { sendEppError($conn, 2303, 'Object does not exist'); return; } - + // Fetch contacts $stmt = $db->prepare("SELECT * FROM domain_contact_map WHERE domain_id = :id"); $stmt->execute(['id' => $domain['id']]); @@ -117,7 +200,7 @@ function processDomainInfo($conn, $db, $xml) { foreach ($contacts as $contact) { $transformedContacts[] = [$contact['type'], getContactIdentifier($db, $contact['contact_id'])]; } - + // Fetch hosts $stmt = $db->prepare("SELECT * FROM domain_host_map WHERE domain_id = :id"); $stmt->execute(['id' => $domain['id']]); @@ -144,32 +227,73 @@ function processDomainInfo($conn, $db, $xml) { } $response = [ - 'command' => 'info_domain', - 'clTRID' => $clTRID, - 'svTRID' => generateSvTRID(), - 'resultCode' => 1000, - 'msg' => 'Command completed successfully', - 'name' => $domain['name'], - 'roid' => $domain['id'], - 'status' => $statusArray, - 'registrant' => $domain['registrant'], + 'command' => 'info_domain', + 'clTRID' => $clTRID, + 'svTRID' => generateSvTRID(), + 'resultCode' => 1000, + 'msg' => 'Command completed successfully', + 'name' => $domain['name'], + 'roid' => 'D_'.$domain['id'], + 'status' => $statusArray, + 'registrant' => $domain['registrant'], 'contact' => $transformedContacts, - 'hostObj' => $transformedHosts, - 'clID' => getRegistrarClid($db, $domain['clid']), - 'crID' => getRegistrarClid($db, $domain['crid']), - 'crDate' => $domain['crdate'], - 'upID' => getRegistrarClid($db, $domain['upid']), - 'upDate' => $domain['update'], - 'exDate' => $domain['exdate'], - 'trDate' => $domain['trdate'], - 'authInfo' => 'valid', - 'authInfo_type' => $authInfo['authtype'], - 'authInfo_val' => $authInfo['authinfo'] + 'hostObj' => $transformedHosts, + 'clID' => getRegistrarClid($db, $domain['clid']), + 'crID' => getRegistrarClid($db, $domain['crid']), + 'crDate' => $domain['crdate'], + 'upID' => getRegistrarClid($db, $domain['upid']), + 'upDate' => $domain['update'], + 'exDate' => $domain['exdate'], + 'trDate' => $domain['trdate'], + 'authInfo' => 'valid', + 'authInfo_type' => $authInfo['authtype'], + 'authInfo_val' => $authInfo['authinfo'] ]; $epp = new EPP\EppWriter(); $xml = $epp->epp_writer($response); sendEppResponse($conn, $xml); + } catch (PDOException $e) { + 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'); } diff --git a/epp/epp.php b/epp/epp.php index 55d6c1f..1de874b 100644 --- a/epp/epp.php +++ b/epp/epp.php @@ -197,6 +197,28 @@ $server->handle(function (Connection $conn) use ($table, $db) { processHostCheck($conn, $db, $xml); 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: {