['sftp'=>22, 'ftp'=>21, 'pop3'=>'110', 'imap'=>'143', 'smtp'=>'25'], 'hosting.danwin1210.me'=>['sftp'=>22, 'ftp'=>21, 'pop3'=>'995', 'imap'=>'993', 'smtp'=>'465'] ]; const EMAIL_TO=''; //Send email notifications about new registrations to this address const INDEX_MD5S=[ //MD5 sums of index.hosting.html files that should be considdered as unchanged for deletion '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=['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 const PHP_CONFIG='memory_limit = 256M error_reporting = E_ALL post_max_size = 10G upload_max_filesize = 10G max_file_uploads = 100 date.timezone = UTC pdo_odbc.connection_pooling=off odbc.allow_persistent = Off ibase.allow_persistent = 0 mysqli.allow_persistent = Off pgsql.allow_persistent = Off opcache.enable=1 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=20000 opcache.use_cwd=1 opcache.validate_timestamps=1 opcache.revalidate_freq=2 opcache.revalidate_path=1 opcache.save_comments=1 opcache.optimization_level=0xffffffff opcache.validate_permission=1 opcache.validate_root=1 '; const NGINX_DEFAULT = 'server { listen unix:/var/run/nginx/suspended backlog=2048; add_header Content-Type text/html; location / { return 200 \'SuspendedThis domain has been suspended due to violation of our hosting rules.\'; } } server { listen [::]:80 ipv6only=off fastopen=100 backlog=2048 default_server; listen unix:/var/run/nginx.sock backlog=2048 default_server; root /var/www/html; index index.php; server_name ' . ADDRESS . ' *.' . ADDRESS . '; location / { try_files $uri $uri/ =404; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_param DOCUMENT_ROOT /html; fastcgi_param SCRIPT_FILENAME /html$fastcgi_script_name; fastcgi_pass unix:/var/run/php/7.3-hosting; } } location /squirrelmail { location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/var/run/php/7.3-squirrelmail; } } location /phpmyadmin { root /usr/share; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_param DOCUMENT_ROOT /html; fastcgi_param SCRIPT_FILENAME /html$fastcgi_script_name; fastcgi_pass unix:/run/php/7.3-phpmyadmin; } } location /adminer { root /var/www/html/adminer; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_param DOCUMENT_ROOT /html/adminer; fastcgi_param SCRIPT_FILENAME /html/adminer$fastcgi_script_name; fastcgi_pass unix:/run/php/7.3-adminer; } } location /externals/jush/ { root /var/www/html/adminer; } location /nginx/ { root /var/log/; internal; } } '; 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); $pk = base64_decode(substr($keyData['key'], 27, -26)); $skipped_first_22 = substr($pk, 22); $first_80_bits_of_sha1 = hex2bin(substr(sha1($skipped_first_22), 0, 20)); return base32_encode($first_80_bits_of_sha1); } function get_onion_v3(string $sk) : string { if(PHP_INT_SIZE === 4){ $pk = ParagonIE_Sodium_Core32_Ed25519::sk_to_pk($sk); }else{ $pk = ParagonIE_Sodium_Core_Ed25519::sk_to_pk($sk); } $checksum = substr(hash('SHA3-256', '.onion checksum' . $pk . hex2bin('03'), true), 0, 2); return base32_encode($pk . $checksum . hex2bin('03')); } function base32_encode(string $input) : string { $map = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', // 7 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', // 15 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', // 23 'y', 'z', '2', '3', '4', '5', '6', '7', // 31 ]; if(empty($input)){ return ''; } $input = str_split($input); $binaryString = ''; $c = count($input); for($i = 0; $i < $c; ++$i) { $binaryString .= str_pad(decbin(ord($input[$i])), 8, '0', STR_PAD_LEFT); } $fiveBitBinaryArray = str_split($binaryString, 5); $base32 = ''; $i = 0; $c = count($fiveBitBinaryArray); while($i < $c) { $base32 .= $map[bindec($fiveBitBinaryArray[$i])]; ++$i; } return $base32; } function send_captcha() { global $db; if(!CAPTCHA || !extension_loaded('gd')){ return; } $captchachars = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789'; $length = strlen($captchachars)-1; $code = ''; for($i = 0; $i < 5; ++$i){ $code .= $captchachars[mt_rand(0, $length)]; } $randid = mt_rand(); $time = time(); $stmt = $db->prepare('INSERT INTO captcha (id, time, code) VALUES (?, ?, ?);'); $stmt->execute([$randid, $time, $code]); echo "Copy: "; if(CAPTCHA === 1){ $im = imagecreatetruecolor(55, 24); $bg = imagecolorallocate($im, 0, 0, 0); $fg = imagecolorallocate($im, 255, 255, 255); imagefill($im, 0, 0, $bg); imagestring($im, 5, 5, 5, $code, $fg); echo ''; echo ""; } function check_login(){ global $db; if(empty($_SESSION['csrf_token'])){ $_SESSION['csrf_token']=sha1(uniqid()); } if(empty($_SESSION['hosting_username'])){ header('Location: login.php'); session_destroy(); exit; } $stmt=$db->prepare('SELECT * FROM users WHERE username=?;'); $stmt->execute([$_SESSION['hosting_username']]); if(!$user=$stmt->fetch(PDO::FETCH_ASSOC)){ header('Location: login.php'); session_destroy(); exit; } return $user; } function get_system_hash($pass) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./'; $salt = ''; for($i = 0; $i < 16; ++$i){ $salt .= $chars[random_int(0, strlen($chars)-1)]; } return crypt($pass, '$6$' . $salt . '$'); } function check_captcha_error() { global $db; if(CAPTCHA){ if(!isset($_REQUEST['challenge'])){ return 'Error: Wrong Captcha'; }else{ $stmt=$db->prepare('SELECT code FROM captcha WHERE id=?;'); $stmt->execute([$_REQUEST['challenge']]); $stmt->bindColumn(1, $code); if(!$stmt->fetch(PDO::FETCH_BOUND)){ return 'Error: Captcha expired'; }else{ $time=time(); $stmt=$db->prepare('DELETE FROM captcha WHERE id=? OR timeexecute([$_REQUEST['challenge'], $time-3600]); if($_REQUEST['captcha']!==$code){ if(strrev($_REQUEST['captcha'])!==$code){ return 'Error: Wrong captcha'; } } } } } return false; } function rewrite_torrc(PDO $db, string $key){ $torrc="ClientUseIPv6 1 ClientUseIPv4 1 SOCKSPort 0 MaxClientCircuitsPending 1024 NumEntryGuards 15 NumDirectoryGuards 15 NumPrimaryGuards 15 "; $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]); while($tmp=$stmt->fetch(PDO::FETCH_NUM)){ if($tmp[6]==1){ $socket=$tmp[1]; }else{ $socket='suspended'; } $torrc.="HiddenServiceDir /var/lib/tor-instances/$key/hidden_service_$tmp[0].onion HiddenServiceNumIntroductionPoints $tmp[2] HiddenServiceVersion $tmp[4] HiddenServiceMaxStreamsCloseCircuit 1 HiddenServiceMaxStreams $tmp[5] HiddenServicePort 80 unix:/var/run/nginx/$socket "; if($tmp[3]){ $torrc.="HiddenServicePort 25\n"; } } file_put_contents("/etc/tor/instances/$key/torrc", $torrc); exec("service tor@$key reload"); } function private_key_to_onion(string $priv_key) : array { $ok = true; $message = ''; $onion = ''; $priv_key = trim($priv_key); $version = 0; if(($pkey = openssl_pkey_get_private($priv_key)) !== false){ $version = 2; $details = openssl_pkey_get_details($pkey); if($details['type'] === OPENSSL_KEYTYPE_RSA){ $p = gmp_init(bin2hex($details['rsa']['p']), 16); $q = gmp_init(bin2hex($details['rsa']['q']), 16); $n = gmp_init(bin2hex($details['rsa']['n']), 16); $d = gmp_init(bin2hex($details['rsa']['d']), 16); $dmp1 = gmp_init(bin2hex($details['rsa']['dmp1']), 16); $dmq1 = gmp_init(bin2hex($details['rsa']['dmq1']), 16); $iqmp = gmp_init(bin2hex($details['rsa']['iqmp']), 16); } if($details['type'] !== OPENSSL_KEYTYPE_RSA){ $message = 'Error: private key is not an RSA key.'; $ok = false; }elseif($details['bits'] !== 1024){ $message = 'Error: private key not of bitsize 1024.'; $ok = false; }elseif(gmp_prob_prime($p) === 0){ $message = 'Error: p is not a prime'; $ok = false; }elseif(gmp_prob_prime($q) === 0){ $message = 'Error: q is not a prime'; $ok = false; }elseif(gmp_cmp($n, gmp_mul($p, $q) ) !== 0){ $message = 'Error: n does not equal p q'; $ok = false; }elseif(gmp_cmp($dmp1, gmp_mod($d, gmp_sub($p, 1) ) ) !==0 ){ $message = 'Error: dmp1 invalid'; $ok = false; }elseif(gmp_cmp($dmq1, gmp_mod($d, gmp_sub($q, 1) ) ) !== 0){ $message = 'Error: dmq1 invalid'; $ok = false; }elseif(gmp_cmp($iqmp, gmp_invert($q, $p) ) !==0 ){ $sessage = 'Error: iqmp not inverse of q'; $ok = false; }else{ $onion = get_onion_v2($pkey); } openssl_pkey_free($pkey); return ['ok' => $ok, 'message' => $message, 'onion' => $onion, 'version' => $version]; } elseif(($priv = base64_decode($priv_key, true)) !== false){ $version = 3; if(strpos($priv, '== ed25519v1-secret: type0 ==' . hex2bin('000000')) !== 0 || strlen($priv) !== 96){ $message = 'Error: v3 secret key invalid.'; $ok = false; } else { $onion = get_onion_v3(substr($priv, 32)); } return ['ok' => $ok, 'message' => $message, 'onion' => $onion, 'version' => $version]; } $message = 'Error: private key invalid.'; $ok = false; return ['ok' => $ok, 'message' => $message, 'onion' => $onion, 'version' => $version]; } function generate_new_onion(int $version = 3) : array { $priv_key = ''; $onion = ''; if($version === 2){ $pkey = openssl_pkey_new(['private_key_bits' => 1024, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); openssl_pkey_export($pkey, $priv_key); $onion = get_onion_v2($pkey); openssl_pkey_free($pkey); } else { $seed = random_bytes(32); $sk = ed25519_seckey_expand($seed); $priv_key = base64_encode('== ed25519v1-secret: type0 ==' . hex2bin('000000') . $sk); $onion = get_onion_v3($sk); } return ['priv_key' => $priv_key, 'onion' => $onion, 'version' => $version]; } function ed25519_seckey_expand(string $seed) : string { $sk = hash('sha512', substr($seed, 0, 32), true); $sk[0] = chr(ord($sk[0]) & 248); $sk[31] = chr(ord($sk[31]) & 63); $sk[31] = chr(ord($sk[31]) | 64); return $sk; } function rewrite_nginx_config(PDO $db){ $nginx=''; // onions $stmt=$db->query("SELECT users.system_account, users.php, users.autoindex, onions.onion FROM users INNER JOIN onions ON (onions.user_id=users.id) WHERE onions.enabled IN (1, -2) AND users.id NOT IN (SELECT user_id FROM new_account) AND users.todelete!=1;"); while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){ if($tmp['php']>0){ $php_location=" location ~ [^/]\.php(/|\$) { include snippets/fastcgi-php.conf; fastcgi_param DOCUMENT_ROOT /www; fastcgi_param SCRIPT_FILENAME /www\$fastcgi_script_name; fastcgi_pass unix:/run/php/$tmp[system_account]; }"; }else{ $php_location=''; } $autoindex = $tmp['autoindex'] ? 'on' : 'off'; $nginx.="server { listen unix:/var/run/nginx/$tmp[system_account]; root /home/$tmp[system_account]/www; server_name $tmp[onion].onion *.$tmp[onion].onion; access_log /var/log/nginx/access_$tmp[system_account].log custom buffer=4k flush=1m; access_log /home/$tmp[system_account]/logs/access.log custom buffer=4k flush=1m; error_log /var/log/nginx/error_$tmp[system_account].log notice; error_log /home/$tmp[system_account]/logs/error.log notice; disable_symlinks on from=/home/$tmp[system_account]; autoindex $autoindex; location / { try_files \$uri \$uri/ =404;$php_location } } "; } // clearnet domains $stmt=$db->query("SELECT users.system_account, users.php, users.autoindex, domains.domain FROM users INNER JOIN domains ON (domains.user_id=users.id) WHERE domains.enabled = 1 AND users.id NOT IN (SELECT user_id FROM new_account) AND users.todelete != 1;"); while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){ if($tmp['php']>0){ $php_location=" location ~ [^/]\.php(/|\$) { include snippets/fastcgi-php.conf; fastcgi_param DOCUMENT_ROOT /www; fastcgi_param SCRIPT_FILENAME /www\$fastcgi_script_name; fastcgi_pass unix:/run/php/$tmp[system_account]; }"; }else{ $php_location=''; } $autoindex = $tmp['autoindex'] ? 'on' : 'off'; $nginx.="server { listen [::]:80; root /home/$tmp[system_account]/www; server_name $tmp[domain]; access_log /var/log/nginx/access_$tmp[system_account].log custom buffer=4k flush=1m; access_log /home/$tmp[system_account]/logs/access.log custom buffer=4k flush=1m; error_log /var/log/nginx/error_$tmp[system_account].log notice; error_log /home/$tmp[system_account]/logs/error.log notice; disable_symlinks on from=/home/$tmp[system_account]; autoindex $autoindex; location / { try_files \$uri \$uri/ =404;$php_location } } "; } file_put_contents("/etc/nginx/sites-enabled/hosted_sites", $nginx); $nginx=''; $stmt=$db->query("SELECT system_account FROM users WHERE id NOT IN (SELECT user_id FROM new_account) AND todelete!=1;"); while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){ $nginx.="server { listen unix:/home/$tmp[system_account]/var/run/mysqld/mysqld.sock; proxy_pass unix:/var/run/mysqld/mysqld.sock; } "; } file_put_contents("/etc/nginx/streams-enabled/hosted_sites", $nginx); exec("service nginx reload"); } function rewrite_php_config(PDO $db, string $key){ $stmt=$db->prepare("SELECT system_account FROM users WHERE instance = ? AND php=? AND todelete!=1 AND id NOT IN (SELECT user_id FROM new_account);"); foreach(array_replace(PHP_VERSIONS, DISABLED_PHP_VERSIONS) as $php_key => $version){ $stmt->execute([$key, $php_key]); $php = "[www] user = www-data group = www-data listen = /run/php/$version-$key listen.owner = www-data listen.group = www-data pm = ondemand pm.max_children = 8 "; while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){ $php.='['.$tmp['system_account']."] user = $tmp[system_account] group = www-data listen = /run/php/$tmp[system_account] listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = ondemand pm.max_children = 75 pm.process_idle_timeout = 10s; chroot = /home/$tmp[system_account] php_admin_value[memory_limit] = 256M php_admin_value[disable_functions] = pcntl_alarm,pcntl_async_signals,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_signal_get_handler,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_waitpid,pcntl_wait,pcntl_wexitstatus,pcntl_wifcontinued,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,popen,posix_ctermid,posix_getgrgid,posix_getgrnam,posix_getpgid,posix_getpwnam,posix_getpwuid,posix_getrlimit,posix_getsid,posix_kill,posix_setegid,posix_seteuid,posix_setgid,posix_setpgid,posix_setrlimit,posix_setuid,posix_ttyname,posix_uname,putenv,socket_listen,socket_create_listen,socket_bind,stream_socket_server php_admin_value[upload_tmp_dir] = /tmp php_admin_value[soap.wsdl_cache_dir] = /tmp php_admin_value[session.save_path] = /tmp "; } file_put_contents("/etc/php/$version/fpm/pool.d/$key/www.conf", $php); exec("service php$version-fpm@$key restart"); } } function add_mysql_user(PDO $db, string $password) : string { $mysql_user = ''; $stmt = $db->prepare('SELECT null FROM users WHERE mysql_user = ?;'); do { $mysql_user = substr(preg_replace('/[^a-z0-9]/i', '', base64_encode(random_bytes(32))), 0, 32); $stmt->execute([$mysql_user]); } while($stmt->fetch()); $create_user = $db->prepare("CREATE USER ?@'%' IDENTIFIED BY ?;"); $create_user->execute([$mysql_user, $password]); return $mysql_user; } function add_user_db(PDO $db, int $user_id) : ?string { $mysql_db = ''; $stmt = $db->prepare('SELECT COUNT(*) FROM mysql_databases WHERE user_id = ?;'); $stmt->execute([$user_id]); $count = $stmt->fetch(PDO::FETCH_NUM); if($count[0]>=MAX_NUM_USER_DBS) { return null; } $stmt = $db->prepare('SELECT null FROM mysql_databases WHERE mysql_database = ?;'); do { $mysql_db = substr(preg_replace('/[^a-z0-9]/i', '', base64_encode(random_bytes(32))), 0, 32); $stmt->execute([$mysql_db]); } while($stmt->fetch()); $stmt = $db->prepare('INSERT INTO mysql_databases (user_id, mysql_database) VALUES (?, ?);'); $stmt->execute([$user_id, $mysql_db]); $db->exec("CREATE DATABASE IF NOT EXISTS `" . $mysql_db . "`;"); $stmt = $db->prepare('SELECT mysql_user FROM users WHERE id = ?;'); $stmt->execute([$user_id]); $user = $stmt->fetch(PDO::FETCH_ASSOC); $stmt=$db->prepare("GRANT ALL PRIVILEGES ON `" . $mysql_db . "`.* TO ?@'%';"); $stmt->execute([$user['mysql_user']]); $db->exec('FLUSH PRIVILEGES;'); return $mysql_db; } function del_user_db(PDO $db, int $user_id, string $mysql_db) { $stmt = $db->prepare('SELECT mysql_user FROM users WHERE id = ?;'); $stmt->execute([$user_id]); $user = $stmt->fetch(PDO::FETCH_ASSOC); $stmt = $db->prepare('SELECT null FROM mysql_databases WHERE user_id = ? AND mysql_database = ?;'); $stmt->execute([$user_id, $mysql_db]); if($stmt->fetch()){ $stmt = $db->prepare('REVOKE ALL PRIVILEGES ON `'.preg_replace('/[^a-z0-9]/i', '', $mysql_db)."`.* FROM ?@'%';"); $stmt->execute([$user['mysql_user']]); $db->exec('DROP DATABASE IF EXISTS `'.preg_replace('/[^a-z0-9]/i', '', $mysql_db).'`;'); $stmt = $db->prepare('DELETE FROM mysql_databases WHERE user_id = ? AND mysql_database = ?;'); $stmt->execute([$user_id, $mysql_db]); } } function get_new_tor_instance(PDO $db){ $stmt = $db->query('SELECT s.ID FROM service_instances AS s LEFT JOIN onions AS o ON (s.ID = o.instance) GROUP BY s.ID ORDER BY count(s.ID) LIMIT 1;'); return $stmt->fetch(PDO::FETCH_NUM)[0]; } function add_user_onion(PDO $db, int $user_id, string $onion, string $priv_key, int $onion_version) { $stmt=$db->prepare('INSERT INTO onions (user_id, onion, private_key, version, enabled, instance) VALUES (?, ?, ?, ?, 2, ?);'); $stmt->execute([$user_id, $onion, $priv_key, $onion_version, get_new_tor_instance($db)]); } function del_user_onion(PDO $db, int $user_id, string $onion) { $stmt = $db->prepare('SELECT null FROM onions WHERE user_id = ? AND onion = ? AND enabled IN (0, 1);'); $stmt->execute([$user_id, $onion]); if($stmt->fetch()){ $stmt = $db->prepare("UPDATE onions SET enabled='-1' WHERE user_id = ? AND onion = ?;"); $stmt->execute([$user_id, $onion]); } } function add_user_domain(PDO $db, int $user_id, string $domain) : string { $domain = strtolower($domain); if(strlen($domain) > 255){ return "Domain can't be longer than 255 characters."; } if(preg_match('/.onion$/', $domain)){ return "Domain can't end in .onion which is reserved for tor hidden services."; } $parts = explode('.', $domain); if(count($parts) < 2){ return 'Invalid domain'; } foreach($parts as $part){ if(!preg_match('/^([0-9a-z][0-9a-z\-]*[0-9a-z]|[0-9a-z])$/', $part)){ return 'Invalid domain'; } } $stmt = $db->prepare('SELECT null FROM domains WHERE domain = ?;'); $stmt->execute([$domain]); if($stmt->fetch()){ return 'This domain already exists!'; } $stmt = $db->prepare("INSERT INTO domains (user_id, domain, enabled) VALUES (?, ?, 1);"); $stmt->execute([$user_id, $domain]); return ''; } function del_user_domain(PDO $db, int $user_id, string $domain) { $stmt = $db->prepare('SELECT null FROM domains WHERE user_id = ? AND domain = ? AND enabled IN (0, 1);'); $stmt->execute([$user_id, $domain]); if($stmt->fetch()){ $stmt = $db->prepare("DELETE FROM domains WHERE user_id = ? AND domain = ?;"); $stmt->execute([$user_id, $domain]); } } function check_csrf_error(){ if(empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']){ return 'Invalid CSRF token, please try again.'; } return false; } function enqueue_instance_reload($db, $instance = null){ if($instance === null){ $stmt=$db->prepare('UPDATE service_instances SET reload = 1 LIMIT 1;'); }else{ $stmt=$db->prepare('UPDATE service_instances SET reload = 1 WHERE id = ?;'); $stmt->execute([$instance]); } }