diff --git a/var/www/common.php b/var/www/common.php index 72bc793..bb1d5c3 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=16; //database layout version +const DBVERSION=17; //database layout version const CAPTCHA=1; // 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 @@ -24,7 +24,7 @@ const INDEX_MD5S=[ //MD5 sums of index.hosting.html files that should be considd 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=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's']; //one character per instance - run multiple tor+php-fpm instances for load balancing, remove all but one instance if you expect less than 200 accounts. - run setup.php after change +const SERVICE_INSTANCES=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's']; //one character per instance - run multiple tor+php-fpm instances for load balancing, remove all but one instance if you expect less than 200 accounts. If tor starts using 100% cpu and failing circuits every few hours after a restart, add more instances. In my experience this happens around 250 hidden services per instance - 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=[3 => '7.2', 4 => '7.3', 5 => '7.4']; //currently active php versions const DEFAULT_PHP_VERSION='7.3'; //default php version @@ -116,9 +116,20 @@ 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_SIZE = 1024 * 1024; //per user disk quota in kb - Defaults to 1 GB const DEFAULT_QUOTA_FILES = 100000; //per user file quota - by default allow no more than 100000 files const NUM_GUARDS = 50; //number of tor guard relays to distribute the load on +//Optional paid upgrades in format of 'identifier' => ['name', 'usd_price'] +const ACCOUNT_UPGRADES = [ + '1g_quota' => ['name' => '+1GB disk Quota', 'usd_price' => 10], + '5g_quota' => ['name' => '+5GB disk Quota', 'usd_price' => 20], + '10g_quota' => ['name' => '+10GB disk Quota', 'usd_price' => 30], + '20g_quota' => ['name' => '+20GB disk Quota', 'usd_price' => 40], +]; +const COINPAYMENTS_PRIVATE = 'COINPAYMENTS_PRIVATE_API_KEY'; +const COINPAYMENTS_PUBLIC = 'COINPAYMENTS_PUBLIC_API_KEY'; +const COINPAYMENTS_MERCHANT_ID = 'COINPAYMENTS_MERCHANT_ID'; +const COINPAYMENTS_IPN_SECRET = 'COINPAYMENTS_IPN_SECRET'; function get_onion_v2($pkey) : string { $keyData = openssl_pkey_get_details($pkey); @@ -751,3 +762,103 @@ function get_db_instance(){ } return $db; } + +function coinpayments_create_transaction(string $currency, int $price, string $payment_for, $user_id = null){ + $query=[]; + $query['currency1'] = 'USD'; + $query['currency2'] = $currency; + $query['amount'] = $price; + $query['buyer_email'] = 'daniel@danwin1210.me'; + $query['version'] = '1'; + $query['cmd'] = 'create_transaction'; + $query['key'] = COINPAYMENTS_PUBLIC; + $query['format'] = 'json'; + $query_string = http_build_query( $query ); + $hmac = hash_hmac( 'sha512', $query_string, COINPAYMENTS_PRIVATE ); + + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $query_string ); + curl_setopt( $ch, CURLOPT_HTTPHEADER, ["HMAC: $hmac", 'Content-type: application/x-www-form-urlencoded'] ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $ch, CURLOPT_URL, 'https://www.coinpayments.net/api.php' ); + $result = curl_exec( $ch ); + if( !$result ) { + return false; + } + $json = json_decode( $result, true ); + if( !$json ){ + return false; + } + if( $json['error'] !== 'ok' ) { + return false; + } + $db = get_db_instance(); + $stmt = $db->prepare('INSERT INTO payments (user_id, payment_for, txn_id, status) VALUES (?, ?, ?, 0);'); + $stmt->execute([$user_id, $payment_for, $json['result']['txn_id']]); + return $json['result']; +} + +function coinpayments_get_rates(){ + $query=[]; + $query['accepted'] = '1'; + $query['short'] = '0'; + $query['version'] = '1'; + $query['cmd'] = 'rates'; + $query['key'] = COINPAYMENTS_PUBLIC; + $query['format'] = 'json'; + $query_string = http_build_query( $query ); + $hmac = hash_hmac( 'sha512', $query_string, COINPAYMENTS_PRIVATE ); + + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $query_string ); + curl_setopt( $ch, CURLOPT_HTTPHEADER, ["HMAC: $hmac", 'Content-type: application/x-www-form-urlencoded'] ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $ch, CURLOPT_URL, 'https://www.coinpayments.net/api.php' ); + $result = curl_exec( $ch ); + if( !$result ) { + return false; + } + $json = json_decode( $result, true ); + if( !$json ){ + return false; + } + if( $json['error'] !== 'ok' ) { + return false; + } + return $json['result']; +} + +function payment_status_update(string $txid){ + $db = get_db_instance(); + $stmt = $db->prepare('SELECT * FROM payments WHERE txn_id = ?;'); + $stmt->execute([$txid]); + while($tmp = $stmt->fetch(PDO::FETCH_ASSOC)){ + if($tmp['status'] == '2'){ + switch($tmp['payment_for']){ + case '1g_quota': + add_disk_quota($tmp['user_id'], 1024 * 1024); + break; + case '5g_quota': + add_disk_quota($tmp['user_id'], 5 * 1024 * 1024); + break; + case '10g_quota': + add_disk_quota($tmp['user_id'], 10 * 1024 * 1024); + break; + case '20g_quota': + add_disk_quota($tmp['user_id'], 20 * 1024 * 1024); + break; + default: + break; + } + } + } +} + +function add_disk_quota(int $user_id, int $kb){ + $db = get_db_instance(); + $stmt = $db->prepare('SELECT quota_size FROM disk_quota WHERE user_id = ?;'); + $stmt->execute([$user_id]); + $tmp = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $db->prepare('UPDATE disk_quota SET quota_size = ?, updated = 1 WHERE user_id = ?;'); + $stmt->execute([$tmp['quota_size'] + $kb, $user_id]); +} diff --git a/var/www/composer.json b/var/www/composer.json index f2de8bb..f0d68dd 100644 --- a/var/www/composer.json +++ b/var/www/composer.json @@ -1,5 +1,6 @@ { "require": { - "paragonie/sodium_compat": "^1.11" + "paragonie/sodium_compat": "^1.11", + "chillerlan/php-qrcode": "^3.1" } } diff --git a/var/www/find_old.php b/var/www/find_old.php index 4b0ab35..e2fa4f7 100644 --- a/var/www/find_old.php +++ b/var/www/find_old.php @@ -2,6 +2,19 @@ include('common.php'); $db = get_db_instance(); +//update quota usage +$stmt=$db->query('SELECT id, system_account FROM users WHERE id NOT IN (SELECT user_id FROM new_account) AND todelete!=1;'); +$update=$db->prepare('UPDATE disk_quota SET quota_size_used = ?, quota_files_used = ? WHERE user_id = ?;'); +while($tmp=$stmt->fetch(PDO::FETCH_NUM)){ + $quota = shell_exec('quota -pu ' . escapeshellarg($tmp[1])); + $quota_array = explode("\n", $quota); + if(!empty($quota_array[2])){ + $quota_size=(int) preg_replace('~^\s+[^\s]+\s+([^\s]+).*~', '$1', $quota_array[2]); + $quota_files=(int) preg_replace('~^\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+([^\s]+).*~', '$1', $quota_array[2]); + $update->execute([$quota_size, $quota_files, $tmp[0]]); + } +} + //delete tmp files older than 24 hours $stmt=$db->query('SELECT system_account FROM users;'); $all=$stmt->fetchAll(PDO::FETCH_NUM); diff --git a/var/www/html/coinpayments_ipn.php b/var/www/html/coinpayments_ipn.php new file mode 100644 index 0000000..c589c88 --- /dev/null +++ b/var/www/html/coinpayments_ipn.php @@ -0,0 +1,44 @@ + 0 && $_POST['status'] < 100){ + $status = 1; +}elseif($_POST['status'] >= 100){ + $status = 2; +} +$stmt = $db->prepare('SELECT status FROM payments WHERE txn_id = ?;'); +$stmt->execute([$_POST['txn_id']]); +if($tmp = $stmt->fetch(PDO::FETCH_ASSOC)){ + if($status != $tmp['status']){ + $stmt = $db->prepare('UPDATE payments SET status = ? WHERE txn_id = ?;'); + $stmt->execute([$status, $_POST['txn_id']]); + payment_status_update($_POST['txn_id']); + } +} diff --git a/var/www/html/home.php b/var/www/html/home.php index f90d85f..f674d5a 100644 --- a/var/www/html/home.php +++ b/var/www/html/home.php @@ -3,6 +3,7 @@ include('../common.php'); $db = get_db_instance(); session_start(); $user=check_login(); +header('Content-Type: text/html; charset=UTF-8'); if(isset($_POST['action']) && $_POST['action']==='add_db'){ if($error=check_csrf_error()){ die($error); @@ -186,17 +187,18 @@ if(isset($_REQUEST['action']) && isset($_POST['domain']) && $_POST['action']===' enqueue_instance_reload(); } } - -header('Content-Type: text/html; charset=UTF-8'); -echo ''; -echo 'Daniel\'s Hosting - Dashboard'; -echo ''; -echo ''; -echo ''; -echo ''; -echo ''; -echo ''; -echo "

