From 80bb08fbd21b6afd1cadf4e622b40791a965440c Mon Sep 17 00:00:00 2001 From: r4sas Date: Sun, 21 Feb 2021 16:37:27 +0000 Subject: [PATCH] reg.i2p files Signed-off-by: r4sas --- .gitignore | 2 + LICENSE | 7 + README.md | 110 ++++++ bin/verifyhost.cpp | 107 ++++++ cache/.gitkeep | 0 checker.php | 70 ++++ composer.json | 38 ++ composer.lock | 718 +++++++++++++++++++++++++++++++++++++ config.php.dist | 33 ++ database.sql | 39 ++ export.php | 118 ++++++ fetch.php | 110 ++++++ import.php | 65 ++++ lib/bob.php | 168 +++++++++ lib/checker.php | 39 ++ lib/db.php | 38 ++ lib/router.php | 28 ++ lib/utils.php | 264 ++++++++++++++ logs/.gitkeep | 0 public/css/style.css | 642 +++++++++++++++++++++++++++++++++ public/export/.gitkeep | 0 public/favicon.ico | Bin 0 -> 13028 bytes public/img/logo.svg | 83 +++++ public/index.php | 43 +++ templates/404.twig | 54 +++ templates/_page.twig | 105 ++++++ templates/_pagination.twig | 60 ++++ templates/add.twig | 74 ++++ templates/alive.twig | 40 +++ templates/all.twig | 41 +++ templates/home.twig | 67 ++++ templates/jump.twig | 57 +++ templates/latest.twig | 33 ++ templates/search.twig | 53 +++ tmp/.gitkeep | 0 views/404.php | 15 + views/add.php | 176 +++++++++ views/alive.php | 41 +++ views/all.php | 33 ++ views/home.php | 35 ++ views/jump.php | 43 +++ views/latest.php | 21 ++ views/search.php | 40 +++ 43 files changed, 3710 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bin/verifyhost.cpp create mode 100644 cache/.gitkeep create mode 100644 checker.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config.php.dist create mode 100644 database.sql create mode 100644 export.php create mode 100644 fetch.php create mode 100644 import.php create mode 100644 lib/bob.php create mode 100644 lib/checker.php create mode 100644 lib/db.php create mode 100644 lib/router.php create mode 100644 lib/utils.php create mode 100644 logs/.gitkeep create mode 100644 public/css/style.css create mode 100644 public/export/.gitkeep create mode 100644 public/favicon.ico create mode 100644 public/img/logo.svg create mode 100644 public/index.php create mode 100644 templates/404.twig create mode 100644 templates/_page.twig create mode 100644 templates/_pagination.twig create mode 100644 templates/add.twig create mode 100644 templates/alive.twig create mode 100644 templates/all.twig create mode 100644 templates/home.twig create mode 100644 templates/jump.twig create mode 100644 templates/latest.twig create mode 100644 templates/search.twig create mode 100644 tmp/.gitkeep create mode 100644 views/404.php create mode 100644 views/add.php create mode 100644 views/alive.php create mode 100644 views/all.php create mode 100644 views/home.php create mode 100644 views/jump.php create mode 100644 views/latest.php create mode 100644 views/search.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c81a72c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/config.php +/hosts.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65bfda1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2021 © The PurpleI2P Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b254da --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +Registry - reg.i2p +=== + +This is reg.i2p domain registry code + + +Requirements +--- + +* i2pd with enabled BOB protocol +* `verifyhost` binary built inside i2pd-tools project folder +* PHP and php-fpm 7.2+ +* Composer +* Corntab (for jobs) + + +Installation +--- + +1. Clone repository in your webserver working directory. Note: that must not be `/var/www/html` or something like this, your webserver must point to `public` directory of forked project. +2. Fetch requirements by runnning `composer update`. +3. Point your webserver to serving webpages from `public` directory. +4. Create cron jobs for quering and updating hosts lists. + + +nginx configuration +--- + +```nginx +server { + listen 127.0.0.1:8000; + + access_log /dev/null; + error_log /dev/null; + + root /home/www/reg.i2p/public; + index index.php index.html; + port_in_redirect off; + + location ~* ^/hosts\.txt$ { + if ($http_if_none_match) { + rewrite ^/hosts\.txt$ /export/hosts.txt break; + } + + rewrite ^/hosts\.txt$ /export/hosts-basic.txt break; + } + + location / { + try_files $uri $uri/ /index.php$request_uri; + } + + location ~ ^/index\.php(/|$) { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SERVER_PORT 80; + fastcgi_pass unix:/home/www/reg.i2p/tmp/php-fpm.sock; + } +} +``` + +php-fpm configuration +--- + +```conf +[reg.i2p] + +prefix = /home/www/$pool +user = www-data +group = www-data +listen = tmp/php-fpm.sock + +listen.owner = www-data +listen.group = www-data +listen.mode = 0660 + +pm = dynamic +pm.max_children = 5 +pm.start_servers = 1 +pm.min_spare_servers = 1 +pm.max_spare_servers = 2 +pm.max_requests = 500 + +chdir = public + +php_admin_flag[log_errors] = On +php_admin_flag[display_errors] = Off +php_admin_value[error_reporting] = E_ALL +php_admin_value[error_log] = /home/www/$pool/logs/php-error.log + +php_admin_value[upload_tmp_dir] = /home/www/$pool/tmp +php_admin_value[date.timezone] = Etc/UTC + +php_admin_value[session.save_path] = /home/www/$pool/sessions +php_admin_value[session.save_handler] = files +php_admin_value[session.gc_probability] = 2 +php_admin_value[session.gc_maxlifetime] = 1440 + +php_admin_value[memory_limit] = 32M +php_admin_value[upload_max_filesize] = "2M" +php_admin_value[max_execution_time] = 60 +``` + +Cron jobs +--- + +```crontab +5 * * * * php7.2 /home/www/reg.i2p/checker.php > /home/www/reg.i2p/logs/checker.log 2>&1 +45 */6 * * * php7.2 /home/www/reg.i2p/export.php > /home/www/reg.i2p/logs/export.log 2>&1 +50 23 * * * php7.2 /home/www/reg.i2p/fetch.php > /home/www/reg.i2p/logs/fetch.log 2>&1 +``` diff --git a/bin/verifyhost.cpp b/bin/verifyhost.cpp new file mode 100644 index 0000000..59779ac --- /dev/null +++ b/bin/verifyhost.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include "Identity.h" +#include "Base.h" + +int main (int argc, char * argv[]) +{ + if (argc < 2) + { + std::cout << "Usage: verifyhost ''" << std::endl; + return -1; + } + + i2p::crypto::InitCrypto (false, true, true, false); + + i2p::data::IdentityEx Identity, OldIdentity; + + std::string str (argv[1]); + std::size_t pos; + + // get record without command block after "#!" + pos = str.find ("#!"); + std::string hostStr = str.substr (0, pos); + + // get host base64 + pos = hostStr.find ("="); + std::string hostBase64 = hostStr.substr (pos + 1); + + // load identity + if (Identity.FromBase64 (hostBase64)) + { + // get record without sig key and signature + pos = str.find ("#sig="); + if (pos == std::string::npos) + { + pos = str.find ("!sig="); + if (pos == std::string::npos) + { + std::cout << "Destination signature not found." << std::endl; + return 1; + } + } + + int offset = (str[pos - 1] == '#' /* only sig in record */) ? 1 : 0; + + std::string hostNoSig = str.substr (0, pos - offset); + std::string sig = str.substr (pos + 5); // after "#sig=" till end + + auto signatureLen = Identity.GetSignatureLen (); + uint8_t * signature = new uint8_t[signatureLen]; + + // validate signature + i2p::data::Base64ToByteStream(sig.c_str (), sig.length(), signature, signatureLen); + if (!Identity.Verify ((uint8_t *)hostNoSig.c_str (), hostNoSig.length (), signature)) + { + std::cout << "Invalid destination signature." << std::endl; + return 1; + } + + if (str.find ("olddest=") != std::string::npos) // if olddest present + { + // get olddest + pos = str.find ("#olddest="); + std::string oldDestCut = str.substr (pos + 9); + pos = oldDestCut.find ("#"); + std::string oldDestBase64 = oldDestCut.substr (0, pos); + + // load identity + if(!OldIdentity.FromBase64 (oldDestBase64)) + { + std::cout << "Invalid old destination base64." << std::endl; + return 1; + } + + signatureLen = OldIdentity.GetSignatureLen (); + signature = new uint8_t[signatureLen]; + + // get record till oldsig key and oldsig + pos = str.find ("#oldsig="); + std::string hostNoOldSig = str.substr (0, pos); + + std::string oldSigCut = str.substr (pos + 8); + pos = oldSigCut.find ("#"); + std::string oldSig = oldSigCut.substr (0, pos); + + // validate signature + i2p::data::Base64ToByteStream(oldSig.c_str (), oldSig.length(), signature, signatureLen); + bool oldSignValid = OldIdentity.Verify ((uint8_t *)hostNoOldSig.c_str (), hostNoOldSig.length (), signature); + + if(!oldSignValid) + { + std::cout << "Invalid old destination signature." << std::endl; + return 1; + } + } + } + else + { + std::cout << "Invalid destination base64." << std::endl; + return 1; + } + + i2p::crypto::TerminateCrypto (); + + return 0; +} diff --git a/cache/.gitkeep b/cache/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/checker.php b/checker.php new file mode 100644 index 0000000..bdeeb9f --- /dev/null +++ b/checker.php @@ -0,0 +1,70 @@ +pdo; + +$results = []; + +$STH = $pdo->query("SELECT `host`, `base32` FROM `hosts`"); +$hosts = $STH->fetchAll(PDO::FETCH_KEY_PAIR); + +/* Start temporary BOB tunnel for checking */ +$bob = new BOB($options); +$bob->setnick(); +$bob->options(); +$bob->newkeys(); +$bob->intun(); +$bob->start(); + +/* Sleep 10 seconds awaitng tunnels get built */ +echo "BOB session started, awaiting 10 seconds for tunnels" . PHP_EOL; +sleep(10); + +/* Start async checker tasks */ +Loop::run(function () use (&$results, $hosts, $options) { + $pool = new DefaultPool(64); + + $coroutines = []; + + foreach ($hosts as $host => $base32) { + $coroutines[$host] = Amp\call(function () use ($pool, $base32, $options) { + $result = yield $pool->enqueue(new App\Checker($base32, $options)); + return $result; + }); + } + + $results = yield Amp\Promise\all($coroutines); + + return yield $pool->shutdown(); +}); + +/* Stop BOB tunnel and terminate */ +$bob->stop(); +$bob->clear(); +$bob = null; + +/* Update last seen time in DB */ +$i = 0; +$pdo->beginTransaction(); + +foreach ($results as $host => $result) +{ + if($result) + { + $pdo->exec("UPDATE `hosts` SET `last_seen` = current_timestamp() WHERE `host` = '" . $host . "'"); + $i++; + } +} + +$pdo->commit(); + +echo "Result: Total hosts: " . count($results) . ", Alive: " . $i . PHP_EOL; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..827789e --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "purplei2p/regi2p", + "description": "I2P domain registrar", + "type": "project", + "require": { + "php": "^7.2.5", + "twig/twig": "^3.0", + "amphp/parallel": "^1.4" + }, + "license": "MIT", + "authors": [ + { + "name": "R4SAS", + "email": "r4sas@i2pmail.org" + } + ], + "autoload": { + "files": [ + "lib/bob.php", + "lib/checker.php", + "lib/db.php", + "lib/router.php", + "lib/utils.php" + ] + }, + "scripts": { + "checker": [ + "Composer\\Config::disableProcessTimeout", + "php checker.php" + ], + "clearCache": "rm -rf cache/*", + "export": "php export.php", + "fetch": [ + "Composer\\Config::disableProcessTimeout", + "php fetch.php" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2cd5734 --- /dev/null +++ b/composer.lock @@ -0,0 +1,718 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4053556a32d0a974795f8ab1ee625ae1", + "packages": [ + { + "name": "amphp/amp", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-01-10T17:06:37+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/master" + }, + "time": "2020-06-29T18:35:05+00:00" + }, + { + "name": "amphp/parallel", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "2c1039bf7ca137eae4d954b14c09a7535d7d4e1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/2c1039bf7ca137eae4d954b14c09a7535d7d4e1c", + "reference": "2c1039bf7ca137eae4d954b14c09a7535d7d4e1c", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "amphp/byte-stream": "^1.6.1", + "amphp/parser": "^1", + "amphp/process": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^1.0.1", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.1", + "phpunit/phpunit": "^8 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parallel\\": "lib" + }, + "files": [ + "lib/Context/functions.php", + "lib/Sync/functions.php", + "lib/Worker/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "support": { + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/master" + }, + "time": "2020-04-27T15:12:37+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/f83e68f03d5b8e8e0365b8792985a7f341c57ae1", + "reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.3", + "phpunit/phpunit": "^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/is-valid" + }, + "time": "2017-06-06T05:29:10+00:00" + }, + { + "name": "amphp/process", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "355b1e561b01c16ab3d78fada1ad47ccc96df70e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/355b1e561b01c16ab3d78fada1ad47ccc96df70e", + "reference": "355b1e561b01c16ab3d78fada1ad47ccc96df70e", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "amphp/byte-stream": "^1.4", + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "phpunit/phpunit": "^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Process\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Asynchronous process manager.", + "homepage": "https://github.com/amphp/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/master" + }, + "time": "2019-02-26T16:33:03+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Serialization\\": "src" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/sync", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "613047ac54c025aa800a9cde5b05c3add7327ed4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/613047ac54c025aa800a9cde5b05c3add7327ed4", + "reference": "613047ac54c025aa800a9cde5b05c3add7327ed4", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.1", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Sync\\": "src" + }, + "files": [ + "src/functions.php", + "src/ConcurrentIterator/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Mutex, Semaphore, and other synchronization tools for Amp.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v1.4.0" + }, + "time": "2020-05-07T18:57:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "twig/twig", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5", + "reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2021-02-08T09:54:36+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.2.5" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/config.php.dist b/config.php.dist new file mode 100644 index 0000000..926fa2a --- /dev/null +++ b/config.php.dist @@ -0,0 +1,33 @@ + '127.0.0.1', + 'db_user' => 'regi2p', + 'db_pass' => 'pass', + 'db_name' => 'user', + + /* I2P settings */ + 'bob_host' => '127.0.0.1', + 'bob_port' => '2827', + 'bob_options' => 'inbound.quantity=5 outbound.quantity=5 inbound.length=1 outbound.length=1 i2cp.leaseSetType=3', + 'bob_nick' => 'hostchecker', + 'http_proxy' => 'tcp://127.0.0.1:4444', // this is HTTP proxy, which must be specified as tcp protocol because we using stream context + + /* Service settings */ + 'approval' => true, // require approval (check host for availability before publishing) + 'fetcher' => true, // enable external subscriptions fetcher + 'tableitems' => 30, // records limit on alive, all, search pages + + /* Records processing options */ + 'approvedelay' => 24, // check host for availability before publishing for this time (hours) + 'approveseen' => 3, // host must be seen lesser than this amount of hours for approving (hours) + + 'newdays' => 7, // assume host as new for that amout of days + 'delnewdays' => 3, // if new host not seen more than X days, disable it and disapprove + 'delolddays' => 30, // same as above, but for old hosts +]; diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..2a2c071 --- /dev/null +++ b/database.sql @@ -0,0 +1,39 @@ +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + + +CREATE TABLE `hosts` ( + `host` varchar(67) NOT NULL, + `base64` varchar(616) NOT NULL, + `base32` varchar(52) NOT NULL, + `description` varchar(128) NOT NULL DEFAULT '', + `add_date` timestamp NOT NULL DEFAULT current_timestamp(), + `last_seen` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `approved` tinyint(1) NOT NULL DEFAULT 0, + `initial` tinyint(1) NOT NULL DEFAULT 0 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `subscriptions` ( + `name` varchar(67) NOT NULL, + `url` varchar(256) NOT NULL, + `etag` varchar(64) NOT NULL DEFAULT '', + `active` tinyint(1) NOT NULL DEFAULT 0 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + +ALTER TABLE `hosts` + ADD PRIMARY KEY (`host`); + +ALTER TABLE `subscriptions` + ADD PRIMARY KEY (`name`); +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/export.php b/export.php new file mode 100644 index 0000000..6e77908 --- /dev/null +++ b/export.php @@ -0,0 +1,118 @@ +pdo; +$util = new App\Utils; + +$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `add_date`, `last_seen`, `approved`, `initial` FROM hosts"); +$hosts = $STH->fetchAll(PDO::FETCH_ASSOC); + +// for automatic approving +$approffs = date ("Y-m-d H:i:s", strtotime ("-" . $options["approvedelay"] . " hour")); // approval offset +$apprseen = date ("Y-m-d H:i:s", strtotime ("-" . $options["approveseen"] . " hour")); // approval maxseen offset + +$newregoffs = date ("Y-m-d H:i:s", strtotime ("-" . $options["newdays"] . " day")); +$newseenlim = date ("Y-m-d H:i:s", strtotime ("-" . $options["delnewdays"] . " day")); +$oldseenlim = date ("Y-m-d H:i:s", strtotime ("-" . $options["delolddays"] . " day")); + +$export_full = $export_live = $export_init = []; +$export_addr_full = $export_addr_live = $export_addr_init = []; + +foreach ($hosts as $host) +{ + /* Convert UFT-8 domains to Punycode */ + $domain = $util->isASCII ($host["host"]) ? $host["host"] : idn_to_ascii ($host["host"]); + + array_push($export_full, $domain . "=" . $host["base64"]); + array_push($export_addr_full, $domain . "," . $host["base32"]); + + if ( + ($options["approval"] == false || $host["approved"] == 1) && ( + ($host["add_date"] > $newregoffs && $host["last_seen"] > $newseenlim) || + ($host["add_date"] < $newregoffs && $host["last_seen"] > $oldseenlim) + ) + ) + { + array_push($export_live, $domain . "=" . $host["base64"]); + array_push($export_addr_live, $domain . "," . $host["base32"]); + + if ($host["initial"] == 1) + { + array_push($export_init, $domain . "=" . $host["base64"]); + array_push($export_addr_init, $domain . "," . $host["base32"]); + } + } + else if ( + $host["approved"] == 0 && ( + ($host["add_date"] < $approffs && $host["last_seen"] > $apprseen) // if host were registered more then X days ago and seen lesser than X hours ago + ) + ) + { + $pdo->exec ("UPDATE `hosts` SET `approved` = 1 WHERE `host` = '" . $host["host"] . "'"); + array_push($export_live, $domain . "=" . $host["base64"]); + array_push($export_addr_live, $domain . "," . $host["base32"]); + } +} + +$STH = null; +$pdo = null; + +/* Sort records */ +sort ($export_full); +sort ($export_addr_full); +// +sort ($export_live); +sort ($export_addr_live); +// +sort ($export_init); +sort ($export_addr_init); + + +/* Export all records */ +$f = fopen (__DIR__ . "/public/export/hosts-all.txt", "w"); +foreach ($export_full as $l) { + $toWrite = print_r ($l, true); + fwrite ($f, $toWrite . "\n"); +} +fclose ($f); + +$f = fopen (__DIR__ . "/public/export/addresses-all.csv", "w"); +foreach ($export_addr_full as $l) { + $toWrite = print_r ($l, true); + fwrite ($f, $toWrite . "\n"); +} +fclose ($f); + +/* Export alive records */ +$f = fopen (__DIR__ . "/public/export/hosts.txt", "w"); +foreach ($export_live as $l) { + $toWrite = print_r ($l, true); + fwrite ($f, $toWrite . "\n"); +} +fclose ($f); + +$f = fopen (__DIR__ . "/public/export/addresses.csv", "w"); +foreach ($export_addr_live as $l) { + $toWrite = print_r ($l, true); + fwrite ($f, $toWrite . "\n"); +} +fclose ($f); + +/* Export initial records */ +$f = fopen (__DIR__ . "/public/export/hosts-basic.txt", "w"); +foreach ($export_init as $l) { + $toWrite = print_r ($l, true); + fwrite ($f, $toWrite . "\n"); +} +fclose ($f); + +$f = fopen (__DIR__ . "/public/export/addresses-basic.csv", "w"); +foreach ($export_addr_init as $l) { + $toWrite = print_r ($l, true); + fwrite ($f, $toWrite . "\n"); +} +fclose ($f); diff --git a/fetch.php b/fetch.php new file mode 100644 index 0000000..19fde21 --- /dev/null +++ b/fetch.php @@ -0,0 +1,110 @@ +pdo; +$util = new App\Utils; + +$error = ""; + +$aContext = array( + 'http' => array( + 'method' => 'GET', + 'proxy' => $options['http_proxy'], + 'user_agent' => 'MYOB/6.66 (AN/ON)', + 'request_fulluri' => true, + 'timeout' => 120.0, + ), +); + + +$STH = $pdo->query ("SELECT `name`, `url`, `etag` FROM `subscriptions` WHERE `active` = 1"); +$lists = $STH->fetchAll(PDO::FETCH_ASSOC); + +foreach ($lists as $list) { + echo "Processing " . $list['name'] . " subscription..." . PHP_EOL; + + if (!empty($list['etag'])) + $aContext['http']['header'] = 'If-None-Match: ' . $list['etag'] . '\r\n'; + + $cxContext = stream_context_create($aContext); + $f = fopen($list['url'], "r", false, $cxContext); + + if ($f) { + $f_meta = stream_get_meta_data($f); + + if (strpos($f_meta['wrapper_data'][0], "200") === false) { + continue; + } + + $etagHeader = array_filter($f_meta['wrapper_data'], function($el) { + return (strpos($el, "ETag") !== false); + }); + + if ($etagHeader) { + $etag = substr($etagHeader[array_keys($etagHeader)[0]], 6); + var_dump($etag); + $pdo->exec("UPDATE `subscriptions` SET `etag` = '" . $etag . "' WHERE `name` = '" . $list['name'] . "'"); + } + + while (($buffer = fgets($f, 4096)) !== false) { + $domain = ""; + $record = $util->parseHostRecord($buffer); + + if (!$util->isValidAddress($record['host'], $error)) { + echo "Error while validating " . $record['host'] . ": " . $error . PHP_EOL; + continue; + + } else { + if ($util->isPunycodeDomain($record['host'])) { + $domain = idn_to_utf8($record['host']); + + } else { + $domain = $record['host']; + } + } + + if (!$util->isValidBase64($record['b64'])) { + continue; + } + + if ((isset($record['commands']) && isset($record['commands']['action']) && $record['commands']['action'] == "adddest" && isset($record['commands']['olddest'])) && + $pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' AND `base64` = '" . $record['commands']['olddest'] . "' LIMIT 1")->fetchColumn()) { + + $base32 = $util->b32from64($record['b64']); + $pdo->exec("UPDATE `hosts` SET `base64` = '" . $record["b64"] . "', `base32` = '" . $base32 . "' WHERE `host` = '" . $domain . "'"); + echo "Processed " . $domain . " (adddest)" . PHP_EOL; + continue; + } + + if ((isset($record['commands']) && isset($record['commands']['action']) && $record['commands']['action'] == "addsubdomain" && isset($record['commands']['oldname']) && isset($record['commands']['olddest'])) && + ($pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $record['commands']['oldname'] . "' AND `base64` = '" . $record['commands']['olddest'] . "' LIMIT 1")->fetchColumn() && + !$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "'")->fetchColumn())) { + + $base32 = $util->b32from64($record['b64']); + $pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`, `approved`) VALUES ('" . $domain . "', '" . $record["b64"] . "', '" . $base32 . "', 1)"); + echo "Processed " . $domain . " (addsubdomain)" . PHP_EOL; + continue; + } + + if(isset($record['commands']) && !$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' LIMIT 1")->fetchColumn()) { + + $base32 = $util->b32from64($record['b64']); + $pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`, `approved`) VALUES ('" . $domain . "', '" . $record["b64"] . "', '" . $base32 . "', 1)"); + echo "Processed " . $domain . PHP_EOL; + continue; + } + } + + if (!feof($f)) { + echo "Error: fgets() ended earlier than needed." . PHP_EOL; + } + + fclose($f); + } else { + echo "Empty response while fetching update from " . $list['name']. "." . PHP_EOL; + } +} + +$pdo = null; + diff --git a/import.php b/import.php new file mode 100644 index 0000000..e485597 --- /dev/null +++ b/import.php @@ -0,0 +1,65 @@ +pdo; +$util = new App\Utils; + +$error = ""; +$f = @fopen("hosts.txt", "r"); + +if ($f) { + $pdo->beginTransaction(); + + $STH = $pdo->prepare("INSERT IGNORE INTO hosts (host, base64, base32) VALUES (:host, :base64, :base32)"); + $STH->bindParam('host', $domain); + $STH->bindParam('base64', $base64); + $STH->bindParam('base32', $base32); + + while (($buffer = fgets($f, 4096)) !== false) + { + $domain = ""; + $record = $util->parseHostRecord($buffer); + + if (!$util->isValidAddress($record['host'], $error)) + { + echo "Error while validating " . $record['host'] . ": " . $error . PHP_EOL; + continue; + } + else + { + if($util->isPunycodeDomain($record['host'])) + { + $domain = idn_to_utf8($record['host']); + } + else + { + $domain = $record['host']; + } + } + + if (!$util->isValidBase64($record['b64'])) + { + echo "Error while validating " . $record['host'] . ": incorrect base64: " . $record['b64'] . PHP_EOL; + continue; + } + + $base64 = $record['b64']; + $base32 = $util->b32from64($record['b64']); + + $STH->execute(); + } + if (!feof($f)) + { + echo "Error: fgets() ended earlier than needed" . PHP_EOL; + } + + if (!$pdo->commit()) + { + echo "Error while saving records to database"; + } + + fclose($f); +} + +$pdo = null; diff --git a/lib/bob.php b/lib/bob.php new file mode 100644 index 0000000..89737c0 --- /dev/null +++ b/lib/bob.php @@ -0,0 +1,168 @@ + "127.0.0.1", + "bob_port" => "2827", + "bob_options" => "inbound.quantity=5 outbound.quantity=5 inbound.length=1 outbound.length=1 i2cp.leaseSetType=3", + "bob_nick" => "hostchecker", + ]; + + public function __construct(array $options = []) + { + ob_implicit_flush(); + + $this->options = array_merge($this->options, (array) $options); + + $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if ($this->sock === false) + { + throw new \ErrorException("socket_create() failed: reason: " . socket_strerror(socket_last_error())); + } + else + { + $result = socket_connect($this->sock, $this->options["bob_host"], $this->options["bob_port"]); + if ($result === false) + { + throw new \ErrorException("socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket))); + } + + /* Reading BOB greeting */ + $response = socket_read($this->sock, 1024); + + if(!preg_match('/OK/', $response)) + { + throw new \ErrorException("BOB returned incorrect response on connect"); + } + } + } + + public function __destruct() + { + $command = "quit\n"; + + socket_write($this->sock, $command, strlen($command)); + socket_read($this->sock, 1024, PHP_NORMAL_READ); + + socket_close($this->sock); + } + + public function setnick() + { + $command = "setnick " . $this->options["bob_nick"] . "\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function options() + { + $options = explode(" ", $this->options["bob_options"]); + + foreach($options as $option) + { + $command = "option " . $option . "\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + } + + public function newkeys() + { + $command = "newkeys\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function start() + { + $command = "start\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function intun() + { + $command = "inhost 127.0.0.1\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + + $command = "inport " . rand(1024, 65535) . "\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function getnick() + { + $command = "getnick " . $this->options["bob_nick"] . "\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function lookup(string $address): bool + { + $command = "lookup " . $address . "\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + + if(preg_match('/^OK/', $response)) + return true; + + return false; + } + + public function stop() + { + $command = "stop\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function clear() + { + $command = "clear\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } + + public function zap() + { + $command = "zap\n"; + socket_write($this->sock, $command, strlen($command)); + + $response = socket_read($this->sock, 1024, PHP_NORMAL_READ); + if(!preg_match('/^OK/', $response)) + echo "BOB response: " . $response; + } +} diff --git a/lib/checker.php b/lib/checker.php new file mode 100644 index 0000000..43009cf --- /dev/null +++ b/lib/checker.php @@ -0,0 +1,39 @@ +options = array_merge($this->options, (array) $options); + $this->base32 = $base32; + } + + /** + * @param Environment $environment + * @return \Amp\Promise|\Generator|mixed + */ + public function run(Environment $environment) + { + $bob = new BOB($this->options); + + $bob->getnick(); + $result = $bob->lookup($this->base32 . ".b32.i2p"); + + $bob = null; + echo "Processed " . $this->base32 . ": " . ($result ? "online" : "offline") . PHP_EOL; + return $result; + } +} + diff --git a/lib/db.php b/lib/db.php new file mode 100644 index 0000000..c222443 --- /dev/null +++ b/lib/db.php @@ -0,0 +1,38 @@ + "127.0.0.1", + "db_user" => "regi2p", + "db_pass" => "password", + "db_name" => "regi2p", + ]; + + public function __construct(array $options = []) + { + $this->options = array_merge($this->options, (array) $options); + + try { + $this->pdo = new PDO("mysql:host=".$this->options["db_host"].";dbname=".$this->options["db_name"], $this->options["db_user"], $this->options["db_pass"]); + $this->pdo->setAttribute(PDO::ATTR_TIMEOUT, 20); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->exec("set names utf8mb4"); + } + + catch(PDOException $e) { + exit("Database connection error: " . $e . PHP_EOL); + } + } + + public function __destruct() + { + $pdo = null; + } +} diff --git a/lib/router.php b/lib/router.php new file mode 100644 index 0000000..b687af6 --- /dev/null +++ b/lib/router.php @@ -0,0 +1,28 @@ +routes['{'.$pattern.'}'] = $function; + } + + public function addErrorRoute($function) { + $this->errRoute = $function; + } + + public function run() { + foreach ($this->routes as $pattern => $function) { + if (preg_match($pattern, $_SERVER['REQUEST_URI'], $params)) { + array_shift($params); + array_unshift($params, $_SERVER['REQUEST_URI']); + return call_user_func_array($function, array_values($params)); + } + } + return call_user_func($this->errRoute); + } +} diff --git a/lib/utils.php b/lib/utils.php new file mode 100644 index 0000000..5c77652 --- /dev/null +++ b/lib/utils.php @@ -0,0 +1,264 @@ + "+", "~" => "/"); + + // base32 alpabet + private const b32alphabet = 'abcdefghijklmnopqrstuvwxyz234567'; + + /** + * base32 encoding function. + * @param string $data + * @return string + */ + private static function b32encode(string $data): string + { + if (empty($data)) { + return ""; + } + + /* Create binary string zeropadded to eight bits. */ + $data = str_split($data); + $binary = implode("", array_map(function ($character) { + return sprintf("%08b", ord($character)); + }, $data)); + + /* Split to five bit chunks and make sure last chunk has five bits. */ + $binary = str_split($binary, 5); + $last = array_pop($binary); + if (null !== $last) { + $binary[] = str_pad($last, 5, "0", STR_PAD_RIGHT); + } + + /* Convert each five bits to Base32 character. */ + $encoded = implode("", array_map(function ($fivebits) { + $index = bindec($fivebits); + return self::b32alphabet[$index]; + }, $binary)); + + return $encoded; + } + + /** + * I2P base64 decoding function. + * @param string $data + * @return string + */ + private static function b64decode(string $data): string + { + return base64_decode(strtr($data, static::b64Trans)); + } + + /** + * Checks if the given domain is in Punycode. + * @param string $domain The domain to check. + * @return bool Whether the domain is in Punycode. + */ + public static function isPunycodeDomain($domain): bool + { + $hasPunycode = false; + + foreach (explode('.', $domain) as $part) { + if (false === static::isAscii($part)) + return false; + + if (static::isPunycode($part)) + $hasPunycode = true; + } + + return $hasPunycode; + } + + /** + * Checks if the given value is in ASCII character encoding. + * @param string $value The value to check. + * @return bool Whether the value is in ASCII character encoding. + */ + public static function isAscii($value): bool + { + return ('ASCII' === mb_detect_encoding($value, 'ASCII', true)); + } + + /** + * Checks if the given value is in Punycode. + * @param string $value The value to check. + * @return bool Whether the value is in Punycode. + * @throws \LogicException If the string is not encoded by UTF-8. + */ + public static function isPunycode($value): bool + { + if (false === static::isAscii($value)) + return false; + + if ('UTF-8' !== mb_detect_encoding($value, 'UTF-8', true)) + throw new \LogicException('The string should be encoded by UTF-8 to do the right check.'); + + return (0 === mb_stripos($value, 'xn--', 0, 'UTF-8')); + } + + /** + * base64 validation function. + * @param string $data + * @return bool + */ + public static function isValidBase64(string $data): bool + { + $len = strlen($data); + + if($len < 516 || $len > 616) + { + return false; + } + + /* .i2p in string */ + if(preg_match('/\.i2p/', $data)) + { + return false; + } + + /* DSA-SHA1. Will reject them because they are old (length 516) */ + if($len == 516 && preg_match('/^[a-zA-Z0-9\-~]+AA$/', $data)) + { + return false; + } + + /* ECDSA or EdDSA */ + if($len == 524 && !preg_match('/^[a-zA-Z0-9\-~]+AEAA[Ec]AAA==$/', $data)) + { + return false; + } + + return true; + } + + /** + * I2P base64 to base32 conversion function. + * @param string $data + * @return string + */ + public static function b32from64(string $data): string + { + return self::b32encode(hash('sha256', self::b64decode($data), true)); + } + + /** + * Domain validation function for registration. + * @param string $data + * @return bool + */ + public static function isValidAddress(string $data, string &$result): bool + { + $len = strlen($data); + + if($len < 5 || $len > 67) + { + $result = "Domain must be longer than 5 and lesser than 67 chars."; + return false; + } + + if(preg_match('/\.b32\.i2p$/', $data)) + { + $result = "Domain can't end with .b32.i2p."; + return false; + } + + if(filter_var($data, FILTER_VALIDATE_DOMAIN) !== false && preg_match('/\.i2p$/', $data) + && (static::isPunycodeDomain($data) || static::isAscii($data))) + { + return true; + } + else + { + $result = "Domain is not valid or is not in Punycode format."; + return false; + } + + return false; + } + + /** + * Domain validation function. + * @param string $data + * @return bool + */ + public static function isValidDomain(string $data, string &$result): bool + { + $len = strlen($data); + + if($len < 5 || $len > 67) + { + $result = "Domain must be longer than 5 and lesser than 67 chars."; + return false; + } + + if(preg_match('/\.b32\.i2p$/', $data)) + { + $result = "Domain can't end with .b32.i2p."; + return false; + } + + if(filter_var($data, FILTER_VALIDATE_DOMAIN) !== false && preg_match('/\.i2p$/', $data)) + { + return true; + } + else + { + $result = "Inputed data or domain is not valid."; + return false; + } + + return false; + } + + public static function verifyHostRecord(string $data, &$output): bool + { + $retval = null; + + $cmd = dirname(__FILE__) . "/../bin/verifyhost '" . $data . "'"; + exec($cmd, $output, $retval); + + if (!$retval) + return true; + + return false; + } + + public static function parseHostRecord(string $data): array + { + $record = []; + + /* Return empty array if no data provided */ + if(!strlen($data)) + return $record; + + $data = trim($data); + + /* Split addressbook host record and extended commands */ + $tmp = preg_split("/#!/", $data); + + /* Parse host record */ + $host = preg_split("/\=/", $tmp[0], 2); + $record["host"] = $host[0]; + $record["b64"] = $host[1]; + + /* Parse extended commands */ + if(isset($tmp[1])) + { + $cmds = []; + + $cmdlist = preg_split("/#/", $tmp[1]); + foreach($cmdlist as $i) + { + list($name,$value) = explode('=', $i, 2); + $cmds[$name] = $value; + } + + $record["commands"] = $cmds; + } + + return $record; + } +} diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..876bc2f --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,642 @@ +body { + margin: 0; + margin-right: auto; + margin-left: auto; + background-color: #e6e6e6 +} + +main { + flex-grow: 1; + display: flex; + flex-flow: column nowrap; + align-items: stretch; + justify-content: flex-start; + padding: 5px 0; +} + +.container_100vh { + flex-grow: 1; + display: flex; + flex-flow: column nowrap; + align-items: stretch; + justify-content: center; +} + +.container_main { + font-size: 16px; +} + +.container_main ul { + margin-bottom: 20px; +} + +.container_main li { + list-style-position: inside; + margin-bottom: 5px; +} + +.container_main b { + font-size: 18px; + margin-bottom: 5px; +} + +.container_main a { + color: #203070; + transition: .3s color linear; +} + +.container_main a:hover { + color: #4477ff; +} +* { + box-sizing: border-box; + font-family: monospace; +} + +button, input, ul, li { + padding: 0; + margin: 0; + border: 0 solid transparent; +} + +svg { + max-width: 100%; +} + +html { + background-color: #e6e6e6; +} + +body { + background-color: #dadada; + max-width: 1240px; + margin: 0 auto; + min-height: 100vh; + display: flex; + flex-flow: column nowrap; + align-items: stretch; + justify-content: flex-start; +} + +.container { + width: 100%; + padding: 0 20px; +} + +.header { + position: sticky; + top: 0; + left: 0; + width: 100%; +} + +.header__top { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + padding: 10px 20px; + background-color: #c1c1c1; +} + +.header__logo { + flex-basis: 220px; +} + +.logo { + font-size: 0; +} + +.logo__link { + display: inline-block; + font-size: 0; + width: 100%; +} + +.search { + display: flex; + flex-flow: row nowrap; + align-items: stretch; + justify-content: space-between; +} + +.text-input { + padding: 0 10px; + border: 1px solid #e6e6e6; + background-color: #e6e6e6; + transition: .3s background-color linear, .3s border linear, .3s box-shadow linear; + outline: none; +} + +.search__text-input { + width: 260px; + margin-right: 5px; +} + +.text-input:hover { + background-color: #fff; +} + +.text-input:focus { + border: 1px solid #0054a6; + background-color: #fff; + box-shadow: 0 0px 6px 1px rgba(0, 84, 166, .6); +} + +.text-input::placeholder { + font-size: 16px; + font-weight: bold; + color: #777 +} + +.search__btn { + height: 100%; + padding: 5px 10px; + cursor: pointer; + background-color: #dadada; + transition: .3s background-color linear, .3s box-shadow linear; +} + +.search__btn img { + width: 20px; +} + +.search__btn svg path { + fill: #333; + transition: .3s fill linear; +} + + +.search__btn:hover { + background-color: #fff; + box-shadow: 0 0px 6px 1px rgba(0, 84, 166, .6) +} + +.search__btn:hover svg path { + fill: #0054a6; +} + +.main-menu__list { + display: flex; + flex-flow: row nowrap; + align-items: stretch; + justify-content: space-between; +} + +.main-menu__item { + list-style: none; + font-size: 0; + flex-grow: 1; + margin-right: 5px; +} + +.main-menu__item:last-child{ + margin-right: 0px; +} + +.main-menu__link { + display: inline-block; + width: 100%; + font-size: 22px; + font-size: 22px; + font-weight: 400; + text-transform: uppercase; + text-align: center; + padding: 4px 0; + cursor: pointer; + background-color: #e6e6e6; + border: 1px solid #e6e6e6; + box-shadow: 0 4px 6px 2px rgba(0, 0, 0, .5); + text-decoration: none; + color: #444; + transition: .3s background-color linear, .3s border linear, .3s color linear, .3s box-shadow linear; +} + +.main-menu__link:hover { + background-color: #fff; + border: 1px solid #0054a6; + color: #0054a6; + box-shadow: 0 0px 12px 2px rgba(0, 84, 166, .8) +} + +.pagination__link:hover { + background-color: #fff; + border: 1px solid #808080; + color: #0054a6; +} + +.header__mobile-logo { + display: none; +} + +.header__mobile-search { + display: none; +} + +.footer { + margin-top: auto; + padding: 25px 0; + text-align: center; + font-size: 18px; + color: #000; + background-color: #a7a7a7; +} + + +.adder, .jumper { + font-size: 18px; + color: #000000; +} + +.title { + font-size: 24px; + text-align: center; + text-transform: uppercase; + font-weight: bold; +} + +.line { + margin-bottom: 10px; +} + +.line:last-child { + margin-bottom: 0; + +} + +.crazy-base64-span, .table__cell_long-ass { + word-break: break-all; +} + +.form__field { + margin-bottom: 20px; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; +} + +.form__field:last-child { + margin-bottom: 0; +} + +.adder__text-input, .jumper__text-input, .search-page__text-input { + flex-grow: 1; + padding: 5px 10px; + margin-left: 15px; +} + +.btn { + height: 100%; + padding: 5px 10px; + cursor: pointer; + background-color: #dadada; + transition: .3s background-color linear, .3s color linear, .3s border linear, .3s box-shadow linear; +} + +.adder__btn, .jumper__btn, .search-page__btn { + display: block; + margin-left: auto; + margin-right: auto; + background-color: #fff; + text-transform: uppercase; + border: 1px solid #0054a6; + color: #0054a6; + font-size: 20px; + font-weight: bold; +} + +.adder__btn:hover, .jumper__btn:hover, .search-page__btn:hover { + background-color: #0054a6; + color: #ffffff; + border: 1px solid #fff; + box-shadow: 0 0px 23px 13px rgba(255, 255, 255, 1) +} + +.pagination__item.active .pagination__link, .pagination__item.active .pagination__link:hover { + background-color: #0054a6; + color: #ffffff; + box-shadow: 0px 0px 0px 0px transparent; +} + +.table { + font-size: 18px; + border-collapse: collapse; + width: 100%; +} + +.table__head { + font-weight: bold; + text-transform: uppercase; +} + +.table__head .table__row { + background-color: transparent !important; + color: #000 !important; +} + +.table__row { + margin-bottom: 10px; + background-color: #dadada; + color: #000; +} + +.table__row:nth-child(odd) { + background-color: #808080; + color: #fff; +} + +.table__row a { + color: #203070; + transition: .3s color linear; +} +.table__row:nth-child(odd) a { + color: #0054a6; +} + +.table__row a:hover { + color: #50a0ff +} + +.table__row:nth-child(odd) a:hover { + color: #ddddff; +} + +.table__cell { + padding: 10px; +} + +.pagination { + margin-top: 40px; + display: flex; + flex-flow: row nowrap; + align-items: stretch; + justify-content: center; +} + +.pagination__item { + list-style: none; + width: 40px; + display: flex; + flex-flow: column nowrap; + align-items: center; + justify-content: center; + font-size: 0; + margin-right: 5px; +} + +.pagination__item:last-child { + margin-right: 0; +} + +.pagination__link { + text-decoration: none; + text-transform: uppercase; + line-height: 1; + width: 100%; + height: 100%; + display: flex; + flex-flow: column nowrap; + align-items: center; + justify-content: center; + padding: 10px 0px; + cursor: pointer; + background-color: #dadada; + transition: .3s background-color linear, .3s color linear, .3s border linear, .3s box-shadow linear; + border: 1px solid transparent; + color: #0054a6; + font-size: 18px; + font-weight: bold; +} + +.pagination__link_arrow { + font-size: 0; + position: relative; +} + +.pagination__link_arrow::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 5px; + height: 5px; +} + +.pagination__link_arrow_prev::after { + transform: translate(-50%, -50%) rotate(-45deg); + border-left: 1px solid #0054a6; + border-top: 1px solid #0054a6; +} +.pagination__link_arrow_next::after { + transform: translate(-50%, -50%) rotate(45deg); + border-right: 1px solid #0054a6; + border-top: 1px solid #0054a6; +} + +.error-msg { + text-align: center; +} + +.error-msg svg { + max-width: 300px; +} + +.hamburger { + display: none; +} + +.header-bottom__main-menu-switch { + display: none; +} + +.section-head { + margin-bottom: 40px; +} + +.disclaimer { + font-size: 16px; + margin-bottom: 15px; +} + +.disclaimer:last-child { + margin-bottom: 30px; +} + +@media screen and (max-width: 920px) { + .container { + padding: 0 8px; + } + + .header__logo { + flex-basis: auto; + margin-bottom: 5px; + } + + .logo svg { + max-width: 120px; + } + + .header__search .search__text-input { + max-width: unset; + flex-grow: 1; + margin: 0; + } + + .header__top { + display: none; + } + + .header__bottom { + /* background-color: rgba(30, 30, 80, .8); */ + padding: 3px 5px; + position: relative; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + } + + .header__bottom:before { + content: ""; + z-index: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(190, 190, 190, .6); + backdrop-filter: blur(1px); + -webkit-backdrop-filter: blur(1px); + } + + .header__mobile-logo { + display: block; + margin-left: 10px; + margin-right: auto; + width: 100px; + position: relative; + z-index: 1; + } + + .header__mobile-logo .st2 { + /* fill: #fff; */ + } + + .header__mobile-search { + display: flex; + margin-left: 10px; + position: relative; + z-index: 1; + } + + .header__mobile-search .search__btn { + padding: 3px 5px; + } + + .header__mobile-search svg { + width: 15px; + } + + .header__mobile-search .search__text-input { + width: 140px; + } + + .hamburger { + display: block; + } + + .hamburger__stripes { + border-top: 2px solid #333; + border-bottom: 2px solid #333; + width: 20px; + height: 14px; + position: relative + } + + .hamburger__stripes:after { + content: ""; + position: absolute; + height: 2px; + width: 100%; + top: 50%; + left: 0; + transform: translateY(-50%); + background-color: #333; + } + + .main-menu { + background-color: #fff; + position: absolute; + left: 0; + top: 100%; + height: calc(100vh - 32px); + background-color: #e6e6e6; + transform: translateX(-100%); + transition: .2s transform linear, .1s box-shadow linear; + box-shadow: none; + } + + .main-menu__list { + flex-flow: column nowrap; + align-items: stretch; + justify-content: flex-start; + } + + .main-menu__item { + margin-right: 0; + padding: 0 8px; + min-width: 30vw; + } + + .main-menu__link { + box-shadow: none; + border-bottom: 1px solid #aaa + } + + .header-bottom__main-menu-switch:checked ~ .main-menu { + transform: translateX(0); + box-shadow: 0px 0px 12px 4px rgba(0, 0, 0, .5) + } + + .title { + font-size: 16px; + } + + .table { + font-size: 14px; + } + + .table__cell { + padding: 6px 4px; + word-break: break-word; + min-width: 48px; + } + + .text-input { + padding: 0 6px; + font-size: 16px; + } + .text-input::placeholder { + font-size: 14px; + } + + .pagination__link { + padding: 3px 0px; + font-size: 14px; + } + + .adder__btn, .jumper__btn, .search-page__btn { + font-size: 16px; + } + + .footer { + font-size: 12px; + } +} + +@media screen and (max-width: 620px) { + .main-menu__item { + min-width: 45vw; + } +} \ No newline at end of file diff --git a/public/export/.gitkeep b/public/export/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..99e5a40cc5d8ab5710c0a092cb3a22b9a47fc420 GIT binary patch literal 13028 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%0Dw?Z7Ol@|%_^pKJ*U;| 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 literal 0 HcmV?d00001 diff --git a/public/img/logo.svg b/public/img/logo.svg new file mode 100644 index 0000000..4f799f6 --- /dev/null +++ b/public/img/logo.svg @@ -0,0 +1,83 @@ + \ No newline at end of file diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..a36398b --- /dev/null +++ b/public/index.php @@ -0,0 +1,43 @@ +addRoute('^/$', function($url) { + require __DIR__ . '/../views/home.php'; +}); + +$r->addRoute('^/add/?$', function($url) { + require __DIR__ . '/../views/add.php'; +}); + +$r->addRoute('^/alive/?([0-9]+)?/?', function($url, $page = 1) { + require __DIR__ . '/../views/alive.php'; +}); + +$r->addRoute('^/all/?([0-9]+)?/?', function($url, $page = 1) { + require __DIR__ . '/../views/all.php'; +}); + +$r->addRoute('^/jump/?(.*)/?', function($url, $query = "") { + require __DIR__ . '/../views/jump.php'; +}); + +$r->addRoute('^/latest/?$', function($url) { + require __DIR__ . '/../views/latest.php'; +}); + +$r->addRoute('^/search/?(.*)/?', function($url, $query = "") { + require __DIR__ . '/../views/search.php'; +}); + +$r->addErrorRoute(function() { + require __DIR__ . '/../views/404.php'; +}); + +/* Process requests */ +$r->run(); diff --git a/templates/404.twig b/templates/404.twig new file mode 100644 index 0000000..7654713 --- /dev/null +++ b/templates/404.twig @@ -0,0 +1,54 @@ +{% extends "_page.twig" %} + +{% block title %}404{% endblock %} +{% block content %} +
+
+ + + + + + + + + + + + + + + + + + +
+
+{% endblock %} diff --git a/templates/_page.twig b/templates/_page.twig new file mode 100644 index 0000000..6bbb57f --- /dev/null +++ b/templates/_page.twig @@ -0,0 +1,105 @@ + + + + {% block head %} + + {% block title %}{% endblock %} - Registry + + + + + {% endblock %} + + + +
+
+ + +
+
+
+ +
+ + + + +
+
+
+ {% block content %}{% endblock %} +
+
+ Powered using i2pd by PurpleI2P team, {{ "now"|date("Y") == '2021' ? "now"|date("Y") : '2021-' ~ "now"|date("Y") }} +
+ + diff --git a/templates/_pagination.twig b/templates/_pagination.twig new file mode 100644 index 0000000..1c39107 --- /dev/null +++ b/templates/_pagination.twig @@ -0,0 +1,60 @@ +{% macro pagination(total, current, url, nearbyPagesLimit = 4) %} + + {# Create "main_url" variable with link for the first page #} + {% set foo = url|split('/') %} + {% set foo = foo|slice(0, -1) %} + {% set main_url = foo|join('/') ~ "/" %} + + {% apply spaceless %} + {% if total > 1 %} +
+ +
+ {% endif %} + {% endapply %} +{% endmacro %} + +{{ _self.pagination(total, current, url) }} diff --git a/templates/add.twig b/templates/add.twig new file mode 100644 index 0000000..e274a3a --- /dev/null +++ b/templates/add.twig @@ -0,0 +1,74 @@ +{% extends "_page.twig" %} + +{% block title %}Add record{% endblock %} +{% block content %} +
+
+ {% if result|length > 0 %} + {% if result.error|length > 0 %} +
+

Error

+ {{ result.error }} +
+ {% else %} +
+
+

Domain successfuly added

+
+ + Domain: + + + {{ result.host }} + +
+
+ + Addresshelper: + + Go to site +
+
+ + Base32: + + + {{ result.base32 }}.b32.i2p + +
+
+ + Base64: + + + {{ result.base64 }} + +
+
+
+ {% endif %} + {% endif %} +
+

+ Domain adding +

+
+
+ + 0 %} value="{{ record }}"{% endif %} required> +
+
+ + 0 %} value="{{ desc }}"{% endif %}> +
+ +
+
+
+
+{% endblock %} + diff --git a/templates/alive.twig b/templates/alive.twig new file mode 100644 index 0000000..3d1a30d --- /dev/null +++ b/templates/alive.twig @@ -0,0 +1,40 @@ +{% extends "_page.twig" %} + +{% block title %}Alive hosts{% endblock %} +{% block content %} +
+
+

