From 287eedf318c59e0ebf26c3b43c7690ae2e013597 Mon Sep 17 00:00:00 2001 From: Daniel Winzen Date: Sun, 30 Jun 2019 16:20:06 +0200 Subject: [PATCH] More tor instances + guard relays for stability and added disk quota --- var/www/common.php | 16 ++++++++++------ var/www/cron.php | 9 +++++++++ var/www/html/index.php | 7 +++---- var/www/html/register.php | 2 ++ var/www/setup.php | 30 +++++++++++++++++++----------- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/var/www/common.php b/var/www/common.php index b8426e0..f28c3ee 100644 --- a/var/www/common.php +++ b/var/www/common.php @@ -5,7 +5,7 @@ const DBUSER='hosting'; // Database user const DBPASS='MY_PASSWORD'; // Database password const DBNAME='hosting'; // Database const PERSISTENT=true; // Use persistent database conection true/false -const DBVERSION=14; //database layout version +const DBVERSION=15; //database layout version const CAPTCHA=0; // Captcha difficulty (0=off, 1=simple, 2=moderate, 3=extreme) const ADDRESS='dhosting4xxoydyaivckq7tsmtgi4wfs3flpeyitekkmqwu4v4r46syd.onion'; // our own address const CANONICAL_URL='https://hosting.danwin1210.me'; // our preferred domain for search engines @@ -18,11 +18,12 @@ const INDEX_MD5S=[ //MD5 sums of index.hosting.html files that should be considd 'd41d8cd98f00b204e9800998ecf8427e', //empty file '7ae7e9bac6be76f00e0d95347111f037', //default file '703fac6634bf637f942db8906092d0ab', //new default file +'e109a5a44969c2a109aee0ea3565529e', //TOR HTML Site ]; const REQUIRE_APPROVAL=false; //require admin approval of new sites? true/false const ENABLE_SHELL_ACCESS=true; //allows users to login via ssh, when disabled only (s)ftp is allowed - run setup.php to migrate existing accounts const ADMIN_PASSWORD='MY_PASSWORD'; //password for admin interface -const SERVICE_INSTANCES=['2', '3', '4', '5', '6', '7', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; //one character per instance - run multiple tor+php-fpm instances for load balancing, remove all but one instance if you expect less than 100 accounts. Adding new instances is always possible at a later time, just removing one takes some manual cleanup for now - run setup.php after change +const SERVICE_INSTANCES=['1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; //one character per instance - run multiple tor+php-fpm instances for load balancing, remove all but one instance if you expect less than 100 accounts. Adding new instances is always possible at a later time, just removing one takes some manual cleanup for now - run setup.php after change const DISABLED_PHP_VERSIONS=[]; //php versions still installed on the system but no longer offered for new accounts const PHP_VERSIONS=[4 => '7.3']; //currently active php versions const DEFAULT_PHP_VERSION='7.3'; //default php version @@ -109,6 +110,9 @@ server { const MAX_NUM_USER_DBS = 5; //maximum number of databases a user may have const MAX_NUM_USER_ONIONS = 3; //maximum number of onion domains a user may have const MAX_NUM_USER_DOMAINS = 3; //maximum number of clearnet domains a user may have +const SKIP_USER_CHROOT_UPDATE = true; //skips updating user chroots when running setup.php +const DEFAULT_QUOTA_SIZE = 10 * 1024 * 1024; //per user disk quota in kb - Defaults to 10 GB +const DEFAULT_QUOTA_FILES = 100000; //per user file quota - by default allow no more than 100000 files function get_onion_v2($pkey) : string { $keyData = openssl_pkey_get_details($pkey); @@ -313,9 +317,9 @@ $torrc="ClientUseIPv6 1 ClientUseIPv4 1 SOCKSPort 0 MaxClientCircuitsPending 1024 -NumEntryGuards 6 -NumDirectoryGuards 6 -NumPrimaryGuards 6 +NumEntryGuards 9 +NumDirectoryGuards 9 +NumPrimaryGuards 9 "; $stmt=$db->prepare('SELECT onions.onion, users.system_account, onions.num_intros, onions.enable_smtp, onions.version, onions.max_streams, onions.enabled FROM onions LEFT JOIN users ON (users.id=onions.user_id) WHERE onions.instance = ? AND onions.enabled IN (1, -2) AND users.id NOT IN (SELECT user_id FROM new_account) AND users.todelete!=1;'); $stmt->execute([$key]); @@ -529,7 +533,7 @@ listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = ondemand -pm.max_children = 50 +pm.max_children = 75 pm.process_idle_timeout = 10s; chroot = /home/$tmp[system_account] php_admin_value[memory_limit] = 256M diff --git a/var/www/cron.php b/var/www/cron.php index f9a8744..126ae13 100644 --- a/var/www/cron.php +++ b/var/www/cron.php @@ -161,3 +161,12 @@ while($account=$stmt->fetch(PDO::FETCH_NUM)){ exec('usermod -p '. escapeshellarg($account[1]) . ' ' . escapeshellarg($account[0])); $del->execute([$account[2]]); } + +//update quotas +$stmt=$db->query('SELECT users.system_account, disk_quota.quota_files, disk_quota.quota_size, users.id FROM disk_quota INNER JOIN users ON (users.id=disk_quota.user_id) WHERE disk_quota.updated = 1 AND users.id NOT IN (SELECT user_id FROM new_account) AND users.todelete!=1;'); +$updated=$db->prepare("UPDATE disk_quota SET updated = 0 WHERE user_id=?;"); +while($account=$stmt->fetch(PDO::FETCH_NUM)){ + exec('quotatool -u '. escapeshellarg($account[0]) . ' -i -q ' . escapeshellarg($account[1]) . ' -l ' . escapeshellarg($account[1]) . ' /home'); + exec('quotatool -u '. escapeshellarg($account[0]) . ' -b -q ' . escapeshellarg($account[2]) . ' -l ' . escapeshellarg($account[2]) . ' /home'); + $updated->execute([$account[3]]); +} diff --git a/var/www/html/index.php b/var/www/html/index.php index c47651c..e14b554 100644 --- a/var/www/html/index.php +++ b/var/www/html/index.php @@ -11,11 +11,10 @@ header('Content-Type: text/html; charset=UTF-8');