Logged in as $user[username] Logout | Change passwords | FileManager | Delete account

"; +?> + +Daniel's Hosting - Dashboard + + + + + + + +

Logged in as Logout | Change passwords | FileManager | Delete account

+'; if($count_dbs

'; } -echo '

Change MySQL password

'; -echo '

You can use PHPMyAdmin and Adminer for web based database administration.

'; -echo '

System Account

'; -echo ''; -echo ''; +?> +

Change MySQL password

+

You can use PHPMyAdmin and Adminer for web based database administration.

+

System Account

+
UsernameHostFTP PortSFTP PortPOP3 PortIMAP PortSMTP port
+ +$tmp){ echo ""; } -echo '
UsernameHostFTP PortSFTP PortPOP3 PortIMAP PortSMTP port
$user[system_account]$server$tmp[ftp]$tmp[sftp]$tmp[pop3]$tmp[imap]$tmp[smtp]
'; -echo '

Change system account password

'; -echo '

You can use the FileManager for web based file management.

'; -echo '

Logs

'; -echo ''; -echo ''; -echo ''; -echo ''; -echo '
Dateaccess.logerror.log
Todayaccess.logerror.log
Yesterdayaccess.logerror.log
'; -echo ''; +?> + +

Change system account password

+

You can use the FileManager for web based file management.