+ Alive domains +

+ + + + + + + + + + + {% for host in hosts %} + + + + + + {% endfor %} + +
DomainBase32Last seen
{{ host.host }}{{ host.base32 }}.b32.i2p{{ host.last_seen }}
+ + {% if total > 1 %} + {% include "_pagination.twig" with { + total, + current, + url: "/alive/" + } only %} + {% endif %} +
+
+{% endblock %} + diff --git a/templates/all.twig b/templates/all.twig new file mode 100644 index 0000000..4fc07ed --- /dev/null +++ b/templates/all.twig @@ -0,0 +1,41 @@ +{% extends "_page.twig" %} + +{% block title %}All hosts{% endblock %} +{% block content %} +
+
+ +

+ All domains +

+ + + + + + + + + + + {% for host in hosts %} + + + + + + {% endfor %} + +
DomainBase32Last seen
{{ host.host }}{{ host.base32 }}.b32.i2p{{ host.last_seen != '0000-00-00 00:00:00' ? host.last_seen : 'Never' }}
+ + {% if total > 1 %} + {% include "_pagination.twig" with { + total, + current, + url: "/all/" + } only %} + {% endif %} +
+
+{% endblock %} + diff --git a/templates/home.twig b/templates/home.twig new file mode 100644 index 0000000..094bdd0 --- /dev/null +++ b/templates/home.twig @@ -0,0 +1,67 @@ +{% extends "_page.twig" %} + +{% block title %}Home{% endblock %} +{% block content %} +
+

