diff --git a/das/composer.json b/das/composer.json index 13d102c..223e2bc 100644 --- a/das/composer.json +++ b/das/composer.json @@ -1,5 +1,6 @@ { "require": { - "monolog/monolog": "^3.5" + "monolog/monolog": "^3.5", + "namingo/rately": "^0.1.0" } } diff --git a/das/config.php.dist b/das/config.php.dist index acc9838..976462f 100644 --- a/das/config.php.dist +++ b/das/config.php.dist @@ -6,5 +6,8 @@ return [ 'db_port' => 3306, 'db_database' => 'registry', 'db_username' => 'your_username', - 'db_password' => 'your_password' + 'db_password' => 'your_password', + 'rately' => false, + 'limit' => 1000, + 'period' => 60, ]; \ No newline at end of file diff --git a/das/helpers.php b/das/helpers.php index d6c567e..d67c25c 100644 --- a/das/helpers.php +++ b/das/helpers.php @@ -39,4 +39,11 @@ function setupLogger($logFilePath, $channelName = 'app') { $log->pushHandler($fileHandler); return $log; +} + +function isIpWhitelisted($ip, $pdo) { + $stmt = $pdo->prepare("SELECT COUNT(*) FROM registrar_whitelist WHERE addr = ?"); + $stmt->execute([$ip]); + $count = $stmt->fetchColumn(); + return $count > 0; } \ No newline at end of file diff --git a/das/start_das.php b/das/start_das.php index 1db8555..477ce38 100644 --- a/das/start_das.php +++ b/das/start_das.php @@ -5,6 +5,7 @@ if (!extension_loaded('swoole')) { } use Swoole\Server; +use Namingo\Rately\Rately; $c = require_once 'config.php'; require_once 'helpers.php'; @@ -43,6 +44,8 @@ $server->set([ 'open_eof_check' => true, 'package_eof' => "\r\n" ]); + +$rateLimiter = new Rately(); $log->info('server started.'); // Register a callback to handle incoming connections @@ -51,21 +54,35 @@ $server->on('connect', function ($server, $fd) use ($log) { }); // Register a callback to handle incoming requests -$server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool, $log) { +$server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool, $log, $rateLimiter) { // Get a PDO connection from the pool $pdo = $pool->get(); $domain = trim($data); + $clientInfo = $server->getClientInfo($fd); + $remoteAddr = $clientInfo['remote_ip']; + + if (!isIpWhitelisted($remoteAddr, $pdo)) { + if (($c['rately'] == true) && ($rateLimiter->isRateLimited('das', $remoteAddr, $c['limit'], $c['period']))) { + $log->error('rate limit exceeded for ' . $remoteAddr); + $server->send($fd, "rate limit exceeded. Please try again later"); + $server->close($fd); + return; + } + } + // Perform the DAS lookup try { // Validate and sanitize the domain name if (!$domain) { $server->send($fd, "2"); $server->close($fd); + return; } if (strlen($domain) > 68) { $server->send($fd, "2"); $server->close($fd); + return; } // Convert to Punycode if the domain is not in ASCII if (!mb_detect_encoding($domain, 'ASCII', true)) { @@ -73,6 +90,7 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool if ($convertedDomain === false) { $server->send($fd, "2"); $server->close($fd); + return; } else { $domain = $convertedDomain; } @@ -80,6 +98,7 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool if (!preg_match('/^(?:(xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){1,3}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $domain)) { $server->send($fd, "2"); $server->close($fd); + return; } $domain = strtoupper($domain); @@ -171,7 +190,7 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool // Register a callback to handle client disconnections $server->on('close', function ($server, $fd) use ($log) { - $log->info('client ' . $fd . ' connected.'); + $log->info('client ' . $fd . ' disconnected.'); }); // Start the server diff --git a/epp/composer.json b/epp/composer.json index 0155b26..68f6ec9 100644 --- a/epp/composer.json +++ b/epp/composer.json @@ -4,6 +4,7 @@ "jeremykendall/php-domain-parser": "^6.3", "matthiasmullie/scrapbook": "^1.5", "guzzlehttp/guzzle": "^7.8", - "league/flysystem": "^3.23" + "league/flysystem": "^3.23", + "namingo/rately": "^0.1.0" } } diff --git a/epp/config.php.dist b/epp/config.php.dist index 471ef3f..eaea2b6 100644 --- a/epp/config.php.dist +++ b/epp/config.php.dist @@ -15,4 +15,7 @@ return [ 'ssl_cert' => '', 'ssl_key' => '', 'test_tlds' => '.test,.com.test', + 'rately' => false, + 'limit' => 1000, + 'period' => 60, ]; \ No newline at end of file diff --git a/epp/start_epp.php b/epp/start_epp.php index a79fcf5..7455eff 100644 --- a/epp/start_epp.php +++ b/epp/start_epp.php @@ -20,6 +20,7 @@ use Swoole\Table; use Swoole\Timer; use Swoole\Coroutine\Server; use Swoole\Coroutine\Server\Connection; +use Namingo\Rately\Rately; $table = new Table(1024); $table->column('clid', Table::TYPE_STRING, 64); @@ -64,9 +65,11 @@ $server->set([ 'ssl_protocols' => SWOOLE_SSL_TLSv1_2 | SWOOLE_SSL_TLSv1_3, 'ssl_ciphers' => 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384', ]); + +$rateLimiter = new Rately(); $log->info('Namingo EPP server started'); -$server->handle(function (Connection $conn) use ($table, $pool, $c, $log, $permittedIPsTable) { +$server->handle(function (Connection $conn) use ($table, $pool, $c, $log, $permittedIPsTable, $rateLimiter) { // Get the client information $clientInfo = $conn->exportSocket()->getpeername(); $clientIP = $clientInfo['address'] ?? ''; @@ -77,7 +80,13 @@ $server->handle(function (Connection $conn) use ($table, $pool, $c, $log, $permi $conn->close(); return; } - + + if (($c['rately'] == true) && ($rateLimiter->isRateLimited('epp', $clientIP, $c['limit'], $c['period']))) { + $log->error('rate limit exceeded for ' . $clientIP); + $conn->close(); + return; + } + $log->info('new client from ' . $clientIP . ' connected'); sendGreeting($conn); diff --git a/rdap/composer.json b/rdap/composer.json index 13d102c..223e2bc 100644 --- a/rdap/composer.json +++ b/rdap/composer.json @@ -1,5 +1,6 @@ { "require": { - "monolog/monolog": "^3.5" + "monolog/monolog": "^3.5", + "namingo/rately": "^0.1.0" } } diff --git a/rdap/config.php.dist b/rdap/config.php.dist index 494b417..8c1e2c7 100644 --- a/rdap/config.php.dist +++ b/rdap/config.php.dist @@ -10,4 +10,7 @@ return [ 'roid' => 'XX', 'registry_url' => 'https://example.com/rdap-terms', 'rdap_url' => 'https://rdap.example.com', + 'rately' => false, + 'limit' => 1000, + 'period' => 60, ]; \ No newline at end of file diff --git a/rdap/helpers.php b/rdap/helpers.php index a443d62..f0fc6cb 100644 --- a/rdap/helpers.php +++ b/rdap/helpers.php @@ -91,4 +91,11 @@ function mapContactToVCard($contactDetails, $role, $c) { ] ], ]; +} + +function isIpWhitelisted($ip, $pdo) { + $stmt = $pdo->prepare("SELECT COUNT(*) FROM registrar_whitelist WHERE addr = ?"); + $stmt->execute([$ip]); + $count = $stmt->fetchColumn(); + return $count > 0; } \ No newline at end of file diff --git a/rdap/start_rdap.php b/rdap/start_rdap.php index 2c54ac2..c3f4f4e 100644 --- a/rdap/start_rdap.php +++ b/rdap/start_rdap.php @@ -7,6 +7,7 @@ if (!extension_loaded('swoole')) { use Swoole\Http\Server; use Swoole\Http\Request; use Swoole\Http\Response; +use Namingo\Rately\Rately; $c = require_once 'config.php'; require_once 'helpers.php'; @@ -44,13 +45,25 @@ $http->set([ 'reload_async' => true, 'http_compression' => true ]); + +$rateLimiter = new Rately(); $log->info('server started.'); // Handle incoming HTTP requests -$http->on('request', function ($request, $response) use ($c, $pool, $log) { +$http->on('request', function ($request, $response) use ($c, $pool, $log, $rateLimiter) { // Get a PDO connection from the pool $pdo = $pool->get(); - + + $remoteAddr = $request->server['remote_addr']; + if (!isIpWhitelisted($remoteAddr, $pdo)) { + if (($c['rately'] == true) && ($rateLimiter->isRateLimited('rdap', $remoteAddr, $c['limit'], $c['period']))) { + $log->error('rate limit exceeded for ' . $remoteAddr); + $response->header('Content-Type', 'application/json'); + $response->status(429); + $response->end(json_encode(['error' => 'Rate limit exceeded. Please try again later.'])); + } + } + try { // Extract the request path $requestPath = $request->server['request_uri']; diff --git a/whois/port43/composer.json b/whois/port43/composer.json index 13d102c..223e2bc 100644 --- a/whois/port43/composer.json +++ b/whois/port43/composer.json @@ -1,5 +1,6 @@ { "require": { - "monolog/monolog": "^3.5" + "monolog/monolog": "^3.5", + "namingo/rately": "^0.1.0" } } diff --git a/whois/port43/config.php.dist b/whois/port43/config.php.dist index c5f9edc..1573b79 100644 --- a/whois/port43/config.php.dist +++ b/whois/port43/config.php.dist @@ -9,4 +9,7 @@ return [ 'db_password' => 'your_password', 'privacy' => false, 'roid' => 'XX', + 'rately' => false, + 'limit' => 25, + 'period' => 60, ]; \ No newline at end of file diff --git a/whois/port43/helpers.php b/whois/port43/helpers.php index 7966cb4..5ec119b 100644 --- a/whois/port43/helpers.php +++ b/whois/port43/helpers.php @@ -51,4 +51,11 @@ function parseQuery($data) { } else { return ['type' => 'domain', 'data' => $data]; } +} + +function isIpWhitelisted($ip, $pdo) { + $stmt = $pdo->prepare("SELECT COUNT(*) FROM registrar_whitelist WHERE addr = ?"); + $stmt->execute([$ip]); + $count = $stmt->fetchColumn(); + return $count > 0; } \ No newline at end of file diff --git a/whois/port43/start_whois.php b/whois/port43/start_whois.php index 66d424f..7e7e635 100644 --- a/whois/port43/start_whois.php +++ b/whois/port43/start_whois.php @@ -5,6 +5,7 @@ if (!extension_loaded('swoole')) { } use Swoole\Server; +use Namingo\Rately\Rately; $c = require_once 'config.php'; require_once 'helpers.php'; @@ -43,6 +44,8 @@ $server->set([ 'open_eof_check' => true, 'package_eof' => "\r\n" ]); + +$rateLimiter = new Rately(); $log->info('server started.'); // Register a callback to handle incoming connections @@ -51,13 +54,25 @@ $server->on('connect', function ($server, $fd) use ($log) { }); // Register a callback to handle incoming requests -$server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool, $log) { +$server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool, $log, $rateLimiter) { // Get a PDO connection from the pool $pdo = $pool->get(); $privacy = $c['privacy']; $parsedQuery = parseQuery($data); $queryType = $parsedQuery['type']; $queryData = $parsedQuery['data']; + + $clientInfo = $server->getClientInfo($fd); + $remoteAddr = $clientInfo['remote_ip']; + + if (!isIpWhitelisted($remoteAddr, $pdo)) { + if (($c['rately'] == true) && ($rateLimiter->isRateLimited('whois', $remoteAddr, $c['limit'], $c['period']))) { + $log->error('rate limit exceeded for ' . $remoteAddr); + $server->send($fd, "rate limit exceeded. Please try again later"); + $server->close($fd); + return; + } + } // Handle the WHOIS query try { @@ -69,10 +84,12 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool if (!$domain) { $server->send($fd, "please enter a domain name"); $server->close($fd); + return; } if (strlen($domain) > 68) { $server->send($fd, "domain name is too long"); $server->close($fd); + return; } // Convert to Punycode if the domain is not in ASCII if (!mb_detect_encoding($domain, 'ASCII', true)) { @@ -80,6 +97,7 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool if ($convertedDomain === false) { $server->send($fd, "Domain conversion to Punycode failed"); $server->close($fd); + return; } else { $domain = $convertedDomain; } @@ -87,6 +105,7 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool if (!preg_match('/^(?:(xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){1,3}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $domain)) { $server->send($fd, "domain name invalid format"); $server->close($fd); + return; } $domain = strtoupper($domain); @@ -670,7 +689,7 @@ $server->on('receive', function ($server, $fd, $reactorId, $data) use ($c, $pool // Register a callback to handle client disconnections $server->on('close', function ($server, $fd) use ($log) { - $log->info('client ' . $fd . ' connected.'); + $log->info('client ' . $fd . ' disconnected.'); }); // Start the server