Hosting - Info

Info | Register | Login | List of hosted sites | FAQ

-

This is a completely fresh installation with many changes done to the internals of how the hosting works. Not everything is working 100% yet, please be patient. To those coming here for the first time since 15th November and are wondering what happened to their account, see here.

Here you can get yourself a hosting account on my server.

What you will get:

diff --git a/var/www/html/register.php b/var/www/html/register.php index 33cdf79..1ba12bb 100644 --- a/var/www/html/register.php +++ b/var/www/html/register.php @@ -110,6 +110,8 @@ if($_SERVER['REQUEST_METHOD']==='POST'){ $stmt=$db->prepare('INSERT INTO users (username, system_account, password, dateadded, public, php, autoindex, mysql_user, instance) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);'); $stmt->execute([$_POST['username'], substr("$onion.onion", 0, 32), $hash, time(), $public_list, $php, $autoindex, $mysql_user, SERVICE_INSTANCES[array_rand(SERVICE_INSTANCES)]]); $user_id = $db->lastInsertId(); + $stmt = $db->prepare('INSERT INTO disk_quota (user_id, quota_size, quota_files) VALUES (?, ?, ?);'); + $stmt->execute([$user_id, DEFAULT_QUOTA_SIZE, DEFAULT_QUOTA_FILES]); add_user_onion($db, $user_id, $onion, $priv_key, $onion_version); add_user_db($db, $user_id); $stmt=$db->prepare('INSERT INTO new_account (user_id, password) VALUES (?, ?);'); diff --git a/var/www/setup.php b/var/www/setup.php index 79a5832..723c633 100644 --- a/var/www/setup.php +++ b/var/www/setup.php @@ -29,10 +29,7 @@ if(!@$version=$db->query("SELECT value FROM settings WHERE setting='version';")) $db->exec('CREATE TABLE mysql_databases (user_id int(11) NOT NULL, mysql_database varchar(64) COLLATE latin1_bin NOT NULL, KEY user_id (user_id), CONSTRAINT mysql_database_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;'); $db->exec("CREATE TABLE onions (user_id int(11) NULL, onion varchar(56) COLLATE latin1_bin NOT NULL PRIMARY KEY, private_key varchar(1000) COLLATE latin1_bin NOT NULL, version tinyint(1) NOT NULL, enabled tinyint(1) NOT NULL DEFAULT '1', num_intros tinyint(3) NOT NULL DEFAULT '3', enable_smtp tinyint(1) NOT NULL DEFAULT '1', max_streams tinyint(3) unsigned NOT NULL DEFAULT '20', instance char(1) NOT NULL DEFAULT '2', KEY user_id (user_id), KEY enabled (enabled), KEY instance(instance), CONSTRAINT onions_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT instance_ibfk_1 FOREIGN KEY (instance) REFERENCES service_instances (id) ON DELETE RESTRICT ON UPDATE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;"); $db->exec("CREATE TABLE domains (user_id int(11) NULL, domain varchar(255) COLLATE latin1_bin NOT NULL PRIMARY KEY, enabled tinyint(1) NOT NULL DEFAULT '1', KEY user_id (user_id), KEY enabled (enabled), CONSTRAINT domains_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;"); - $stmt=$db->prepare('INSERT INTO service_instances (id) VALUES (?);'); - foreach(SERVICE_INSTANCES as $key){ - $stmt->execute([$key]); - } + $db->exec('CREATE TABLE disk_quota (user_id int(11) NOT NULL, quota_size int(10) unsigned NOT NULL, quota_files int(10) unsigned NOT NULL, updated tinyint(1) NOT NULL DEFAULT 1, KEY user_id (user_id), KEY updated (updated), CONSTRAINT disk_quota_ibfk_2 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;'); $db->exec('CREATE TABLE settings (setting varchar(50) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL PRIMARY KEY, value text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;'); $stmt=$db->prepare("INSERT INTO settings (setting, value) VALUES ('version', ?);"); $stmt->execute([DBVERSION]); @@ -148,6 +145,11 @@ if(!@$version=$db->query("SELECT value FROM settings WHERE setting='version';")) $db->exec("ALTER TABLE users ADD instance char(1) NOT NULL DEFAULT '2', ADD KEY instance(instance), ADD CONSTRAINT instance_ibfk_2 FOREIGN KEY (instance) REFERENCES service_instances (id) ON DELETE RESTRICT ON UPDATE RESTRICT;"); $db->exec('UPDATE users SET instance = SUBSTR(system_account, 1, 1);'); } + if($version<15){ + $db->exec('CREATE TABLE disk_quota (user_id int(11) NOT NULL, quota_size int(10) unsigned NOT NULL, quota_files int(10) unsigned NOT NULL, updated tinyint(1) NOT NULL DEFAULT 1, KEY user_id (user_id), KEY updated (updated), CONSTRAINT disk_quota_ibfk_2 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;'); + $stmt = $db->prepare('INSERT INTO disk_quota (user_id, quota_size, quota_files) SELECT id, ?, ? FROM users;'); + $stmt->execute([DEFAULT_QUOTA_SIZE, DEFAULT_QUOTA_FILES]); + } $stmt=$db->prepare("UPDATE settings SET value=? WHERE setting='version';"); $stmt->execute([DBVERSION]); } @@ -251,13 +253,15 @@ php_admin_value[open_basedir] = /usr/share/adminer:/tmp } echo "Updating chroots, this might take a while…\n"; exec('/var/www/setup_chroot.sh /var/www'); -$stmt=$db->query('SELECT system_account FROM users;'); -$shell = ENABLE_SHELL_ACCESS ? '/bin/bash' : '/usr/sbin/nologin'; -while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){ - echo "Updating chroot for user $tmp[system_account]…\n"; - exec('usermod -s ' . escapeshellarg($shell) . ' ' . escapeshellarg($tmp['system_account'])); - exec('/var/www/setup_chroot.sh ' . escapeshellarg('/home/'.$tmp['system_account'])); - exec('grep ' . escapeshellarg($tmp['system_account']) . ' /etc/passwd >> ' . escapeshellarg("/home/$tmp[system_account]/etc/passwd")); +if(!SKIP_USER_CHROOT_UPDATE){ + $stmt=$db->query('SELECT system_account FROM users;'); + $shell = ENABLE_SHELL_ACCESS ? '/bin/bash' : '/usr/sbin/nologin'; + while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){ + echo "Updating chroot for user $tmp[system_account]…\n"; + exec('usermod -s ' . escapeshellarg($shell) . ' ' . escapeshellarg($tmp['system_account'])); + exec('/var/www/setup_chroot.sh ' . escapeshellarg('/home/'.$tmp['system_account'])); + exec('grep ' . escapeshellarg($tmp['system_account']) . ' /etc/passwd >> ' . escapeshellarg("/home/$tmp[system_account]/etc/passwd")); + } } file_put_contents('/etc/nginx/sites-enabled/default', NGINX_DEFAULT); if(!file_exists("/etc/nginx/streams-enabled/")){ @@ -268,5 +272,9 @@ file_put_contents('/etc/nginx/streams-enabled/default', "server { proxy_pass unix:/var/run/mysqld/mysqld.sock; }"); exec("service nginx reload"); +$stmt=$db->prepare('INSERT IGNORE INTO service_instances (id) VALUES (?);'); +foreach(SERVICE_INSTANCES as $key){ + $stmt->execute([$key]); +} $db->exec('UPDATE service_instances SET reload=1;'); echo "Done - Database and files have been updated to the latest version :)\n";