+ This is reg.i2p registry service.
+

+ +

+ Info:
+ Service works, hosts check done every hour.
+ Supported commands: +

    +
  • adding of hosts for 2LD domains (example.i2p)
  • +
  • adding subdomains (addsubdomain)
  • +
  • adding/changing destination (adddest, changedest) - destination will be replaced with new one, old will be purged
  • +
+

+ +

+ Subscription:
+ For addressbook subscription use /hosts.txt file.
+ That link returns basic addressbook if request sent first time or without If-Match-None header.
+ On next requests it will return full alive hosts list. +

+ +

+ Static links to lists:
+ Lists updated every 4 hours. +

+

+ +

+ Rules:
+ Here no particular rules about registering domains, but we have some limitations about handling dead domain records.
+ Your domain will be open for registration if it has been dead for: +

    +
  • {{ delnewdays }} days if registered less than {{ newdays }} days ago
  • +
  • {{ delolddays }} days if registered more than {{ newdays }} days ago
  • +
+

+ + {% if approval %} +

+ Approval:
+ Domain will be approved after {{ apprdelay }} hours, but only if it has been alive not more than {{ apprseen }} hours ago when approval time is reached. +

+ {% endif %} + + {% if fetcher and subscrs|length > 0 %} +

+ External subscriptions:
+ Our service fetching updates from external subscriptions once a day. List of external services: +

    + {% for subscr in subscrs %} +
  • {{ subscr.name }}
  • + {% endfor %} +