+prepare('SELECT quota_size, quota_size_used FROM disk_quota WHERE user_id = ?;'); +$stmt->execute([$user['id']]); +$quota = $stmt->fetch(PDO::FETCH_ASSOC); +$quota_usage = $quota['quota_size_used'] / $quota['quota_size']; +?> +

Your disk usage: % - % (updated hourly) Upgrade

+

Logs

+ + + + +
Dateaccess.logerror.log
Todayaccess.logerror.log
Yesterdayaccess.logerror.log
+ diff --git a/var/www/html/index.php b/var/www/html/index.php index ce3110f..58dbf26 100644 --- a/var/www/html/index.php +++ b/var/www/html/index.php @@ -22,9 +22,9 @@ header('X-Accel-Expires: 60');
  • MariaDB (MySQL) database support
  • PHPMyAdmin and Adminer for web based database administration
  • Web-based file manager
  • -
  • FTP access
  • -
  • SFTP access
  • -
  • 10GB disk quota and a maximum of 100.000 files. If you need more, just contact me
  • +
  • FTP and SFTP access
  • +
  • command line access to shell via SSH
  • +
  • 1GB disk quota and a maximum of 100.000 files. - upgradable
  • mail() can send e-mails from your.onion@ (your.onion@hosting.danwin1210.me for clearnet) - not yet working but will return in future, use https://github.com/PHPMailer/PHPMailer or similar for now
  • Webmail and IMAP, POP3 and SMTP access to your mail account
  • Mail sent to anything@your.onion gets automatically redirected to your inbox
  • diff --git a/var/www/html/login.php b/var/www/html/login.php index b3ac3c2..241f9f4 100644 --- a/var/www/html/login.php +++ b/var/www/html/login.php @@ -73,7 +73,8 @@ if(isset($_POST['username'])){ echo '" required autofocus>'; echo 'Password'; send_captcha(); -echo ''; -echo ''; -echo '

    If you disabled cookies, please re-enable them. You can\'t log in without!

    '; -echo ''; +?> + + +

    If you disabled cookies, please re-enable them. You can't log in without!

    + diff --git a/var/www/html/robots.txt b/var/www/html/robots.txt new file mode 100644 index 0000000..297e070 --- /dev/null +++ b/var/www/html/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / + diff --git a/var/www/html/upgrade.php b/var/www/html/upgrade.php new file mode 100644 index 0000000..cdc6811 --- /dev/null +++ b/var/www/html/upgrade.php @@ -0,0 +1,70 @@ + + +Daniel's Hosting - Upgrade account + + + + + + +

    Hosting - Upgrade account

    +An error occured talking to coinpayments

    '; +}else{ +?> +
    + + + + +
    Desired upgrade +
    Desired payment currency +
    +
    +Sorry, looks like you didn't select a valid upgrade.

    "; + }elseif(!isset($rates[$_POST['currency']]) || $rates[$_POST['currency']]['accepted'] !== 1 || !in_array('payments', $rates[$_POST['currency']]['capabilities'])){ + echo "

    Sorry, looks like you didn't select a valid payment currency.

    "; + }else{ + $db = get_db_instance(); + $transaction = coinpayments_create_transaction($_POST['currency'], ACCOUNT_UPGRADES[$_POST['upgrade']]['usd_price'], $_POST['upgrade'], $user['id']); + if($transaction === false){ + echo "

    An error occured creating the transaction, please try again

    "; + }else{ + echo "

    Please pay $transaction[amount] $_POST[currency] to $transaction[address]

    "; + echo 'QR Code'; + echo '

    Once paid, it can take a while until the upgrade is applied to your account. Usually within an hour.

    '; + } + } +} +?> +

    Go back to dashboard.

    + + diff --git a/var/www/setup.php b/var/www/setup.php index dc5a44e..06f52ef 100644 --- a/var/www/setup.php +++ b/var/www/setup.php @@ -29,8 +29,9 @@ 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;"); - $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 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, quota_size_used int(10) unsigned NOT NULL DEFAULT '0', quota_files_used int(10) unsigned NOT NULL DEFAULT '0', 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 nginx_rewrites (id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id int(11) NOT NULL, `regex` varchar(255) NOT NULL, replacement varchar(255) NOT NULL, `flag` varchar(9) NOT NULL, ifnotexists tinyint(1) NOT NULL, CONSTRAINT nginx_rewrites_ibfk_2 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;'); + $db->exec('CREATE TABLE payments (id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id int(11) NULL, payment_for varchar(255) COLLATE latin1_bin NOT NULL, txn_id varchar(255) COLLATE utf8mb4_bin NOT NULL, status tinyint NOT NULL, CONSTRAINT payments_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL 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]); @@ -155,6 +156,10 @@ if(!@$version=$db->query("SELECT value FROM settings WHERE setting='version';")) $db->exec('UPDATE onions SET enabled=1 WHERE enabled=2;'); $db->exec('CREATE TABLE nginx_rewrites (id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id int(11) NOT NULL, `regex` varchar(255) NOT NULL, replacement varchar(255) NOT NULL, `flag` varchar(9) NOT NULL, ifnotexists tinyint(1) NOT NULL, CONSTRAINT nginx_rewrites_ibfk_2 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;'); } + if($version<17){ + $db->exec("ALTER TABLE disk_quota ADD quota_size_used int(10) unsigned NOT NULL DEFAULT '0', ADD quota_files_used int(10) unsigned NOT NULL DEFAULT '0';"); + $db->exec('CREATE TABLE payments (id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id int(11) NULL, payment_for varchar(255) COLLATE latin1_bin NOT NULL, txn_id varchar(255) COLLATE utf8mb4_bin NOT NULL, status tinyint NOT NULL, CONSTRAINT payments_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;'); + } $stmt=$db->prepare("UPDATE settings SET value=? WHERE setting='version';"); $stmt->execute([DBVERSION]); } @@ -302,7 +307,7 @@ foreach(SERVICE_INSTANCES as $instance){ exec("systemctl start ".escapeshellarg("php$version-fpm@$instance")); } $stmt->execute([$instance]); - echo "Successfully added new instance $instance. Don't forget to add _tor-$instance as allowed user to your firewall rules in /etc/rc.local"; + echo "Successfully added new instance $instance. Don't forget to add _tor-$instance as allowed user to your firewall rules in /etc/rc.local\n"; } } // remove no longer enabled php/tor instances diff --git a/var/www/setup_chroot.sh b/var/www/setup_chroot.sh index 68a5e21..c9afde4 100755 --- a/var/www/setup_chroot.sh +++ b/var/www/setup_chroot.sh @@ -237,4 +237,4 @@ done for BINARY in /usr/lib/php/*/*.so; do CHROOT_BINARY $BINARY done -ln $CHROOT_DIRECTORY/usr/bin/php7.4 $CHROOT_DIRECTORY/usr/bin/php +ln -f $CHROOT_DIRECTORY/usr/bin/php7.4 $CHROOT_DIRECTORY/usr/bin/php