+

+
+ {% endif %} +{% endblock %} diff --git a/templates/jump.twig b/templates/jump.twig new file mode 100644 index 0000000..60e4b60 --- /dev/null +++ b/templates/jump.twig @@ -0,0 +1,57 @@ +{% extends "_page.twig" %} + +{% block title %}Jump to site{% endblock %} +{% block content %} +
+
+ {% if result|length > 0 %} + {% if result.error|length > 0 %} +
+

Error

+ {{ result.error }} +
+ {% else %} +
+

Query result for "{{ result.host }}"

+
+ + Addresshelper: + + Go to site +
+ +
+ + Base32: + + + {{ result.base32 }}.b32.i2p + +
+
+ + Base64: + + + {{ result.base64 }} + +
+
+ {% endif %} + {% endif %} +
+

+ Query domain +

+
+
+ + +
+ +
+
+
+
+{% endblock %} + diff --git a/templates/latest.twig b/templates/latest.twig new file mode 100644 index 0000000..e365dd1 --- /dev/null +++ b/templates/latest.twig @@ -0,0 +1,33 @@ +{% extends "_page.twig" %} + +{% block title %}Latest hosts{% endblock %} +{% block content %} +
+
+ +

+ Latest added domains +

+ + + + + + + + + + + {% for host in hosts %} + + + + + + {% endfor %} + +
DomainBase32Added
{{ host.host }}{{ host.base32 }}.b32.i2p{{ host.add_date }}
+
+
+{% endblock %} + diff --git a/templates/search.twig b/templates/search.twig new file mode 100644 index 0000000..840ae70 --- /dev/null +++ b/templates/search.twig @@ -0,0 +1,53 @@ +{% extends "_page.twig" %} + +{% block title %}Search host{% endblock %} +{% block content %} +
+ {% if result|length > 0 %} +

Query result for "{{ query }}"

+
+ + Note: only first {{ limit }} record(s) are shown + +
+ {% if result.error|length > 0 %} +
+ + {{ result.error }} + +
+ {% else %} + + + + + + + + + + {% for host in result %} + + + + + + {% endfor %} + +
DomainBase32Last seen
{{ host.host }}{{ host.base32 }}.b32.i2p{{ host.last_seen != '0000-00-00 00:00:00' ? host.last_seen : 'Never' }}
+ {% endif %} + {% endif %} + +

+ Search domain +

+
+
+ + +
+ +
+
+{% endblock %} + diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/views/404.php b/views/404.php new file mode 100644 index 0000000..f954b5e --- /dev/null +++ b/views/404.php @@ -0,0 +1,15 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$template = $twig->load('404.twig'); +echo $template->render(); + diff --git a/views/add.php b/views/add.php new file mode 100644 index 0000000..e884255 --- /dev/null +++ b/views/add.php @@ -0,0 +1,176 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$record = ""; +$desc = ""; + +$error = ""; +$result = []; + +if (isset($_POST["record"]) && !empty($_POST["record"])) { + $record = (string) $_POST["record"]; + + if (isset($_POST["desc"]) && !empty($_POST["desc"])) { + $desc = (string) $_POST["desc"]; + } + + $pdo = (new App\DB($options))->pdo; + $util = new App\Utils; + + $parsed = $util->parseHostRecord($record); + + if (!$util->isValidAddress($parsed['host'], $error)) { + $result["error"] = "Error while validating: " . $error; + } else { + if ($util->isPunycodeDomain($parsed['host'])) { + $domain = idn_to_utf8($parsed['host']); + } else { + $domain = $parsed['host']; + } + + /* Check if such domain name already registered */ + $STH = $pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' LIMIT 1"); + + if($STH->fetchColumn() == 1) { + $result["error"] = "Error while validating: That domain is already registered."; + + } else { + if (!isset($parsed["commands"]) || !isset($parsed["commands"]["sig"])) { + $result["error"] = "Error while validating: No extended record fields or signature is found."; + + } else if (!$util->verifyHostRecord($record, $error)) { + $result["error"] = "Error while validating: " . $error[0]; + + } else { + if (isset($parsed["commands"]["action"])) { + switch ($parsed["commands"]["action"]) { + case 'addsubdomain': + if (!isset($parsed["commands"]["oldname"]) || !isset($parsed["commands"]["olddest"]) || !isset($parsed["commands"]["oldsig"])) { + $result["error"] = "Error while validating: required fields not found. Re-check your registration string."; + + } else { + /* Getting domain at higher level (2LD for registering 3LD and etc.) and validating that domain is lower than 2LD. */ + $darr = explode(".", $domain); + $dtop = ""; + + for ($i = 1; $i < sizeof ($darr); $i++) { + $dtop .= $darr[$i]; + if ((sizeof ($darr) - 1) != $i) $dtop .= "."; + } + + if (sizeof($darr) < 3) { + $result["error"] = "Error while validating: you can't register second level domain (example.i2p) using addsubdomain action."; + + } else if ($dtop != $parsed["commands"]["oldname"]) { + $result["error"] = "Error while validating: oldname value is not same as your higher level domain."; + + } else if (!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $parsed["commands"]["oldname"] . "' AND `base64` = '" . $parsed["commands"]["olddest"] . "' LIMIT 1")->fetchColumn()) { + $result["error"] = "Error while validating: can't find higher level domain with values from oldname and olddest."; + + } else { + $base32 = $util->b32from64($parsed["b64"]); + if (!$pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`) VALUES ('" . $domain . "', '" . $parsed["b64"] . "', '" . $base32 . "')")) { + $result["error"] = "Error happened while inserting record to database. Please try again later."; + + } else { + $result["host"] = $domain; + $result["base64"] = $parsed["b64"]; + $result["base32"] = $base32; + } + } + } + break; + case 'adddest': + case 'changedest': + if (!isset($parsed["commands"]["olddest"]) || !isset($parsed["commands"]["oldsig"])) { + $result["error"] = "Error while validating: required fields not found. Re-check your registration string."; + + } else { + if (!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' AND `base64` = '" . $parsed["commands"]["olddest"] . "' LIMIT 1")->fetchColumn()) { + $result["error"] = "Error while validating: old base64 and value in olddest field does not match.."; + + } else { + $base32 = $util->b32from64($parsed["b64"]); + if (!$pdo->exec("UPDATE `hosts` SET `base64` = '" . $parsed["b64"] . "', `base32` = '" . $base32 . "' WHERE `host` = '" . $domain . "'")) { + $result["error"] = "Error happened while updating record in database. Please try again later."; + + } else { + $result["host"] = $domain; + $result["base64"] = $parsed["b64"]; + $result["base32"] = $base32; + } + } + } + break; + case 'addname': + if (!isset($parsed["commands"]["olddest"]) || !isset($parsed["commands"]["oldsig"])) { + $result["error"] = "Error while validating: required fields not found. Re-check your registration string."; + + } else { + if (!$pdo->query("SELECT COUNT(*) FROM `hosts` WHERE `host` = '" . $domain . "' AND `base64` = '" . $parsed["commands"]["olddest"] . "' LIMIT 1")->fetchColumn()) { + $result["error"] = "Error while validating: old base64 and value in olddest field does not match.."; + + } else { + $base32 = $util->b32from64($parsed["b64"]); + if (!$pdo->exec("UPDATE `hosts` SET `base64` = '" . $parsed["b64"] . "', `base32` = '" . $base32 . "' WHERE `host` = '" . $domain . "'")) { + $result["error"] = "Error happened while updating record in database. Please try again later."; + + } else { + $result["host"] = $domain; + $result["base64"] = $parsed["b64"]; + $result["base32"] = $base32; + } + } + } + break; + default: + $result["error"] = "Error while validating: extended record fields are NOT supported for now."; + break; + } + + } else { + if (isset($parsed["commands"]["oldname"]) || isset($parsed["commands"]["olddest"]) || isset($parsed["commands"]["oldsig"])) { + $result["error"] = "Error while validating: unexpected fields found."; + + } else if (sizeof(explode(".", $domain)) > 2) { + $result["error"] = "Error while validating: you can't register subdomain without specific action field."; + + } else { + $base32 = $util->b32from64($parsed["b64"]); + + /* Adding to database 2LD domain */ + if (!$pdo->exec("INSERT INTO `hosts` (`host`, `base64`, `base32`) VALUES ('" . $domain . "', '" . $parsed["b64"] . "', '" . $base32 . "')")) { + $result["error"] = "Error happened while inserting record to database. Please try again later."; + + } else { + $result["host"] = $domain; + $result["base64"] = $parsed["b64"]; + $result["base32"] = $base32; + } + } + } + } + } + } + +} else if (isset($_POST["desc"]) && !empty($_POST["desc"])) { + $result["error"] = "Authentication string is required for registering record."; +} + +if (!empty($result)) { + $record = $desc = ""; // clear them if any result present +} + +$pdo = null; + +$template = $twig->load('add.twig'); +echo $template->render(['record' => $record, 'desc' => $desc, 'result' => $result]); diff --git a/views/alive.php b/views/alive.php new file mode 100644 index 0000000..503ec34 --- /dev/null +++ b/views/alive.php @@ -0,0 +1,41 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$offset = $options["tableitems"] * ($page - 1); +$newregoffs = date ("Y-m-d H:i:s", strtotime ("-7 day")); +$newseenlim = date ("Y-m-d H:i:s", strtotime ("-3 day")); +$oldseenlim = date ("Y-m-d H:i:s", strtotime ("-7 day")); + +$pdo = (new App\DB($options))->pdo; + +/* Get records amount */ +$STH = $pdo->query ("SELECT COUNT(*) as `count` FROM `hosts` " . + "WHERE `approved` = 1 AND (" . + " (`add_date` < '" . $newregoffs . "' AND `last_seen` > '" . $oldseenlim . "') OR" . + " (`add_date` > '" . $newregoffs . "' AND `last_seen` > '" . $newseenlim . "')" . + ")"); +$STH->setFetchMode (PDO::FETCH_ASSOC); +$records = $STH->fetch()["count"]; + +$pages = intdiv($records, $options["tableitems"]) + 1; + +/* Get records with limit */ +$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` " . + "WHERE `approved` = 1 AND (" . + " (`add_date` < '" . $newregoffs . "' AND `last_seen` > '" . $oldseenlim . "') OR" . + " (`add_date` > '" . $newregoffs . "' AND `last_seen` > '" . $newseenlim . "')" . + ") LIMIT " . $offset . ", " . $options["tableitems"]); +$STH->setFetchMode(PDO::FETCH_ASSOC); +$rows = $STH->fetchAll(); + +$template = $twig->load('alive.twig'); +echo $template->render(['current' => $page, 'total' => $pages, 'hosts' => $rows]); diff --git a/views/all.php b/views/all.php new file mode 100644 index 0000000..917b6ae --- /dev/null +++ b/views/all.php @@ -0,0 +1,33 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$offset = $options["tableitems"] * ($page - 1); +$newregoffs = date ("Y-m-d H:i:s", strtotime ("-7 day")); +$newseenlim = date ("Y-m-d H:i:s", strtotime ("-3 day")); +$oldseenlim = date ("Y-m-d H:i:s", strtotime ("-7 day")); + +$pdo = (new App\DB($options))->pdo; + +/* Get records amount */ +$STH = $pdo->query ("SELECT COUNT(*) as `count` FROM `hosts`"); +$STH->setFetchMode (PDO::FETCH_ASSOC); +$records = $STH->fetch()["count"]; + +$pages = intdiv($records, $options["tableitems"]) + 1; + +/* Get records with limit */ +$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` LIMIT " . $offset . ", " . $options["tableitems"]); +$STH->setFetchMode(PDO::FETCH_ASSOC); +$rows = $STH->fetchAll(); + +$template = $twig->load('all.twig'); +echo $template->render(['current' => $page, 'total' => $pages, 'hosts' => $rows]); diff --git a/views/home.php b/views/home.php new file mode 100644 index 0000000..026faae --- /dev/null +++ b/views/home.php @@ -0,0 +1,35 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$subscrs = array(); + +if ($options['fetcher']) { + $pdo = (new App\DB($options))->pdo; + + $STH = $pdo->query ("SELECT `name` FROM `subscriptions` WHERE `active` = 1"); + $STH->setFetchMode(PDO::FETCH_ASSOC); + $subscrs = $STH->fetchAll(); +} + +$vars = array( + 'approval' => $options['approval'], + 'apprdelay' => $options['approvedelay'], + 'apprseen' => $options['approveseen'], + 'newdays' => $options['newdays'], + 'delnewdays' => $options['delnewdays'], + 'delolddays' => $options['delolddays'], + 'fetcher' => $options['fetcher'], + 'subscrs' => $subscrs +); + +$template = $twig->load('home.twig'); +echo $template->render($vars); diff --git a/views/jump.php b/views/jump.php new file mode 100644 index 0000000..a065335 --- /dev/null +++ b/views/jump.php @@ -0,0 +1,43 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$utils = new App\Utils; + +$domain = ""; +$result = []; +$error = ""; +if (isset($query) && strlen($query) > 67 || isset($_POST["q"]) && strlen($_POST["q"]) > 67) + $result["error"] = "Request is too long, max length is 67 chars"; +else if (isset($query) && !empty($query)) + $domain = htmlspecialchars($query); +else if (isset($_POST["q"])) + $domain = htmlspecialchars($_POST["q"]); + +if(!empty($domain) && !$utils->isValidDomain($domain, $error)) { + $domain = ""; + $result["error"] = 'Not valid query: ' . $error; +} +else if(!empty($domain) && $utils->isValidDomain($domain, $error)) { + $pdo = (new App\DB($options))->pdo; + + $STH = $pdo->query("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` WHERE `host` = '" . $domain . "' LIMIT 1"); + $STH->setFetchMode(PDO::FETCH_ASSOC); + $row = $STH->fetchAll(); + + if (empty($row)) + $result["error"] = "No such host is found"; + else + $result = array_merge($result, $row[0]); +} + +$template = $twig->load('jump.twig'); +echo $template->render(['domain' => $domain, 'result' => $result]); diff --git a/views/latest.php b/views/latest.php new file mode 100644 index 0000000..be44544 --- /dev/null +++ b/views/latest.php @@ -0,0 +1,21 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$pdo = (new App\DB($options))->pdo; + +/* Get records with limit */ +$STH = $pdo->query ("SELECT `host`, `base64`, `base32`, `add_date` FROM `hosts` ORDER BY `add_date` DESC LIMIT " . $options["tableitems"]); +$STH->setFetchMode(PDO::FETCH_ASSOC); +$rows = $STH->fetchAll(); + +$template = $twig->load('latest.twig'); +echo $template->render(['limit' => $options["tableitems"], 'hosts' => $rows]); diff --git a/views/search.php b/views/search.php new file mode 100644 index 0000000..90e4c47 --- /dev/null +++ b/views/search.php @@ -0,0 +1,40 @@ + __DIR__ . '/../cache', + 'auto_reload' => true, +]); + +$utils = new App\Utils; + +$result = []; +$q = ""; + +if (isset($query) && strlen($query) > 67 || isset($_POST["q"]) && strlen($_POST["q"]) > 67) { + $result["error"] = "Request is too long, max length is 67 chars"; +} else if (isset($query) && !empty($query)) { + $q = htmlspecialchars($query); +} else if (isset($_POST["q"])) { + $q = htmlspecialchars($_POST["q"]); +} + +if(!empty($q)) { + $pdo = (new App\DB($options))->pdo; + + $STH = $pdo->query("SELECT `host`, `base64`, `base32`, `last_seen` FROM `hosts` WHERE `host` LIKE '%" . $q . "%' OR `base32` LIKE '%" . $q . "%' LIMIT " . $options["tableitems"]); + $STH->setFetchMode(PDO::FETCH_ASSOC); + $row = $STH->fetchAll(); + + if (empty($row)) + $result["error"] = "Nothing was found"; + else + $result = array_merge($result, $row); +} + +$template = $twig->load('search.twig'); +echo $template->render(['query' => $q ?: 'removed request', 'result' => $result, 'limit' => $options["tableitems"]]);