├── .gitignore ├── README.md ├── example_install.sh ├── install.php ├── post-install.php ├── scripts ├── files │ ├── apt_updater.php │ ├── firewall_ipv4.txt │ ├── firewall_ipv6.txt │ ├── nginx_core.txt │ ├── nginx_global_location-cms.txt │ ├── nginx_global_location-default.txt │ ├── nginx_global_location-drc.txt │ ├── nginx_global_php-local.txt │ ├── nginx_global_php-proxy.txt │ ├── nginx_global_restrictions.txt │ ├── nginx_site.txt │ ├── nginx_site_default.txt │ └── opendkim_trusted_hosts.txt ├── functions.php ├── init_system.php ├── main.php ├── post_setup_dkim.php ├── post_setup_https.php ├── setup_db_mariadb_configure.php ├── setup_db_mariadb_install.php ├── setup_db_mysql_configure.php ├── setup_db_mysql_install.php ├── setup_db_postgresql.php ├── setup_email_sendonly.php ├── setup_nginx.php ├── setup_php_cli.php ├── setup_php_drc.php └── setup_php_fpm.php └── support ├── cli.php ├── db.php ├── db_mysql_lite.php ├── db_pgsql_lite.php ├── dir_helper.php ├── en_us_lite.json ├── process_helper.php └── random.php /.gitignore: -------------------------------------------------------------------------------- 1 | config.dat 2 | test.sh 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Server Instant Start 2 | ==================== 3 | 4 | Spin up a fully configured Ubuntu/Debian-based web server in under 10 minutes with Nginx (w/ HTTPS), PHP FPM, Postfix, OpenDKIM, MySQL/MariaDB, PostgreSQL, and more. Deploy your web application too. 5 | 6 | Instant Start is useful for setting up an entire server with minimal effort. Quickly install all components of a server in just a couple of minutes: A well-rounded OS configuration plus optional configuration of web server, email sending capabilities, a scripting language, and database(s). The contents of and knowledge contained in this repository come from responsibly managing many Linux-based web servers for over a decade. 7 | 8 | [![Server Instant Start Overview and Demo video](https://user-images.githubusercontent.com/1432111/104147985-ae9dbf80-538d-11eb-863b-9a0a0cd593ac.png)](https://www.youtube.com/watch?v=l3yimAjmo9c "Server Instant Start Overview and Demo") 9 | 10 | Only using Instant Start on a brand new server is highly recommended. Any Debian-based Linux distribution will probably work fine. Failure to use Instant Start on a newly created system may result in damage to existing configuration files and/or data loss. 11 | 12 | [![Donate](https://cubiclesoft.com/res/donate-shield.png)](https://cubiclesoft.com/donate/) [![Discord](https://img.shields.io/discord/777282089980526602?label=chat&logo=discord)](https://cubiclesoft.com/product-support/github/) 13 | 14 | Features 15 | -------- 16 | 17 | * A simple set of scripts that automatically install and configure several software products. 18 | * Your new server is ready to use in just a couple of minutes. 19 | * Nearly zero configuration required (see below). 20 | * Has a liberal open source license. MIT or LGPL, your choice. 21 | * Designed for rapid deployment. 22 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. 23 | 24 | Getting Started 25 | --------------- 26 | 27 | Open the following in a new tab to start creating a Droplet on DigitalOcean: 28 | 29 | [![Deploy to DO](https://mp-assets1.sfo2.digitaloceanspaces.com/deploy-to-do/do-btn-blue.svg)](https://cloud.digitalocean.com/droplets/new?size=s-1vcpu-1gb&distro=ubuntu&options=ipv6) 30 | 31 | (Read the Alternate VPS Setup section below for using Instant Start with other VPS providers.) 32 | 33 | Using the latest Ubuntu x64 Long-Term Support (LTS) release is recommended. 34 | 35 | Under "Select additional options" check the checkbox that says "User data". Copy and paste the following script into the box that appears and modify it as you see fit: 36 | 37 | ```sh 38 | #!/bin/sh 39 | 40 | export DEBIAN_FRONTEND=noninteractive; 41 | 42 | apt-get update; 43 | apt-get -y dist-upgrade; 44 | apt-get -y install openssl git wget curl php-cli; 45 | 46 | export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address); 47 | export PUBLIC_IPV6=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv6/address); 48 | 49 | # A list of timezones can be found here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 50 | # Or automatic: https://geoip.ubuntu.com/lookup 51 | export TZ=""; 52 | 53 | # Set the hostname. What to name this server? 54 | export INSTANT_HOSTNAME=""; 55 | 56 | # Set the configured domain(s) to use (e.g. yourdomain.com, anotherdomain.com). 57 | export INSTANT_EMAIL_DOMAIN=""; 58 | export INSTANT_WWW_DOMAINS=""; 59 | 60 | # Select servers to install (if any). 61 | # Options: nginx, php-fpm, email-sendonly, mariadb, mysql, postgresql, php-drc 62 | export INSTANT_SERVERS=""; 63 | 64 | cd /root; 65 | 66 | # Optionally clone useful but unrelated CubicleSoft network and server management software. 67 | # NOTE: Some software products require separate installation/configuration (e.g. Cloud Backup is not magical). 68 | #git clone https://github.com/cubiclesoft/net-test.git; 69 | #git clone https://github.com/cubiclesoft/network-speedtest-cli.git; 70 | #git clone https://github.com/cubiclesoft/php-ssh.git; 71 | #git clone https://github.com/cubiclesoft/php-ssl-certs.git; 72 | #git clone https://github.com/cubiclesoft/cloud-backup.git; 73 | 74 | # Clone and run Server Instant Start. 75 | git clone https://github.com/cubiclesoft/server-instant-start.git; 76 | 77 | cd /root/server-instant-start; 78 | php install.php init-system php-cli; 79 | 80 | # Put additional installation stuff here (e.g. your application installer). 81 | 82 | # Comment this out if you want to reboot manually later. 83 | cd /root/server-instant-start; 84 | php install.php reboot-if-required; 85 | ``` 86 | 87 | Update the `export TZ=` line with your current timezone. This will be used to set the timezone of the Droplet and associated software (e.g. PHP) so that dates and times are stored and displayed as expected. The timezone also affects any cron jobs that are set up. Leave it blank for `UTC +0000`. 88 | 89 | The other `export` options are optional. Fill out the desired configuration and uncomment/include any additional software you want to install/configure later. 90 | 91 | Even after the Droplet becomes available, it can be a few minutes before the server is fully configured. To watch the installation/configuration progress, run the following command from a SSH terminal: 92 | 93 | ``` 94 | tail -f /var/log/cloud-init-output.log 95 | ``` 96 | 97 | When the server installation is finished, a file called `/root/README-ServerInstantStart` will be created which contains credentials for various server resources (e.g. MariaDB root password). SSH or SFTP is required to read the file. 98 | 99 | ``` 100 | cat /root/README-ServerInstantStart 101 | ``` 102 | 103 | After installation, configure DNS to point at the IP address(es) of the new system. Then run the post-install script to set up HTTPS and/or DKIM: 104 | 105 | ``` 106 | cd /root/server-instant-start 107 | php post-install.php https nginx yourdomain.com www.yourdomain.com 108 | php post-install.php dkim create default yourdomain.com 109 | php post-install.php dkim verify default yourdomain.com 110 | ``` 111 | 112 | Key Locations 113 | ------------- 114 | 115 | * `/var/www/yourdomain.com/public_html` - The public web root for a domain. 116 | * `/var/www/yourdomain.com/protected_html` - A private directory for a domain. 117 | * `/var/scripts` - Various automation scripts (e.g. cron jobs). 118 | * `/etc/iptables/rules.v4` and `/etc/iptables/rules.v6` - Firewall rules (iptables). 119 | * `/opt/php-drc` - Data Relay Center configuration. 120 | 121 | Alternate VPS Setup 122 | ------------------- 123 | 124 | To run this software, you need an Ubuntu/Debian OS distribution on a Virual Private Server (VPS) or dedicated host. Providers like DigitalOcean, OVH, AWS, Azure, etc. make it easy to spin up a VPS. 125 | 126 | The shell script under the Getting Started section is also in `example_install.sh`. For non-DigitalOcean hosts, just upload files, manually modify `PUBLIC_IPV4` and `PUBLIC_IPV6` in `example_install.sh` with correct IP address(es), perform a `chmod 755 example_install.sh`, and then execute the script as the `root` user `./example_install.sh`. 127 | 128 | DigitalOcean is primarily for quickly setting up a temporary Internet-facing server, which is good for trying out new things like Server Instant Start, testing some software in isolation, or for short-lived projects. Web hosting service providers abound but most of those are shared hosts with little control. A Virtual Private Server (VPS), which is what DigitalOcean mostly offers/provides, is something between shared hosting and cloud/dedicated hosting. Droplets are intended to be cheap, short-lived VPS instances that are created and destroyed as needed. Even though Droplets weren't really ever intended for normal web hosting, quite a few people use them that way. 129 | 130 | Running a VPS (or similar) comes with responsbilities. The biggest one is making sure that the system is secure, which means that the system remains fully patched because it won't automatically be done for you. Server Instant Start solves a number of configuration management problems by performing an opinionated installation that attempts to create a generally self-securing setup. For example, it installs a PHP script that runs `apt-get dist-upgrade` with automatic rebooting as needed (e.g. kernel updates) and configures cron to automatically run that script every single day. 131 | 132 | If the intent is to run a server long-term, I highly recommend using an [OVH VPS](https://www.ovhcloud.com/en/vps/cheap-vps/) instead of DigitalOcean since OVH offers a lot more hardware and network transfer for less cost but slightly less comprehensive technical support. 133 | 134 | Installed Software 135 | ------------------ 136 | 137 | Always installed and configured: 138 | 139 | * PHP CLI. 140 | * fail2ban. Slows down attackers. 141 | * iptables-persistent. Sane default firewall rules. 142 | * net-tools. netstat, etc. 143 | * vnstat. Tracks monthly network transfer. 144 | * htop. A much better top. 145 | * Fully automated system update script (except major OS upgrades). 146 | * PHP extensions (cURL, JSON, PDO sqlite, GD). 147 | 148 | Optionally installed and configured: 149 | 150 | * Postfix. 151 | * OpenDKIM. Post-install only. 152 | * Nginx. 153 | * Let's Encrypt. Post-install only. 154 | * PHP FPM. 155 | * PHP extensions (PDO mysql, PDO postgres, PECL ev). 156 | * MariaDB/MySQL. 157 | * PostgreSQL. 158 | * [Data Relay Center](https://github.com/cubiclesoft/php-drc). 159 | 160 | Modified Files 161 | -------------- 162 | 163 | The following changes are made to the system by Instant Start that some distro purists may disagree with. These are documented so that you can decide if you want to adjust specific changes later or install and configure specific packages yourself. 164 | 165 | Always modified: 166 | 167 | * `/etc/sysctl.conf` - Changes a few various kernel options for improved uptime and security. See [this post](http://cubicspot.blogspot.com/2016/06/elegant-iptables-rules-for-your-linux.html) for details. 168 | * `/etc/security/limits.conf`, `/etc/systemd/system.conf`, `/etc/systemd/user.conf`, `/etc/pam.d/common-session`, and `/etc/pam.d/common-session-noninteractive` - Set OS file handle limits. See [this post](https://superuser.com/questions/1200539/cannot-increase-open-file-limit-past-4096-ubuntu) for details. 169 | 170 | When `INSTANT_HOSTNAME` is set (i.e. not an empty string): 171 | 172 | * `/etc/hostname`, `/etc/hosts` - Sets the hostname to the value in `INSTANT_HOSTNAME`. 173 | * `/etc/cloud/cloud.cfg` - Disables setting the hostname during boot. 174 | 175 | When `INSTANT_SERVERS` contains 'nginx': 176 | 177 | * `/etc/apt/sources.list` - Adds the official Nginx packages from nginx.org to the apt sources list since Debian lags behind several releases. 178 | * `/etc/nginx/nginx.conf` - Created using the template from [scripts/files/nginx_core.txt](scripts/files/nginx_core.txt). 179 | * `/etc/nginx/sites-available/default.conf` - Created using the template from [scripts/files/nginx_site_default.txt](scripts/files/nginx_site_default.txt). 180 | 181 | When `INSTANT_SERVERS` contains 'php-fpm': 182 | 183 | * `/etc/php/.../fpm/php.ini` - Increases various limits, enables the Zend opcache, and sets the timezone. 184 | * `/etc/php/.../fpm/pool.d/www.conf` - Switches from Unix sockets to TCP and switches to on-demand mode to better optimize system resources. 185 | 186 | When `INSTANT_SERVERS` contains 'email-sendonly': 187 | 188 | * `/etc/postfix/main.cf` - Sets the mail hostname via `INSTANT_EMAIL_DOMAIN` and applies a couple of sensible changes to prevent an open mail relay. 189 | 190 | When `INSTANT_SERVERS` contains 'mariadb': 191 | 192 | * `/etc/apt/sources.list` - Adds the official MariaDB packages from a DigitalOcean mirror to the apt sources list since Debian lags behind several releases. 193 | 194 | More Information 195 | ---------------- 196 | 197 | The PHP installation script `install.php` aims to be idempotent. That is, if it is run again intentionally or by accident, it will result in the same output. 198 | 199 | A system group called `sftp-users` is created during the installation process. The `setgid` attribute is set on various key locations so that any user assigned to the group can easily create new files in a team setting. Just assign the `sftp-users` group to members of your team. 200 | -------------------------------------------------------------------------------- /example_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export DEBIAN_FRONTEND=noninteractive; 4 | 5 | apt-get update; 6 | apt-get -y dist-upgrade; 7 | apt-get -y install openssl git wget curl php-cli; 8 | 9 | export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address); 10 | export PUBLIC_IPV6=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv6/address); 11 | 12 | # A list of timezones can be found here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 13 | # Or automatic: https://geoip.ubuntu.com/lookup 14 | export TZ=""; 15 | 16 | # Set the hostname. What to name this server? 17 | export INSTANT_HOSTNAME=""; 18 | 19 | # Set the configured domain(s) to use (e.g. yourdomain.com, anotherdomain.com). 20 | export INSTANT_EMAIL_DOMAIN=""; 21 | export INSTANT_WWW_DOMAINS=""; 22 | 23 | # Select servers to install (if any). 24 | # Options: nginx, php-fpm, email-sendonly, mariadb, mysql, postgresql, php-drc 25 | export INSTANT_SERVERS=""; 26 | 27 | cd /root; 28 | 29 | # Optionally clone useful but unrelated CubicleSoft network and server management software. 30 | # NOTE: Some software products require separate installation/configuration (e.g. Cloud Backup is not magical). 31 | #git clone https://github.com/cubiclesoft/net-test.git; 32 | #git clone https://github.com/cubiclesoft/network-speedtest-cli.git; 33 | #git clone https://github.com/cubiclesoft/php-ssh.git; 34 | #git clone https://github.com/cubiclesoft/php-ssl-certs.git; 35 | #git clone https://github.com/cubiclesoft/cloud-backup.git; 36 | 37 | # Clone and run Server Instant Start. 38 | git clone https://github.com/cubiclesoft/server-instant-start.git; 39 | 40 | cd /root/server-instant-start; 41 | php install.php init-system php-cli; 42 | 43 | # Put additional installation stuff here (e.g. your application installer). 44 | 45 | # Comment this out if you want to reboot manually later. 46 | cd /root/server-instant-start; 47 | php install.php reboot-if-required; 48 | -------------------------------------------------------------------------------- /install.php: -------------------------------------------------------------------------------- 1 | 1 && $argv[1] === "reboot-if-required") 27 | { 28 | // Reboot automatically as needed. 29 | if (file_exists("/var/run/reboot-required")) system("reboot"); 30 | } 31 | else 32 | { 33 | system("/usr/bin/apt-get update"); 34 | system("/usr/bin/apt-get -y install software-properties-common iptables-persistent fail2ban vnstat net-tools htop openssl git wget curl php-gd php-json php-sqlite3 php-curl"); 35 | 36 | // Now that the environment is normalized, run the main script. 37 | $cmd = escapeshellarg(PHP_BINARY) . " " . escapeshellarg($rootpath . "/scripts/main.php"); 38 | for ($x = 1; $x < $argc; $x++) $cmd .= " " . escapeshellarg($argv[$x]); 39 | $options = explode(" ", preg_replace('/\s+/', " ", trim(str_replace(array(",", ";"), " ", (string)getenv("INSTANT_SERVERS"))))); 40 | foreach ($options as $opt) 41 | { 42 | if ($opt !== "") $cmd .= " " . escapeshellarg($opt); 43 | } 44 | 45 | RunExecutable($cmd); 46 | 47 | echo "\nInstallation complete.\n"; 48 | } 49 | 50 | putenv("PATH=" . $prevpath); 51 | ?> -------------------------------------------------------------------------------- /post-install.php: -------------------------------------------------------------------------------- 1 | array( 29 | "?" => "help" 30 | ), 31 | "rules" => array( 32 | "help" => array("arg" => false) 33 | ), 34 | "allow_opts_after_param" => false 35 | ); 36 | $args = CLI::ParseCommandLine($options); 37 | 38 | if (isset($args["opts"]["help"])) 39 | { 40 | echo "The post-installation tool\n"; 41 | echo "Purpose: Apply additional post-installation options from the command-line.\n"; 42 | echo "\n"; 43 | echo "This tool is question/answer enabled. Just running it will provide a guided interface. It can also be run entirely from the command-line if you know all the answers.\n"; 44 | echo "\n"; 45 | echo "Syntax: " . $args["file"] . " [options] [cmdgroup cmd [cmdoptions]]\n"; 46 | echo "\n"; 47 | echo "Examples:\n"; 48 | echo "\tphp " . $args["file"] . "\n"; 49 | echo "\tphp " . $args["file"] . " https nginx yourdomain.com www.yourdomain.com\n"; 50 | echo "\tphp " . $args["file"] . " dkim create default yourdomain.com\n"; 51 | echo "\tphp " . $args["file"] . " dkim verify default yourdomain.com\n"; 52 | 53 | exit(); 54 | } 55 | 56 | $origargs = $args; 57 | $suppressoutput = (isset($args["opts"]["suppressoutput"]) && $args["opts"]["suppressoutput"]); 58 | 59 | // Get the command group. 60 | $cmdgroups = array(); 61 | $dir = opendir($rootpath . "/scripts"); 62 | if ($dir) 63 | { 64 | while (($file = readdir($dir)) !== false) 65 | { 66 | if (substr($file, 0, 11) !== "post_setup_" || substr($file, -4) !== ".php") continue; 67 | 68 | $key = substr($file, 11, -4); 69 | 70 | $cmdgroups[$key] = $key; 71 | } 72 | 73 | closedir($dir); 74 | } 75 | 76 | if (!count($cmdgroups)) CLI::DisplayError("No command groups were found in '" . $rootpath . "/scripts'."); 77 | 78 | $cmdgroup = CLI::GetLimitedUserInputWithArgs($args, false, "Command group", false, "Available command groups:", $cmdgroups, true, $suppressoutput); 79 | 80 | require_once $rootpath . "/scripts/post_setup_" . $cmdgroup . ".php"; 81 | 82 | echo "\nDone.\n"; 83 | 84 | putenv("PATH=" . $prevpath); 85 | ?> -------------------------------------------------------------------------------- /scripts/files/apt_updater.php: -------------------------------------------------------------------------------- 1 | /tmp/apt_log.txt 2>&1"); 13 | system("/usr/bin/apt-get -y autoremove"); 14 | system("/usr/bin/apt-get autoclean"); 15 | 16 | // Reboot automatically as needed. 17 | if (file_exists("/var/run/reboot-required")) system("reboot"); 18 | 19 | putenv("PATH=" . $prevpath); 20 | ?> -------------------------------------------------------------------------------- /scripts/files/firewall_ipv4.txt: -------------------------------------------------------------------------------- 1 | *filter 2 | :INPUT DROP [0:0] 3 | :FORWARD DROP [0:0] 4 | :OUTPUT ACCEPT [0:0] 5 | -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 6 | -A INPUT -i lo -j ACCEPT 7 | -A INPUT -p tcp --syn --dport 80 -j ACCEPT 8 | -A INPUT -p tcp --syn --dport 443 -j ACCEPT 9 | -A INPUT -p tcp --dport 22 -j ACCEPT 10 | -A INPUT -p icmp --fragment -j DROP 11 | -A INPUT -p icmp --icmp-type 3 -j ACCEPT 12 | -A INPUT -p icmp --icmp-type 4 -j ACCEPT 13 | -A INPUT -p icmp --icmp-type 8 -j ACCEPT 14 | -A INPUT -p icmp --icmp-type 11 -j ACCEPT 15 | COMMIT 16 | -------------------------------------------------------------------------------- /scripts/files/firewall_ipv6.txt: -------------------------------------------------------------------------------- 1 | *filter 2 | :INPUT DROP [0:0] 3 | :FORWARD DROP [0:0] 4 | :OUTPUT ACCEPT [0:0] 5 | -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 6 | -A INPUT -i lo -j ACCEPT 7 | -A INPUT -p tcp --syn --dport 80 -j ACCEPT 8 | -A INPUT -p tcp --syn --dport 443 -j ACCEPT 9 | -A INPUT -p tcp --dport 22 -j ACCEPT 10 | -A INPUT -p icmpv6 -j ACCEPT 11 | COMMIT 12 | -------------------------------------------------------------------------------- /scripts/files/nginx_core.txt: -------------------------------------------------------------------------------- 1 | user www-data www-data; 2 | worker_processes 2; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 2048; 9 | } 10 | 11 | worker_rlimit_nofile 4096; 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | '$status $body_bytes_sent "$http_referer" ' 19 | '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | access_log /var/log/nginx/access.log main; 22 | 23 | sendfile on; 24 | # tcp_nopush on; 25 | 26 | ssl_dhparam /var/local/dhparam2048.pem; 27 | ssl_buffer_size 4k; 28 | ssl_session_cache shared:SSL:50m; 29 | ssl_session_timeout 10m; 30 | ssl_session_tickets off; 31 | ssl_stapling on; 32 | ssl_stapling_verify on; 33 | ssl_protocols TLSv1.2 TLSv1.3; 34 | ssl_prefer_server_ciphers on; 35 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 36 | 37 | resolver 8.8.8.8 8.8.4.4; 38 | resolver_timeout 10s; 39 | 40 | keepalive_timeout 65; 41 | 42 | # Note that there are known attacks against SSL-enabled compression. Enabling this is not recommended. 43 | #gzip on; 44 | 45 | large_client_header_buffers 8 16k; 46 | client_max_body_size 100m; 47 | index index.php index.html index.htm; 48 | 49 | # Upstream to abstract backend connection(s) for PHP. 50 | upstream php { 51 | # server unix:/tmp/php-fpm.sock; 52 | server 127.0.0.1:9000; 53 | } 54 | 55 | map $http_upgrade $connection_upgrade { 56 | default upgrade; 57 | '' close; 58 | } 59 | 60 | include /etc/nginx/sites-enabled/*.conf; 61 | } 62 | -------------------------------------------------------------------------------- /scripts/files/nginx_global_location-cms.txt: -------------------------------------------------------------------------------- 1 | # Generic fallback for when no other URI matches have taken place. 2 | # Including this or a similar file is recommended when using a CMS. 3 | 4 | location / { 5 | try_files $uri $uri/ /index.php$is_args$args; 6 | } 7 | -------------------------------------------------------------------------------- /scripts/files/nginx_global_location-default.txt: -------------------------------------------------------------------------------- 1 | # Generic fallback for when no other URI matches have taken place. 2 | # Including this file is NOT recommended when using a CMS. 3 | 4 | location / { 5 | try_files $uri $uri/ =404; 6 | } 7 | -------------------------------------------------------------------------------- /scripts/files/nginx_global_location-drc.txt: -------------------------------------------------------------------------------- 1 | # Data Relay Center proxy. 2 | 3 | location /drc/ { 4 | proxy_pass http://127.0.0.1:7328; 5 | proxy_http_version 1.1; 6 | proxy_set_header Host $http_host; 7 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 8 | proxy_set_header X-Forwarded-Proto $scheme; 9 | proxy_set_header Upgrade $http_upgrade; 10 | proxy_set_header Connection "upgrade"; 11 | proxy_send_timeout 300s; 12 | proxy_read_timeout 300s; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/files/nginx_global_php-local.txt: -------------------------------------------------------------------------------- 1 | # Pass all .php files onto a php-fpm/php-fcgi server. 2 | location ~ \.php$ { 3 | # Zero-day exploit defense. 4 | # http://forum.nginx.org/read.php?2,88845,page=3 5 | # Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi. 6 | # Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine. And then cross your fingers that you won't get hacked. 7 | try_files $uri =404; 8 | 9 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 10 | include fastcgi_params; 11 | fastcgi_index index.php; 12 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 13 | # fastcgi_intercept_errors on; 14 | fastcgi_pass php; 15 | fastcgi_buffers 32 4k; 16 | } 17 | -------------------------------------------------------------------------------- /scripts/files/nginx_global_php-proxy.txt: -------------------------------------------------------------------------------- 1 | # For proxying PHP requests to a localhost Apache server. 2 | # Useful for getting the benefits of Apache + PHP as a module w/ Nginx as a frontend for delivering static content. 3 | 4 | location ~ \.php$ { 5 | proxy_pass http://127.0.0.1:8080; 6 | proxy_http_version 1.1; 7 | proxy_set_header Host $http_host; 8 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 9 | proxy_set_header X-Forwarded-Proto $scheme; 10 | proxy_connect_timeout 1800; 11 | proxy_send_timeout 1800; 12 | proxy_read_timeout 1800; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/files/nginx_global_restrictions.txt: -------------------------------------------------------------------------------- 1 | # Designed to be included in any server {} block. 2 | 3 | # Two files that receive a ton of requests. There's no need to log the requests for these files. 4 | location = /favicon.ico { 5 | log_not_found off; 6 | access_log off; 7 | } 8 | 9 | location = /robots.txt { 10 | allow all; 11 | log_not_found off; 12 | access_log off; 13 | } 14 | 15 | # Deny all attempts to access hidden files such as .git, .htaccess, .htpasswd, .DS_Store (Mac). 16 | location ~ /\. { 17 | deny all; 18 | access_log off; 19 | log_not_found off; 20 | } 21 | 22 | # Except allow /.well-known/ which is used by services like Let's Encrypt. 23 | location ^~ /.well-known/ { 24 | allow all; 25 | alias /var/www/shared/.well-known/; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/files/nginx_site.txt: -------------------------------------------------------------------------------- 1 | # To redirect HTTP to HTTPS, uncomment this server section and comment/remove the other server's "listen 80" lines. 2 | #server { 3 | # listen 80; 4 | # listen [::]:80; 5 | # server_name domain.com; 6 | # 7 | # rewrite ^ https://domain.com$request_uri permanent; 8 | #} 9 | 10 | server { 11 | listen 80; 12 | listen [::]:80; 13 | # listen 443 ssl http2; 14 | # listen [::]:443 ssl http2; 15 | server_name domain.com; 16 | root /var/www/domain.com/public_html; 17 | 18 | # ssl_certificate /path/to/domain.com/fullchain.pem; 19 | # ssl_certificate_key /path/to/domain.com/privkey.pem; 20 | # ssl_trusted_certificate /path/to/domain.com/chain.pem; 21 | 22 | include /etc/nginx/global/restrictions.conf; 23 | 24 | # Default location handler. 25 | include /etc/nginx/global/location-default.conf; 26 | # include /etc/nginx/global/location-cms.conf; 27 | 28 | # Additional rules go here. 29 | # error_page 400 404 https://domain.com/; 30 | 31 | include /etc/nginx/global/location-drc.conf; 32 | 33 | include /etc/nginx/global/php-local.conf; 34 | } 35 | -------------------------------------------------------------------------------- /scripts/files/nginx_site_default.txt: -------------------------------------------------------------------------------- 1 | # The default server for whenever a Host header is not supplied. 2 | 3 | server { 4 | listen 80 default_server; 5 | listen [::]:80 default_server; 6 | # listen 443 ssl http2 default_server; 7 | # listen [::]:443 ssl http2 default_server; 8 | # server_name domain.com; 9 | root /var/www/default/public_html; 10 | 11 | # ssl_certificate /path/to/domain.com/fullchain.pem; 12 | # ssl_certificate_key /path/to/domain.com/privkey.pem; 13 | # ssl_trusted_certificate /path/to/domain.com/chain.pem; 14 | 15 | include /etc/nginx/global/restrictions.conf; 16 | 17 | # Default location handler. 18 | include /etc/nginx/global/location-default.conf; 19 | # include /etc/nginx/global/location-cms.conf; 20 | 21 | # Additional rules go here. 22 | # error_page 400 404 https://domain.com/; 23 | 24 | include /etc/nginx/global/php-local.conf; 25 | } 26 | -------------------------------------------------------------------------------- /scripts/files/opendkim_trusted_hosts.txt: -------------------------------------------------------------------------------- 1 | 127.0.0.0/8 2 | [::ffff:127.0.0.0]/104 3 | [::1]/128 4 | localhost 5 | 6 | -------------------------------------------------------------------------------- /scripts/functions.php: -------------------------------------------------------------------------------- 1 | $line) 31 | { 32 | $line = trim($line); 33 | if ($uncomment !== false && substr($line, 0, strlen($uncomment)) === $uncomment) $uline = trim(substr($line, strlen($uncomment))); 34 | else $uline = false; 35 | 36 | if (trim($separator) !== "") 37 | { 38 | $linekey = false; 39 | if ($uline !== false) 40 | { 41 | $pos = strpos($uline, trim($separator)); 42 | if ($pos !== false) $linekey = trim(substr($uline, 0, $pos)); 43 | } 44 | else 45 | { 46 | $pos = strpos($line, trim($separator)); 47 | if ($pos !== false) $linekey = trim(substr($line, 0, $pos)); 48 | } 49 | 50 | if ($linekey !== false && isset($datamap[$linekey])) 51 | { 52 | $val = $datamap[$linekey]; 53 | 54 | if (is_callable($callback)) 55 | { 56 | $lines[$num] = call_user_func_array($callback, array(($uline !== false ? $uline : $line), &$datamap, $linekey, $separator, $val)); 57 | } 58 | else if ($val === false) $lines[$num] = ""; 59 | else 60 | { 61 | $lines[$num] = rtrim($linekey . $separator . $val); 62 | 63 | $datamap[$linekey] = false; 64 | } 65 | } 66 | } 67 | else 68 | { 69 | foreach ($datamap as $key => $val) 70 | { 71 | if (substr($line, 0, strlen($key)) === $key || ($uline !== false && substr($uline, 0, strlen($key)) === $key)) 72 | { 73 | if (is_callable($callback)) 74 | { 75 | $lines[$num] = call_user_func_array($callback, array(($uline !== false ? $uline : $line), &$datamap, $key, $separator, $val)); 76 | } 77 | else if ($val === false) $lines[$num] = ""; 78 | else 79 | { 80 | $lines[$num] = rtrim($key . $separator . $val); 81 | 82 | $datamap[$key] = false; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | if (count($lines) && trim($lines[count($lines) - 1]) !== "") $lines[] = ""; 90 | 91 | if (is_callable($callback)) call_user_func_array($callback, array(false, &$datamap, false, false, false)); 92 | 93 | foreach ($datamap as $key => $val) 94 | { 95 | if ($val !== false) $lines[] = rtrim($key . $separator . $val); 96 | } 97 | 98 | return $lines; 99 | } 100 | 101 | // Intakes a series of lines and performs a series of regular expression matches. Returns a new set of lines. 102 | function UpdateConfFileRegEx($lines, $datamap, $addunused = true) 103 | { 104 | foreach ($lines as $num => $line) 105 | { 106 | $line = trim($line); 107 | 108 | foreach ($datamap as $key => $val) 109 | { 110 | if (preg_match($key, $line)) 111 | { 112 | if ($val === false) $lines[$num] = ""; 113 | else 114 | { 115 | $lines[$num] = $val; 116 | 117 | $datamap[$key] = false; 118 | } 119 | } 120 | } 121 | } 122 | 123 | if (count($lines) && trim($lines[count($lines) - 1]) !== "") $lines[] = ""; 124 | 125 | if ($addunused) 126 | { 127 | foreach ($datamap as $key => $val) 128 | { 129 | if ($val !== false) $lines[] = $val; 130 | } 131 | } 132 | 133 | return $lines; 134 | } 135 | 136 | // Creates a new password. 137 | function MakePassword() 138 | { 139 | $rootpath = str_replace("\\", "/", dirname(__FILE__)); 140 | 141 | if (!class_exists("CSPRNG", false)) require_once $rootpath . "/../support/random.php"; 142 | 143 | $rng = new CSPRNG(); 144 | $freqmap = json_decode(file_get_contents($rootpath . "/../support/en_us_lite.json"), true); 145 | 146 | $words = array(); 147 | for ($x = 0; $x < 3; $x++) $words[] = preg_replace('/[^a-z]/', "-", strtolower($rng->GenerateWordLite($freqmap, $rng->GetInt(4, 8)))); 148 | 149 | return implode("-", $words); 150 | } 151 | 152 | // Runs an executable and waits while dumping output to stdout. 153 | function RunExecutable($cmd) 154 | { 155 | $rootpath = str_replace("\\", "/", dirname(__FILE__)); 156 | 157 | if (!class_exists("ProcessHelper", false)) require_once $rootpath . "/../support/process_helper.php"; 158 | 159 | $procresult = ProcessHelper::StartProcess($cmd); 160 | if (!$procresult["success"]) CLI::DisplayError("Failed to start '" . $cmd . "'.", $procresult); 161 | 162 | ProcessHelper::Wait($procresult["proc"], $procresult["pipes"], "", -1, true); 163 | } 164 | ?> -------------------------------------------------------------------------------- /scripts/init_system.php: -------------------------------------------------------------------------------- 1 | "600", 22 | "net.ipv4.conf.default.rp_filter" => "1", 23 | "net.ipv4.conf.all.rp_filter" => "1", 24 | "net.ipv4.tcp_syncookies" => "1", 25 | "net.ipv4.icmp_echo_ignore_broadcasts" => "1", 26 | "net.ipv4.conf.all.accept_redirects" => "0", 27 | "net.ipv6.conf.all.accept_redirects" => "0", 28 | "net.ipv4.conf.all.secure_redirects" => "1", 29 | "net.ipv4.conf.all.send_redirects" => "0", 30 | "net.ipv4.conf.all.accept_source_route" => "0", 31 | "net.ipv6.conf.all.accept_source_route" => "0", 32 | ); 33 | 34 | $lines = explode("\n", trim(file_get_contents("/etc/sysctl.conf"))); 35 | $lines = UpdateConfFile($lines, $datamap, " = "); 36 | file_put_contents("/etc/sysctl.conf", implode("\n", $lines) . "\n"); 37 | 38 | 39 | // Set iptables rules via external files. 40 | if (file_exists($rootpath2 . "/files/firewall_ipv4.txt") && file_exists("/etc/iptables/rules.v4")) 41 | { 42 | file_put_contents("/etc/iptables/rules.v4", file_get_contents($rootpath2 . "/files/firewall_ipv4.txt")); 43 | 44 | system("iptables-restore < /etc/iptables/rules.v4"); 45 | } 46 | 47 | if (file_exists($rootpath2 . "/files/firewall_ipv6.txt") && file_exists("/etc/iptables/rules.v6")) 48 | { 49 | file_put_contents("/etc/iptables/rules.v6", file_get_contents($rootpath2 . "/files/firewall_ipv6.txt")); 50 | 51 | system("iptables-restore < /etc/iptables/rules.v6"); 52 | } 53 | 54 | 55 | // Set OS file handle limits. 56 | // Source: https://superuser.com/questions/1200539/cannot-increase-open-file-limit-past-4096-ubuntu 57 | if (!file_exists("/etc/security/limits.conf")) CLI::DisplayError("The file '/etc/security/limits.conf' does not exist. Is this a Linux system?"); 58 | 59 | $datamap = array( 60 | "root soft nofile" => SYSTEM_FILES, 61 | "root hard nofile" => SYSTEM_FILES, 62 | ); 63 | 64 | $lines = explode("\n", trim(file_get_contents("/etc/security/limits.conf"))); 65 | $lines = UpdateConfFile($lines, $datamap, " "); 66 | file_put_contents("/etc/security/limits.conf", implode("\n", $lines) . "\n"); 67 | 68 | // Handle systemd. 69 | $datamap = array( 70 | "DefaultLimitNOFILE" => SYSTEM_FILES, 71 | ); 72 | 73 | if (file_exists("/etc/systemd/system.conf")) 74 | { 75 | $lines = explode("\n", trim(file_get_contents("/etc/systemd/system.conf"))); 76 | $lines = UpdateConfFile($lines, $datamap, "="); 77 | file_put_contents("/etc/systemd/system.conf", implode("\n", $lines) . "\n"); 78 | } 79 | 80 | if (file_exists("/etc/systemd/user.conf")) 81 | { 82 | $lines = explode("\n", trim(file_get_contents("/etc/systemd/user.conf"))); 83 | $lines = UpdateConfFile($lines, $datamap, "="); 84 | file_put_contents("/etc/systemd/user.conf", implode("\n", $lines) . "\n"); 85 | } 86 | 87 | if (file_exists("/etc/systemd/system.conf") || file_exists("/etc/systemd/user.conf")) @system("systemctl daemon-reexec"); 88 | 89 | // Update PAM. 90 | if (!file_exists("/etc/pam.d/common-session")) CLI::DisplayError("The file '/etc/pam.d/common-session' does not exist. Is this a Linux system?"); 91 | if (!file_exists("/etc/pam.d/common-session-noninteractive")) CLI::DisplayError("The file '/etc/pam.d/common-session-noninteractive' does not exist. Is this a Linux system?"); 92 | 93 | $datamap = array( 94 | '/session\s+required\s+pam_limits\.so/' => "session required pam_limits.so", 95 | ); 96 | 97 | $lines = explode("\n", trim(file_get_contents("/etc/pam.d/common-session"))); 98 | $lines = UpdateConfFileRegEx($lines, $datamap); 99 | file_put_contents("/etc/pam.d/common-session", implode("\n", $lines) . "\n"); 100 | 101 | $lines = explode("\n", trim(file_get_contents("/etc/pam.d/common-session-noninteractive"))); 102 | $lines = UpdateConfFileRegEx($lines, $datamap); 103 | file_put_contents("/etc/pam.d/common-session-noninteractive", implode("\n", $lines) . "\n"); 104 | 105 | 106 | // Set the system timezone. 107 | @system("timedatectl set-timezone " . escapeshellarg(date_default_timezone_get())); 108 | 109 | 110 | // Create a group for SFTP users. 111 | @system("addgroup sftp-users"); 112 | 113 | // Create a baseline scripts run directory. 114 | @mkdir("/var/scripts"); 115 | @chgrp("/var/scripts", "sftp-users"); 116 | @chmod("/var/scripts", 02770); 117 | 118 | // Set up the apt auto-updater script. 119 | if (!file_exists("/var/scripts/apt_updater.php")) 120 | { 121 | file_put_contents("/var/scripts/apt_updater.php", file_get_contents($rootpath2 . "/files/apt_updater.php")); 122 | @chgrp("/var/scripts/apt_updater.php", "sftp-users"); 123 | @chmod("/var/scripts/apt_updater.php", 0660); 124 | } 125 | 126 | $ts = time() - 60; 127 | $filename = "/root/crontab_" . time() . ".txt"; 128 | @system("crontab -l > " . escapeshellarg($filename)); 129 | $data = trim(file_get_contents($filename)); 130 | if (strpos($data, "/var/scripts/apt_updater.php") === false) 131 | { 132 | $data .= "\n\n"; 133 | $data .= "# Automatic updater.\n"; 134 | $data .= date("i H", $ts) . " * * * /usr/bin/php /var/scripts/apt_updater.php >/tmp/cron_apt_updater.log 2>&1\n"; 135 | 136 | $data = trim($data) . "\n"; 137 | file_put_contents($filename, $data); 138 | @system("crontab " . escapeshellarg($filename)); 139 | } 140 | 141 | @unlink($filename); 142 | 143 | 144 | // Change the hostname. 145 | $hostname = trim((string)getenv("INSTANT_HOSTNAME")); 146 | if ($hostname !== "") 147 | { 148 | if (!file_exists("/etc/hostname")) CLI::DisplayError("The file '/etc/hostname' does not exist. Is this a Linux system?"); 149 | if (!file_exists("/etc/hosts")) CLI::DisplayError("The file '/etc/hosts' does not exist. Is this a Linux system?"); 150 | 151 | $prevhostname = trim(@system("hostname")); 152 | if ($prevhostname !== $hostname) 153 | { 154 | file_put_contents("/etc/hostname", $hostname); 155 | 156 | $lines = explode("\n", trim(file_get_contents("/etc/hosts"))); 157 | foreach ($lines as $num => $line) 158 | { 159 | if (substr($line, 0, 4) === "127.") $lines[$num] = str_replace($prevhostname, $hostname, $line); 160 | } 161 | 162 | file_put_contents("/etc/hosts", implode("\n", $lines) . "\n"); 163 | 164 | // Update cloud-init configuration (if it exists). 165 | if (file_exists("/etc/cloud/cloud.cfg")) 166 | { 167 | $datamap = array( 168 | '/preserve_hostname:\s*false/' => "preserve_hostname: true", 169 | ); 170 | 171 | $lines = explode("\n", trim(file_get_contents("/etc/cloud/cloud.cfg"))); 172 | $lines = UpdateConfFileRegEx($lines, $datamap); 173 | file_put_contents("/etc/cloud/cloud.cfg", implode("\n", $lines) . "\n"); 174 | } 175 | 176 | // Force a reboot. 177 | @touch("/var/run/reboot-required"); 178 | } 179 | } 180 | ?> -------------------------------------------------------------------------------- /scripts/main.php: -------------------------------------------------------------------------------- 1 | array()); 62 | $ipaddr = trim((string)getenv("PUBLIC_IPV4")); 63 | if ($ipaddr !== "") $installconfig["ipv4"] = $ipaddr; 64 | $ipaddr = trim((string)getenv("PUBLIC_IPV6")); 65 | if ($ipaddr !== "") $installconfig["ipv6"] = $ipaddr; 66 | file_put_contents($rootpath . "/../config.dat", json_encode($installconfig, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); 67 | @chmod($rootpath . "/../config.dat", 0600); 68 | 69 | // Generate a README file for the root directory. 70 | $data = "Server Instant Start completed successfully.\n\n"; 71 | $data .= "Configuration from '" . realpath($rootpath . "/../config.dat") . "':\n\n"; 72 | $data .= json_encode($installconfig, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); 73 | 74 | file_put_contents("/root/README-ServerInstantStart", $data); 75 | @chmod("/root/README-ServerInstantStart", 0600); 76 | 77 | echo "-------------\n" . $data . "\n-------------\n\n"; 78 | ?> -------------------------------------------------------------------------------- /scripts/post_setup_dkim.php: -------------------------------------------------------------------------------- 1 | " inet:8892@localhost", 28 | "Canonicalization" => " relaxed/simple", 29 | "Mode" => " sv", 30 | "SubDomains" => " no", 31 | "AutoRestart" => " yes", 32 | "AutoRestartRate" => " 10/1M", 33 | "Background" => " yes", 34 | "DNSTimeout" => " 5", 35 | "SignatureAlgorithm" => " rsa-sha256", 36 | "ExternalIgnoreList" => " /etc/opendkim/trusted.hosts", 37 | "InternalHosts" => " /etc/opendkim/trusted.hosts", 38 | "SigningTable" => " refile:/etc/opendkim/signing.table", 39 | "KeyTable" => " /etc/opendkim/key.table", 40 | ); 41 | 42 | $lines = explode("\n", trim(file_get_contents("/etc/opendkim.conf"))); 43 | $lines = UpdateConfFile($lines, $datamap, " ", "#"); 44 | file_put_contents("/etc/opendkim.conf", trim(implode("\n", $lines)) . "\n"); 45 | 46 | @mkdir("/etc/opendkim", 0755, true); 47 | @chmod("/etc/opendkim", 0755); 48 | @chown("/etc/opendkim", "opendkim"); 49 | @chgrp("/etc/opendkim", "opendkim"); 50 | 51 | @mkdir("/etc/opendkim/keys", 0700, true); 52 | @chmod("/etc/opendkim/keys", 0700); 53 | @chown("/etc/opendkim/keys", "opendkim"); 54 | @chgrp("/etc/opendkim/keys", "opendkim"); 55 | 56 | if (!file_exists("/etc/opendkim/trusted.hosts")) 57 | { 58 | $template = file_get_contents($rootpath2 . "/files/opendkim_trusted_hosts.txt"); 59 | 60 | $installconfig = @json_decode(file_get_contents($rootpath2 . "/../config.dat"), true); 61 | if (isset($installconfig["ipv4"]) && $installconfig["ipv4"] !== "") $template .= $installconfig["ipv4"] . "\n"; 62 | if (isset($installconfig["ipv6"]) && $installconfig["ipv6"] !== "") $template .= $installconfig["ipv6"] . "\n"; 63 | 64 | file_put_contents("/etc/opendkim/trusted.hosts", $template); 65 | @chmod("/etc/opendkim/trusted.hosts", 0644); 66 | 67 | // Configure Postfix integration. 68 | if (file_exists("/etc/postfix/main.cf")) 69 | { 70 | // NOTE: This might overwrite existing milters. 71 | $datamap = array( 72 | "smtpd_milters" => "inet:localhost:8892", 73 | "non_smtpd_milters" => "inet:localhost:8892" 74 | ); 75 | 76 | $lines = explode("\n", trim(file_get_contents("/etc/postfix/main.cf"))); 77 | $lines = UpdateConfFile($lines, $datamap, " = ", "#"); 78 | file_put_contents("/etc/postfix/main.cf", trim(implode("\n", $lines)) . "\n"); 79 | 80 | @system("service postfix reload"); 81 | } 82 | } 83 | 84 | if (!file_exists("/etc/opendkim/signing.table")) 85 | { 86 | file_put_contents("/etc/opendkim/signing.table", ""); 87 | @chmod("/etc/opendkim/signing.table", 0644); 88 | } 89 | 90 | if (!file_exists("/etc/opendkim/key.table")) 91 | { 92 | file_put_contents("/etc/opendkim/key.table", ""); 93 | @chmod("/etc/opendkim/key.table", 0644); 94 | } 95 | 96 | // Get the command. 97 | $cmds = array( 98 | "none" => "Do nothing besides install and configure OpenDKIM + postfix", 99 | "create" => "Create a DKIM signing key for an email domain", 100 | "verify" => "Verify that a DKIM signing key is properly published in DNS" 101 | ); 102 | 103 | $cmd = CLI::GetLimitedUserInputWithArgs($args, false, "Command", false, "Available commands:", $cmds, true, $suppressoutput); 104 | 105 | if ($cmd === "create") 106 | { 107 | $selector = CLI::GetUserInputWithArgs($args, false, "Selector", "default", "", $suppressoutput); 108 | $domain = CLI::GetUserInputWithArgs($args, false, "Email domain", false, "", $suppressoutput); 109 | 110 | @mkdir("/etc/opendkim/keys/" . $domain); 111 | @system("opendkim-genkey -b 2048 -d " . escapeshellarg($domain) . " -D " . escapeshellarg("/etc/opendkim/keys/" . $domain) . " -s " . escapeshellarg($selector) . " -v"); 112 | if (!file_exists("/etc/opendkim/keys/" . $domain . "/" . $selector . ".private")) CLI::DisplayError("An error occurred while creating the DKIM signing key."); 113 | @chmod("/etc/opendkim/keys/" . $domain . "/" . $selector . ".private", 0640); 114 | @chgrp("/etc/opendkim/keys/" . $domain . "/" . $selector . ".private", "opendkim"); 115 | 116 | // Update the signing and key tables. 117 | $datamap = array( 118 | "*@" . $domain => " " . $selector . "._domainkey." . $domain 119 | ); 120 | 121 | $lines = explode("\n", trim(file_get_contents("/etc/opendkim/signing.table"))); 122 | $lines = UpdateConfFile($lines, $datamap, " ", "#"); 123 | file_put_contents("/etc/opendkim/signing.table", trim(implode("\n", $lines)) . "\n"); 124 | 125 | $datamap = array( 126 | $selector . "._domainkey." . $domain => " " . $domain . ":" . $selector . ":/etc/opendkim/keys/" . $domain . "/" . $selector . ".private" 127 | ); 128 | 129 | $lines = explode("\n", trim(file_get_contents("/etc/opendkim/key.table"))); 130 | $lines = UpdateConfFile($lines, $datamap, " ", "#"); 131 | file_put_contents("/etc/opendkim/key.table", trim(implode("\n", $lines)) . "\n"); 132 | 133 | @system("service opendkim reload"); 134 | 135 | echo "\n"; 136 | echo "Publish a TXT record for " . $domain . " as follows in DNS:\n\n"; 137 | 138 | echo file_get_contents("/etc/opendkim/keys/" . $domain . "/" . $selector . ".txt") . "\n"; 139 | } 140 | else if ($cmd === "verify") 141 | { 142 | $selector = CLI::GetUserInputWithArgs($args, false, "Selector", "default", "", $suppressoutput); 143 | $domain = CLI::GetUserInputWithArgs($args, false, "Email domain", false, "", $suppressoutput); 144 | 145 | @system("opendkim-testkey -d " . escapeshellarg($domain) . " -s " . escapeshellarg($selector) . " -vvv"); 146 | } 147 | ?> -------------------------------------------------------------------------------- /scripts/post_setup_https.php: -------------------------------------------------------------------------------- 1 | "\"/var/www/shared/.well-known/acme-challenge\"" 30 | ); 31 | 32 | $lines = explode("\n", trim(file_get_contents("/etc/dehydrated/config"))); 33 | $lines = UpdateConfFile($lines, $datamap, "=", "#"); 34 | file_put_contents("/etc/dehydrated/config", implode("\n", $lines) . "\n"); 35 | 36 | if (!file_exists("/etc/dehydrated/domains.txt")) file_put_contents("/etc/dehydrated/domains.txt", ""); 37 | @chmod("/etc/dehydrated/domains.txt", 0640); 38 | 39 | // Set up dehydrated to auto-renew certificates. 40 | $ts = time() - 60; 41 | $filename = "/root/crontab_" . time() . ".txt"; 42 | @system("crontab -l > " . escapeshellarg($filename)); 43 | $data = trim(file_get_contents($filename)); 44 | if (strpos($data, "/var/scripts/dehydrated/dehydrated") === false) 45 | { 46 | $data .= "\n\n"; 47 | $data .= "# Automatic certificate renewal.\n"; 48 | $data .= date("i H", $ts) . " * * * /usr/bin/timeout 10m /var/scripts/dehydrated/dehydrated --cron >/tmp/cron_dehydrated_renew.log 2>&1\n"; 49 | 50 | $data = trim($data) . "\n"; 51 | file_put_contents($filename, $data); 52 | @system("crontab " . escapeshellarg($filename)); 53 | } 54 | 55 | @unlink($filename); 56 | 57 | @system("/var/scripts/dehydrated/dehydrated --register --accept-terms"); 58 | 59 | // Get the command. 60 | $cmds = array( 61 | "none" => "Do nothing besides install/update dehydrated and register an account", 62 | "certonly" => "Get a certificate but configure your web server manually later", 63 | "nginx" => "Get a certificate and attempt to auto-configure Nginx" 64 | ); 65 | 66 | $cmd = CLI::GetLimitedUserInputWithArgs($args, false, "Command", false, "Available commands:", $cmds, true, $suppressoutput); 67 | 68 | if ($cmd !== "none") 69 | { 70 | if (count($args["params"])) $domains = $args["params"]; 71 | else 72 | { 73 | $domains = CLI::GetUserInputWithArgs($args, false, "Domains", false, "Enter one or more space/comma-separated domains to associate with this certificate. The first domain will be used for the name of the certificate on disk and used for locating any auto-configuration files.", $suppressoutput); 74 | $domains = explode(" ", preg_replace('/\s+/', " ", trim(str_replace(array(",", ";"), " ", $domains)))); 75 | } 76 | 77 | $certname = array_shift($domains); 78 | 79 | // Update domains.txt. 80 | $datamap = array( 81 | $certname => implode(" ", $domains) 82 | ); 83 | 84 | $lines = explode("\n", trim(file_get_contents("/etc/dehydrated/domains.txt"))); 85 | $lines = UpdateConfFile($lines, $datamap, " ", "#"); 86 | file_put_contents("/etc/dehydrated/domains.txt", trim(implode("\n", $lines)) . "\n"); 87 | 88 | @system("/var/scripts/dehydrated/dehydrated --cron"); 89 | 90 | // Attempt to update Nginx config for the first domain. 91 | if ($cmd === "nginx" && file_exists("/etc/dehydrated/certs/" . $certname . "/fullchain.pem") && file_exists("/etc/nginx/sites-available/" . $certname . ".conf")) 92 | { 93 | $datamap = array( 94 | '/listen 443 ssl http2;/' => " listen 443 ssl http2;", 95 | '/listen \[::\]:443 ssl http2;/' => " listen [::]:443 ssl http2;", 96 | '/ssl_certificate\s+/' => " ssl_certificate /etc/dehydrated/certs/" . $certname . "/fullchain.pem;", 97 | '/ssl_certificate_key\s+/' => " ssl_certificate_key /etc/dehydrated/certs/" . $certname . "/privkey.pem;", 98 | '/ssl_trusted_certificate\s+/' => " ssl_trusted_certificate /etc/dehydrated/certs/" . $certname . "/chain.pem;", 99 | ); 100 | 101 | $lines = explode("\n", trim(file_get_contents("/etc/nginx/sites-available/" . $certname . ".conf"))); 102 | $lines = UpdateConfFileRegEx($lines, $datamap, false); 103 | file_put_contents("/etc/nginx/sites-available/" . $certname . ".conf", trim(implode("\n", $lines)) . "\n"); 104 | 105 | @system("service nginx reload"); 106 | } 107 | } 108 | ?> -------------------------------------------------------------------------------- /scripts/setup_db_mariadb_configure.php: -------------------------------------------------------------------------------- 1 | false, 32 | "stdout" => false, 33 | "stderr" => false 34 | ); 35 | 36 | $procresult = ProcessHelper::StartProcess($cmd, $options); 37 | if (!$procresult["success"]) CLI::DisplayError("Unable to start MariaDB server.", $procresult); 38 | 39 | // Wait for the UNIX socket to come up. 40 | echo "Waiting for MariaDB UNIX socket..."; 41 | fflush(STDOUT); 42 | $retries = 30; 43 | do 44 | { 45 | sleep(1); 46 | echo "."; 47 | fflush(STDOUT); 48 | 49 | $found = false; 50 | 51 | if (file_exists("/var/run/mysqld/mysqld.sock")) $found = true; 52 | 53 | $retries--; 54 | } while ($retries && !$found); 55 | 56 | $failedmsg = false; 57 | 58 | if (!$retries) 59 | { 60 | echo "FAILED.\n"; 61 | 62 | $failedmsg = "MariaDB failed to start within 30 seconds."; 63 | } 64 | else 65 | { 66 | sleep(1); 67 | echo "SUCCESS.\n"; 68 | 69 | // Generate a password for the root user. 70 | $installconfig = @json_decode(file_get_contents($rootpath2 . "/../config.dat"), true); 71 | if (!is_array($installconfig)) $installconfig = array("accounts" => array()); 72 | if (!isset($installconfig["accounts"]["mariadb"])) $installconfig["accounts"]["mariadb"] = array("root" => MakePassword()); 73 | file_put_contents($rootpath2 . "/../config.dat", json_encode($installconfig, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); 74 | @chmod($rootpath2 . "/../config.dat", 0600); 75 | 76 | // Try to connect to the database. 77 | require_once $rootpath2 . "/../support/db.php"; 78 | require_once $rootpath2 . "/../support/db_mysql_lite.php"; 79 | 80 | try 81 | { 82 | $db = new CSDB_mysql_lite(); 83 | 84 | $db->SetDebug(true); 85 | 86 | $db->Connect("mysql:unix_socket"); 87 | 88 | // Set the root password, secure the root account, and commit privilege changes. 89 | echo "Setting MariaDB root password, securing the root account, and committing privilege changes...\n"; 90 | $db->Query(false, "FLUSH PRIVILEGES"); 91 | $db->Query(false, "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password AS PASSWORD(?)", $installconfig["accounts"]["mariadb"]["root"]); 92 | $db->Query(false, "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"); 93 | $db->Query(false, "DELETE FROM mysql.user WHERE User=''"); 94 | $db->Query(false, "FLUSH PRIVILEGES"); 95 | 96 | $db->Disconnect(); 97 | } 98 | catch (Exception $e) 99 | { 100 | CLI::DisplayError("An error occurred while attempting to reset the root password. " . $e->getMessage(), false, false); 101 | 102 | $failedmsg = "The MariaDB connection or a query failed."; 103 | } 104 | } 105 | 106 | // Stop the privilege-less MariaDB server. 107 | echo "Terminating child MariaDB process..."; 108 | fflush(STDOUT); 109 | ProcessHelper::TerminateProcess($procresult["pid"], true, false); 110 | 111 | do 112 | { 113 | sleep(1); 114 | echo "."; 115 | fflush(STDOUT); 116 | 117 | $pinfo = @proc_get_status($procresult["proc"]); 118 | } while ($pinfo["running"]); 119 | 120 | echo "Done.\n"; 121 | 122 | if ($failedmsg !== false) CLI::DisplayError($failedmsg); 123 | 124 | // Start the MariaDB server normally. 125 | @system("service mysql start"); 126 | 127 | exit(); 128 | ?> -------------------------------------------------------------------------------- /scripts/setup_db_mariadb_install.php: -------------------------------------------------------------------------------- 1 | "", 32 | "deb-src http://sfo1.mirrors.digitalocean.com/mariadb/repo/" . $ver . "/ubuntu " . $osinfo["VERSION_CODENAME"] . " main" => "" 33 | ); 34 | 35 | if (!file_exists("/etc/apt/sources.list")) CLI::DisplayError("The file '/etc/apt/sources.list' does not exist. Not actually Debian-based?"); 36 | $lines = explode("\n", trim(file_get_contents("/etc/apt/sources.list"))); 37 | $lines = UpdateConfFile($lines, $datamap, ""); 38 | file_put_contents("/etc/apt/sources.list", implode("\n", $lines) . "\n"); 39 | 40 | @system("apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'"); 41 | 42 | @system("/usr/bin/apt-get update"); 43 | @system("/usr/bin/apt-get -y install mariadb-server php-mysql"); 44 | 45 | // Run the main script again to configure MariaDB. 46 | // A separate configuration step is necessary since php-mysql was just installed but is not available to the currently running process. 47 | $cmd = escapeshellarg(PHP_BINARY) . " " . escapeshellarg($rootpath . "/main.php") . " mariadb-configure"; 48 | 49 | RunExecutable($cmd); 50 | ?> -------------------------------------------------------------------------------- /scripts/setup_db_mysql_configure.php: -------------------------------------------------------------------------------- 1 | false, 32 | "stdout" => false, 33 | "stderr" => false 34 | ); 35 | 36 | $procresult = ProcessHelper::StartProcess($cmd, $options); 37 | if (!$procresult["success"]) CLI::DisplayError("Unable to start MySQL server.", $procresult); 38 | 39 | // Wait for the UNIX socket to come up. 40 | echo "Waiting for MySQL UNIX socket..."; 41 | fflush(STDOUT); 42 | $retries = 30; 43 | do 44 | { 45 | sleep(1); 46 | echo "."; 47 | fflush(STDOUT); 48 | 49 | $found = false; 50 | 51 | if (file_exists("/var/run/mysqld/mysqld.sock")) $found = true; 52 | 53 | $retries--; 54 | } while ($retries && !$found); 55 | 56 | $failedmsg = false; 57 | 58 | if (!$retries) 59 | { 60 | echo "FAILED.\n"; 61 | 62 | $failedmsg = "MySQL failed to start within 30 seconds."; 63 | } 64 | else 65 | { 66 | sleep(1); 67 | echo "SUCCESS.\n"; 68 | 69 | // Generate a password for the root user. 70 | $installconfig = @json_decode(file_get_contents($rootpath2 . "/../config.dat"), true); 71 | if (!is_array($installconfig)) $installconfig = array("accounts" => array()); 72 | if (!isset($installconfig["accounts"]["mysql"])) $installconfig["accounts"]["mysql"] = array("root" => MakePassword()); 73 | file_put_contents($rootpath2 . "/../config.dat", json_encode($installconfig, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); 74 | @chmod($rootpath2 . "/../config.dat", 0600); 75 | 76 | // Try to connect to the database. 77 | require_once $rootpath2 . "/../support/db.php"; 78 | require_once $rootpath2 . "/../support/db_mysql_lite.php"; 79 | 80 | try 81 | { 82 | $db = new CSDB_mysql_lite(); 83 | 84 | $db->SetDebug(true); 85 | 86 | $db->Connect("mysql:unix_socket"); 87 | 88 | // Set the root password, secure the root account, and commit privilege changes. 89 | echo "Setting MySQL root password, securing the root account, and committing privilege changes...\n"; 90 | $db->Query(false, "FLUSH PRIVILEGES"); 91 | $db->Query(false, "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ?", $installconfig["accounts"]["mysql"]["root"]); 92 | $db->Query(false, "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"); 93 | $db->Query(false, "DELETE FROM mysql.user WHERE User=''"); 94 | $db->Query(false, "FLUSH PRIVILEGES"); 95 | 96 | $db->Disconnect(); 97 | } 98 | catch (Exception $e) 99 | { 100 | CLI::DisplayError("An error occurred while attempting to reset the root password. " . $e->getMessage(), false, false); 101 | 102 | $failedmsg = "The MySQL connection or a query failed."; 103 | } 104 | } 105 | 106 | // Stop the privilege-less MySQL server. 107 | echo "Terminating child MySQL process..."; 108 | fflush(STDOUT); 109 | ProcessHelper::TerminateProcess($procresult["pid"], true, false); 110 | 111 | do 112 | { 113 | sleep(1); 114 | echo "."; 115 | fflush(STDOUT); 116 | 117 | $pinfo = @proc_get_status($procresult["proc"]); 118 | } while ($pinfo["running"]); 119 | 120 | echo "Done.\n"; 121 | 122 | if ($failedmsg !== false) CLI::DisplayError($failedmsg); 123 | 124 | // Start the MySQL server normally. 125 | @system("service mysql start"); 126 | 127 | exit(); 128 | ?> -------------------------------------------------------------------------------- /scripts/setup_db_mysql_install.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/setup_db_postgresql.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/setup_email_sendonly.php: -------------------------------------------------------------------------------- 1 | trim((string)getenv("INSTANT_EMAIL_DOMAIN")), 25 | "myorigin" => "/etc/mailname", 26 | "smtpd_recipient_restrictions" => "reject_unknown_sender_domain, reject_unknown_recipient_domain, reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination", 27 | "smtpd_sender_restrictions" => "reject_unknown_sender_domain", 28 | "smtpd_relay_restrictions" => "reject_unknown_sender_domain, reject_unknown_recipient_domain, reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination", 29 | ); 30 | 31 | $lines = explode("\n", trim(file_get_contents("/etc/postfix/main.cf"))); 32 | $lines = UpdateConfFile($lines, $datamap, " = ", "#"); 33 | file_put_contents("/etc/postfix/main.cf", implode("\n", $lines) . "\n"); 34 | 35 | // Start the service. 36 | @system("service postfix restart"); 37 | ?> -------------------------------------------------------------------------------- /scripts/setup_nginx.php: -------------------------------------------------------------------------------- 1 | "", 21 | ); 22 | 23 | $lines = (file_exists("/etc/cron.deny") ? explode("\n", trim(file_get_contents("/etc/cron.deny"))) : array()); 24 | $lines = UpdateConfFile($lines, $datamap, ""); 25 | file_put_contents("/etc/cron.deny", implode("\n", $lines) . "\n"); 26 | 27 | 28 | // NOTE: The Ubuntu/Debian nginx package tends to lag far behind the latest version and makes several really bad configuration decisions. 29 | 30 | // Retrieve system information. 31 | if (!file_exists("/etc/os-release")) CLI::DisplayError("The file '/etc/os-release' does not exist. Is this a Linux system?"); 32 | $osinfo = array(); 33 | $lines = explode("\n", file_get_contents("/etc/os-release")); 34 | foreach ($lines as $line) 35 | { 36 | $pos = strpos($line, "="); 37 | if ($pos !== false) $osinfo[substr($line, 0, $pos)] = substr($line, $pos + 1); 38 | } 39 | 40 | // Update the list of sources for apt. 41 | $datamap = array( 42 | "deb http://nginx.org/packages/ubuntu/ " . $osinfo["VERSION_CODENAME"] . " nginx" => "", 43 | "deb-src http://nginx.org/packages/ubuntu/ " . $osinfo["VERSION_CODENAME"] . " nginx" => "" 44 | ); 45 | 46 | if (!file_exists("/etc/apt/sources.list")) CLI::DisplayError("The file '/etc/apt/sources.list' does not exist. Not actually Debian-based?"); 47 | $lines = explode("\n", trim(file_get_contents("/etc/apt/sources.list"))); 48 | $lines = UpdateConfFile($lines, $datamap, ""); 49 | file_put_contents("/etc/apt/sources.list", implode("\n", $lines) . "\n"); 50 | 51 | // Run apt update to gather the required GPG key. 52 | ob_start(); 53 | @system("/usr/bin/apt-get update 2>&1"); 54 | $data = ob_get_contents(); 55 | ob_end_clean(); 56 | 57 | $lines = explode("\n", $data); 58 | foreach ($lines as $line) 59 | { 60 | $pos = strpos($line, "://nginx.org/packages/ubuntu"); 61 | $pos2 = strpos($line, "NO_PUBKEY"); 62 | if ($pos !== false && $pos2 !== false) 63 | { 64 | $pubkey = trim(substr($line, $pos2 + 10)); 65 | @system("/usr/bin/apt-key adv --keyserver keyserver.ubuntu.com --recv-keys " . $pubkey); 66 | @system("/usr/bin/apt-get update"); 67 | } 68 | } 69 | 70 | // Install Nginx. 71 | @system("/usr/bin/apt-get -y install nginx"); 72 | 73 | // Create a stronger ephemeral key exchange for SSL (this process can take several minutes). 74 | if (!file_exists("/var/local/dhparam2048.pem")) 75 | { 76 | $cmd = ProcessHelper::FindExecutable("openssl", "/usr/bin"); 77 | if ($cmd === false) CLI::DisplayError("Unable to locate OpenSSL."); 78 | 79 | RunExecutable(escapeshellarg($cmd) . " dhparam -out /var/local/dhparam2048.pem 2048"); 80 | } 81 | 82 | chmod("/var/local/dhparam2048.pem", 0400); 83 | 84 | // The main Nginx configuration file needs so much work that simply overwriting it is the best option. 85 | if (!file_exists("/etc/nginx/global/php-local.conf")) file_put_contents("/etc/nginx/nginx.conf", file_get_contents($rootpath2 . "/files/nginx_core.txt")); 86 | 87 | // Set up shared global files. This makes certain configurations easier and safer (e.g. PHP FPM). 88 | @mkdir("/etc/nginx/global"); 89 | if (!file_exists("/etc/nginx/global/php-local.conf")) file_put_contents("/etc/nginx/global/php-local.conf", file_get_contents($rootpath2 . "/files/nginx_global_php-local.txt")); 90 | if (!file_exists("/etc/nginx/global/php-proxy.conf")) file_put_contents("/etc/nginx/global/php-proxy.conf", file_get_contents($rootpath2 . "/files/nginx_global_php-proxy.txt")); 91 | if (!file_exists("/etc/nginx/global/restrictions.conf")) file_put_contents("/etc/nginx/global/restrictions.conf", file_get_contents($rootpath2 . "/files/nginx_global_restrictions.txt")); 92 | if (!file_exists("/etc/nginx/global/location-default.conf")) file_put_contents("/etc/nginx/global/location-default.conf", file_get_contents($rootpath2 . "/files/nginx_global_location-default.txt")); 93 | if (!file_exists("/etc/nginx/global/location-cms.conf")) file_put_contents("/etc/nginx/global/location-cms.conf", file_get_contents($rootpath2 . "/files/nginx_global_location-cms.txt")); 94 | if (!file_exists("/etc/nginx/global/location-drc.conf")) file_put_contents("/etc/nginx/global/location-drc.conf", file_get_contents($rootpath2 . "/files/nginx_global_location-drc.txt")); 95 | 96 | // Well, the Ubuntu nginx package does one thing correctly. So let's borrow that idea. 97 | @mkdir("/etc/nginx/sites-available"); 98 | @mkdir("/etc/nginx/sites-enabled"); 99 | if (!file_exists("/etc/nginx/sites-available/default.conf")) file_put_contents("/etc/nginx/sites-available/default.conf", file_get_contents($rootpath2 . "/files/nginx_site_default.txt")); 100 | if (!file_exists("/etc/nginx/sites-enabled/default.conf")) @system("ln -s ../sites-available/default.conf /etc/nginx/sites-enabled/default.conf"); 101 | 102 | // Create the web root. 103 | @mkdir("/var/www"); 104 | @chgrp("/var/www", "sftp-users"); 105 | @chmod("/var/www", 02775); 106 | 107 | // Set up domains. 108 | $domains = explode(" ", preg_replace('/\s+/', " ", str_replace(array(",", ";"), " ", trim((string)getenv("INSTANT_WWW_DOMAINS"))))); 109 | $first = true; 110 | foreach ($domains as $domain) 111 | { 112 | $domain = trim(preg_replace('/[.]+/', ".", preg_replace('/[^a-z0-9-.]/', "", strtolower($domain))), "-."); 113 | if ($domain !== "" && $domain !== "default") 114 | { 115 | if (!file_exists("/etc/nginx/sites-available/" . $domain . ".conf")) file_put_contents("/etc/nginx/sites-available/" . $domain . ".conf", str_replace("domain.com", $domain, file_get_contents($rootpath2 . "/files/nginx_site.txt"))); 116 | if (!file_exists("/etc/nginx/sites-enabled/" . $domain . ".conf")) @system("ln -s " . escapeshellarg("../sites-available/" . $domain . ".conf") . " " . escapeshellarg("/etc/nginx/sites-enabled/" . $domain . ".conf")); 117 | 118 | @mkdir("/var/www/" . $domain . "/public_html", 0777, true); 119 | @mkdir("/var/www/" . $domain . "/protected_html", 0777, true); 120 | DirHelper::SetPermissions("/var/www/" . $domain, false, "sftp-users", 02775, false, "sftp-users", 0664); 121 | 122 | // Link the default path with the first domain. 123 | if ($first) 124 | { 125 | if (!is_dir("/var/www/default")) @system("ln -s " . escapeshellarg("/var/www/" . $domain) . " /var/www/default"); 126 | 127 | $first = false; 128 | } 129 | } 130 | } 131 | 132 | if ($first) 133 | { 134 | @mkdir("/var/www/default/public_html", 0777, true); 135 | @mkdir("/var/www/default/protected_html", 0777, true); 136 | DirHelper::SetPermissions("/var/www/default", false, "sftp-users", 02775, false, "sftp-users", 0664); 137 | } 138 | 139 | @mkdir("/var/www/shared/.well-known/acme-challenge", 0777, true); 140 | DirHelper::SetPermissions("/var/www/shared", false, "sftp-users", 02775, false, "sftp-users", 0664); 141 | 142 | // Start the service. 143 | @system("service nginx restart"); 144 | ?> -------------------------------------------------------------------------------- /scripts/setup_php_cli.php: -------------------------------------------------------------------------------- 1 | date_default_timezone_get() 27 | ); 28 | 29 | $lines = explode("\n", trim(file_get_contents("/etc/php/" . $file . "/cli/php.ini"))); 30 | $lines = UpdateConfFile($lines, $datamap, " = ", ";", "UpdateConfSkipFinal"); 31 | file_put_contents("/etc/php/" . $file . "/cli/php.ini", implode("\n", $lines) . "\n"); 32 | } 33 | } 34 | 35 | closedir($dir); 36 | } 37 | ?> -------------------------------------------------------------------------------- /scripts/setup_php_drc.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/setup_php_fpm.php: -------------------------------------------------------------------------------- 1 | "60", 47 | "post_max_size" => "15M", 48 | "upload_max_filesize" => "10M", 49 | "date.timezone" => date_default_timezone_get(), 50 | "opcache.enable" => "1", 51 | ); 52 | 53 | $lines = explode("\n", trim(file_get_contents("/etc/php/" . $file . "/fpm/php.ini"))); 54 | $lines = UpdateConfFile($lines, $datamap, " = ", ";", "UpdateConfSkipFinal"); 55 | file_put_contents("/etc/php/" . $file . "/fpm/php.ini", implode("\n", $lines) . "\n"); 56 | } 57 | 58 | if (file_exists("/etc/php/" . $file . "/fpm/pool.d/www.conf")) 59 | { 60 | $datamap = array( 61 | "user" => "www-data", 62 | "group" => "www-data", 63 | "listen" => "127.0.0.1:9000", 64 | "listen.owner" => false, 65 | "listen.group" => false, 66 | "listen.mode" => false, 67 | "pm" => "ondemand", 68 | "pm.max_children" => "50", 69 | "pm.start_servers" => false, 70 | "pm.min_spare_servers" => false, 71 | "pm.max_spare_servers" => false, 72 | "pm.process_idle_timeout" => "10s", 73 | "pm.max_requests" => "500", 74 | ); 75 | 76 | $lines = explode("\n", trim(file_get_contents("/etc/php/" . $file . "/fpm/pool.d/www.conf"))); 77 | $lines = UpdateConfFile($lines, $datamap, " = ", ";", "PHPFPMPoolConfUpdate"); 78 | file_put_contents("/etc/php/" . $file . "/fpm/pool.d/www.conf", implode("\n", $lines) . "\n"); 79 | } 80 | 81 | // Start the service. 82 | @system("service php" . $file . "-fpm restart"); 83 | } 84 | } 85 | 86 | closedir($dir); 87 | } 88 | ?> -------------------------------------------------------------------------------- /support/cli.php: -------------------------------------------------------------------------------- 1 | $val) 15 | { 16 | if (!isset($options["rules"][$val])) unset($options["shortmap"][$key]); 17 | } 18 | foreach ($options["rules"] as $key => $val) 19 | { 20 | if (!isset($val["arg"])) $options["rules"][$key]["arg"] = false; 21 | if (!isset($val["multiple"])) $options["rules"][$key]["multiple"] = false; 22 | } 23 | 24 | if ($args === false) $args = $_SERVER["argv"]; 25 | else if (is_string($args)) 26 | { 27 | $args2 = $args; 28 | $args = array(); 29 | $inside = false; 30 | $currarg = ""; 31 | $y = strlen($args2); 32 | for ($x = 0; $x < $y; $x++) 33 | { 34 | $currchr = substr($args2, $x, 1); 35 | 36 | if ($inside === false && $currchr == " " && $currarg != "") 37 | { 38 | $args[] = $currarg; 39 | $currarg = ""; 40 | } 41 | else if ($currchr == "\"" || $currchr == "'") 42 | { 43 | if ($inside === false) $inside = $currchr; 44 | else if ($inside === $currchr) $inside = false; 45 | else $currarg .= $currchr; 46 | } 47 | else if ($currchr == "\\" && $x < $y - 1) 48 | { 49 | $x++; 50 | $currarg .= substr($args2, $x, 1); 51 | } 52 | else if ($inside !== false || $currchr != " ") 53 | { 54 | $currarg .= $currchr; 55 | } 56 | } 57 | 58 | if ($currarg != "") $args[] = $currarg; 59 | } 60 | 61 | $result = array("success" => true, "file" => array_shift($args), "opts" => array(), "params" => array()); 62 | 63 | // Look over shortmap to determine if options exist that are one byte (flags) and don't have arguments. 64 | $chrs = array(); 65 | foreach ($options["shortmap"] as $key => $val) 66 | { 67 | if (isset($options["rules"][$val]) && !$options["rules"][$val]["arg"]) $chrs[$key] = true; 68 | } 69 | 70 | $allowopt = true; 71 | $y = count($args); 72 | for ($x = 0; $x < $y; $x++) 73 | { 74 | $arg = $args[$x]; 75 | 76 | // Attempt to process an option. 77 | $opt = false; 78 | $optval = false; 79 | if ($allowopt && substr($arg, 0, 1) == "-") 80 | { 81 | $pos = strpos($arg, "="); 82 | if ($pos === false) $pos = strlen($arg); 83 | else $optval = substr($arg, $pos + 1); 84 | $arg2 = substr($arg, 1, $pos - 1); 85 | 86 | if (isset($options["rules"][$arg2])) $opt = $arg2; 87 | else if (isset($options["shortmap"][$arg2])) $opt = $options["shortmap"][$arg2]; 88 | else if ($x == 0) 89 | { 90 | // Attempt to process as a set of flags. 91 | $y2 = strlen($arg2); 92 | if ($y2 > 0) 93 | { 94 | for ($x2 = 0; $x2 < $y2; $x2++) 95 | { 96 | $currchr = substr($arg2, $x2, 1); 97 | 98 | if (!isset($chrs[$currchr])) break; 99 | } 100 | 101 | if ($x2 == $y2) 102 | { 103 | for ($x2 = 0; $x2 < $y2; $x2++) 104 | { 105 | $opt = $options["shortmap"][substr($arg2, $x2, 1)]; 106 | 107 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 108 | else 109 | { 110 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 111 | $result["opts"][$opt]++; 112 | } 113 | } 114 | 115 | continue; 116 | } 117 | } 118 | } 119 | } 120 | 121 | if ($opt === false) 122 | { 123 | // Is a parameter. 124 | if (substr($arg, 0, 1) === "\"" || substr($arg, 0, 1) === "'") $arg = substr($arg, 1); 125 | if (substr($arg, -1) === "\"" || substr($arg, -1) === "'") $arg = substr($arg, 0, -1); 126 | 127 | $result["params"][] = $arg; 128 | 129 | if (!$options["allow_opts_after_param"]) $allowopt = false; 130 | } 131 | else if (!$options["rules"][$opt]["arg"]) 132 | { 133 | // Is a flag by itself. 134 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 135 | else 136 | { 137 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 138 | $result["opts"][$opt]++; 139 | } 140 | } 141 | else 142 | { 143 | // Is an option. 144 | if ($optval === false) 145 | { 146 | $x++; 147 | if ($x == $y) break; 148 | $optval = $args[$x]; 149 | } 150 | 151 | if (substr($optval, 0, 1) === "\"" || substr($optval, 0, 1) === "'") $optval = substr($optval, 1); 152 | if (substr($optval, -1) === "\"" || substr($optval, -1) === "'") $optval = substr($optval, 0, -1); 153 | 154 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = $optval; 155 | else 156 | { 157 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = array(); 158 | $result["opts"][$opt][] = $optval; 159 | } 160 | } 161 | } 162 | 163 | return $result; 164 | } 165 | 166 | public static function CanGetUserInputWithArgs(&$args, $prefix) 167 | { 168 | return (($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) || count($args["params"])); 169 | } 170 | 171 | // Gets a line of input from the user. If the user supplies all information via the command-line, this could be entirely automated. 172 | public static function GetUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false, $callback = false, $callbackopts = false) 173 | { 174 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 175 | { 176 | echo "\n" . rtrim($noparamsoutput) . "\n\n"; 177 | 178 | $suppressoutput = false; 179 | $noparamsoutput = ""; 180 | } 181 | 182 | do 183 | { 184 | $prompt = ($suppressoutput ? "" : $question . ($default !== false ? " [" . $default . "]" : "") . ": "); 185 | 186 | if ($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) 187 | { 188 | $line = array_shift($args["opts"][$prefix]); 189 | if ($line === "") $line = $default; 190 | if (!$suppressoutput) echo $prompt . $line . "\n"; 191 | } 192 | else if (count($args["params"])) 193 | { 194 | $line = array_shift($args["params"]); 195 | if ($line === "") $line = $default; 196 | if (!$suppressoutput) echo $prompt . $line . "\n"; 197 | } 198 | else if (strtoupper(substr(php_uname("s"), 0, 3)) != "WIN" && function_exists("readline") && function_exists("readline_add_history")) 199 | { 200 | $line = readline($prompt); 201 | if ($line === false) exit(); 202 | 203 | $line = trim($line); 204 | if ($line === "") $line = $default; 205 | if ($line !== false && $line !== "") readline_add_history($line); 206 | } 207 | else 208 | { 209 | echo $prompt; 210 | fflush(STDOUT); 211 | $line = fgets(STDIN); 212 | if ($line === false || ($line === "" && feof(STDIN))) exit(); 213 | 214 | $line = trim($line); 215 | if ($line === "") $line = $default; 216 | } 217 | 218 | if ($line === false || (is_callable($callback) && !call_user_func_array($callback, array($line, &$callbackopts)))) 219 | { 220 | if ($line !== false) $line = false; 221 | else echo "Please enter a value.\n"; 222 | 223 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 224 | { 225 | echo "\n" . $noparamsoutput . "\n"; 226 | 227 | $noparamsoutput = ""; 228 | } 229 | 230 | $suppressoutput = false; 231 | } 232 | } while ($line === false); 233 | 234 | return $line; 235 | } 236 | 237 | // Obtains a valid line of input. 238 | public static function GetLimitedUserInputWithArgs(&$args, $prefix, $question, $default, $allowedoptionsprefix, $allowedoptions, $loop = true, $suppressoutput = false, $multipleuntil = false) 239 | { 240 | $noparamsoutput = $allowedoptionsprefix . "\n\n"; 241 | $size = 0; 242 | foreach ($allowedoptions as $key => $val) 243 | { 244 | if ($size < strlen($key)) $size = strlen($key); 245 | } 246 | 247 | foreach ($allowedoptions as $key => $val) 248 | { 249 | $newtab = str_repeat(" ", 2 + $size + 3); 250 | $noparamsoutput .= " " . $key . ":" . str_repeat(" ", $size - strlen($key)) . " " . str_replace("\n\t", "\n" . $newtab, $val) . "\n"; 251 | } 252 | 253 | $noparamsoutput .= "\n"; 254 | 255 | if ($default === false && count($allowedoptions) == 1) 256 | { 257 | reset($allowedoptions); 258 | $default = key($allowedoptions); 259 | } 260 | 261 | $results = array(); 262 | do 263 | { 264 | $displayed = (!count($args["params"])); 265 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 266 | if (is_array($multipleuntil) && $multipleuntil["exit"] === $result) break; 267 | $result2 = false; 268 | if (!count($allowedoptions)) break; 269 | foreach ($allowedoptions as $key => $val) 270 | { 271 | if (!strcasecmp($key, $result) || !strcasecmp($val, $result)) $result2 = $key; 272 | } 273 | if ($loop) 274 | { 275 | if ($result2 === false) 276 | { 277 | echo "Please select an option from the list.\n"; 278 | 279 | $suppressoutput = false; 280 | } 281 | else if (is_array($multipleuntil)) 282 | { 283 | $results[$result2] = $result2; 284 | 285 | $question = $multipleuntil["nextquestion"]; 286 | $default = $multipleuntil["nextdefault"]; 287 | } 288 | } 289 | 290 | if ($displayed) $noparamsoutput = ""; 291 | } while ($loop && ($result2 === false || is_array($multipleuntil))); 292 | 293 | return (is_array($multipleuntil) ? $results : $result2); 294 | } 295 | 296 | // Obtains Yes/No style input. 297 | public static function GetYesNoUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false) 298 | { 299 | $default = (substr(strtoupper(trim($default)), 0, 1) === "Y" ? "Y" : "N"); 300 | 301 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 302 | $result = (substr(strtoupper(trim($result)), 0, 1) === "Y"); 303 | 304 | return $result; 305 | } 306 | 307 | public static function GetHexDump($data) 308 | { 309 | $result = ""; 310 | 311 | $x = 0; 312 | $y = strlen($data); 313 | if ($y <= 256) $padwidth = 2; 314 | else if ($y <= 65536) $padwidth = 4; 315 | else if ($y <= 16777216) $padwidth = 6; 316 | else $padwidth = 8; 317 | 318 | $pad = str_repeat(" ", $padwidth); 319 | 320 | $data2 = str_split(strtoupper(bin2hex($data)), 32); 321 | foreach ($data2 as $line) 322 | { 323 | $result .= sprintf("%0" . $padwidth . "X", $x) . " | "; 324 | 325 | $line = str_split($line, 2); 326 | array_splice($line, 8, 0, ""); 327 | $result .= implode(" ", $line) . "\n"; 328 | 329 | $result .= $pad . " |"; 330 | $y2 = $x + 16; 331 | for ($x2 = 0; $x2 < 16 && $x < $y; $x2++) 332 | { 333 | $result .= " "; 334 | if ($x2 === 8) $result .= " "; 335 | 336 | $tempchr = ord($data[$x]); 337 | if ($tempchr === 0x09) $result .= "\\t"; 338 | else if ($tempchr === 0x0D) $result .= "\\r"; 339 | else if ($tempchr === 0x0A) $result .= "\\n"; 340 | else if ($tempchr === 0x00) $result .= "\\0"; 341 | else if ($tempchr < 32 || $tempchr > 126) $result .= " "; 342 | else $result .= " " . $data[$x]; 343 | 344 | $x++; 345 | } 346 | 347 | $result .= "\n"; 348 | } 349 | 350 | return $result; 351 | } 352 | 353 | // Outputs a JSON array (useful for captured output). 354 | public static function DisplayResult($result, $exit = true) 355 | { 356 | if (is_array($result)) echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; 357 | else echo $result . "\n"; 358 | 359 | if ($exit) exit(); 360 | } 361 | 362 | // Useful for reparsing remaining parameters as new arguments. 363 | public static function ReinitArgs(&$args, $newargs) 364 | { 365 | // Process the parameters. 366 | $options = array( 367 | "shortmap" => array( 368 | "?" => "help" 369 | ), 370 | "rules" => array( 371 | ) 372 | ); 373 | 374 | foreach ($newargs as $arg) $options["rules"][$arg] = array("arg" => true, "multiple" => true); 375 | $options["rules"]["help"] = array("arg" => false); 376 | 377 | $args = self::ParseCommandLine($options, array_merge(array(""), $args["params"])); 378 | 379 | if (isset($args["opts"]["help"])) self::DisplayResult(array("success" => true, "options" => array_keys($options["rules"]))); 380 | } 381 | 382 | // Tracks messages for a command-line interface app. 383 | private static $messages = array(); 384 | 385 | public static function LogMessage($msg, $data = null) 386 | { 387 | if (isset($data)) $msg .= "\n\t" . trim(str_replace("\n", "\n\t", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))); 388 | 389 | self::$messages[] = $msg; 390 | 391 | fwrite(STDERR, $msg . "\n"); 392 | } 393 | 394 | public static function DisplayError($msg, $result = false, $exit = true) 395 | { 396 | self::LogMessage(($exit ? "[Error] " : "") . $msg); 397 | 398 | if ($result !== false && is_array($result) && isset($result["error"]) && isset($result["errorcode"])) self::LogMessage("[Error] " . $result["error"] . " (" . $result["errorcode"] . ")", (isset($result["info"]) ? $result["info"] : null)); 399 | 400 | if ($exit) exit(); 401 | } 402 | 403 | public static function GetLogMessages($filters = array()) 404 | { 405 | if (is_string($filters)) $filters = array($filters); 406 | 407 | $result = array(); 408 | foreach (self::$messages as $message) 409 | { 410 | $found = (!count($filters)); 411 | foreach ($filters as $filter) 412 | { 413 | if (preg_match($filter, $message)) $found = true; 414 | } 415 | 416 | if ($found) $result[] = $message; 417 | } 418 | 419 | return $result; 420 | } 421 | 422 | public static function ResetLogMessages() 423 | { 424 | self::$messages = array(); 425 | } 426 | 427 | 428 | private static $timerinfo = array(); 429 | 430 | public static function StartTimer() 431 | { 432 | $ts = microtime(true); 433 | 434 | self::$timerinfo = array( 435 | "start" => $ts, 436 | "diff" => $ts 437 | ); 438 | } 439 | 440 | public static function UpdateTimer() 441 | { 442 | $ts = microtime(true); 443 | $diff = $ts - self::$timerinfo["diff"]; 444 | self::$timerinfo["diff"] = $ts; 445 | 446 | $result = array( 447 | "success" => true, 448 | "diff" => sprintf("%.2f", $diff), 449 | "total" => sprintf("%.2f", $ts - self::$timerinfo["start"]) 450 | ); 451 | 452 | return $result; 453 | } 454 | } 455 | ?> -------------------------------------------------------------------------------- /support/db.php: -------------------------------------------------------------------------------- 1 | numqueries = 0; 47 | $this->totaltime = 0; 48 | $this->dbobj = false; 49 | $this->origdbobj = false; 50 | $this->debug = false; 51 | $this->transaction = 0; 52 | $this->mdsn = false; 53 | $this->currdb = false; 54 | $this->available_status = $this->IsAvailable(); 55 | 56 | if ($dsn !== false) $this->Connect($dsn, $username, $password, $options); 57 | } 58 | 59 | public function __destruct() 60 | { 61 | $this->Disconnect(); 62 | } 63 | 64 | public function SetDebug($debug) 65 | { 66 | $this->debug = $debug; 67 | } 68 | 69 | protected function AssertPDOAvailable($checkdb) 70 | { 71 | if (!is_string($this->available_status)) 72 | { 73 | throw new Exception(CSDB::DB_Translate("The driver is not available.")); 74 | exit(); 75 | } 76 | 77 | if ($checkdb && $this->dbobj === false) 78 | { 79 | throw new Exception(CSDB::DB_Translate("Not connected to a database.")); 80 | exit(); 81 | } 82 | } 83 | 84 | public function BeginTransaction() 85 | { 86 | $this->AssertPDOAvailable(true); 87 | 88 | if (!$this->transaction) 89 | { 90 | $this->dbobj->beginTransaction(); 91 | 92 | if (is_resource($this->debug)) fwrite($this->debug, "BEGIN transaction.\n----------\n"); 93 | } 94 | 95 | $this->transaction++; 96 | } 97 | 98 | public function NumTransactions() 99 | { 100 | return $this->transaction; 101 | } 102 | 103 | public function Commit() 104 | { 105 | $this->AssertPDOAvailable(true); 106 | 107 | if ($this->transaction) 108 | { 109 | $this->transaction--; 110 | if (!$this->transaction) 111 | { 112 | $this->dbobj->commit(); 113 | 114 | if (is_resource($this->debug)) fwrite($this->debug, "COMMIT transaction.\n----------\n"); 115 | } 116 | } 117 | } 118 | 119 | public function Rollback() 120 | { 121 | $this->AssertPDOAvailable(true); 122 | 123 | if ($this->transaction) 124 | { 125 | $this->transaction = 0; 126 | $this->dbobj->rollBack(); 127 | 128 | if (is_resource($this->debug)) fwrite($this->debug, "ROLLBACK transaction.\n----------\n"); 129 | } 130 | } 131 | 132 | public function Connect($dsn, $username = false, $password = false, $options = array()) 133 | { 134 | $this->AssertPDOAvailable(false); 135 | 136 | $this->origdbobj = $this->dbobj; 137 | $this->dbobj = false; 138 | $this->mdsn = false; 139 | 140 | $startts = microtime(true); 141 | 142 | if (is_array($dsn)) 143 | { 144 | foreach ($dsn as $key => $val) $dsn[$key] = $key . "=" . $val; 145 | $dsn = $this->available_status . ":" . implode(";", $dsn); 146 | } 147 | 148 | $pos = strpos($dsn, ":"); 149 | $driver = substr($dsn, 0, $pos); 150 | if ($driver !== $this->available_status) 151 | { 152 | throw new Exception(CSDB::DB_Translate("The driver '%s' is invalid. Must be '%s'.", htmlspecialchars($driver), htmlspecialchars($this->available_status))); 153 | exit(); 154 | } 155 | 156 | try 157 | { 158 | if ($password !== false) $this->dbobj = new PDO($dsn, $username, $password); 159 | else if ($username !== false) $this->dbobj = new PDO($dsn, $username); 160 | else $this->dbobj = new PDO($dsn); 161 | } 162 | catch (Exception $e) 163 | { 164 | if (is_resource($this->debug)) fwrite($this->debug, "The connection to the database server failed. " . $e->getMessage() . "\n----------\n"); 165 | 166 | if (is_resource($this->debug) || $this->debug) throw new Exception(CSDB::DB_Translate("The connection to the database server failed. %s", $e->getMessage())); 167 | else throw new Exception(CSDB::DB_Translate("The connection to the database server failed.")); 168 | exit(); 169 | } 170 | 171 | if (is_resource($this->debug)) fwrite($this->debug, "Connected to '" . $dsn . "'.\n----------\n"); 172 | 173 | $this->totaltime += (microtime(true) - $startts); 174 | 175 | return true; 176 | } 177 | 178 | public function SetMaster($dsn, $username = false, $password = false, $options = array()) 179 | { 180 | $this->mdsn = $dsn; 181 | $this->musername = $username; 182 | $this->mpassword = $password; 183 | $this->moptions = $options; 184 | } 185 | 186 | public function Disconnect() 187 | { 188 | $startts = microtime(true); 189 | 190 | while ($this->transaction) $this->Commit(); 191 | 192 | if ($this->dbobj !== false) 193 | { 194 | unset($this->dbobj); 195 | $this->dbobj = false; 196 | } 197 | if ($this->origdbobj !== false) 198 | { 199 | unset($this->origdbobj); 200 | $this->origdbobj = false; 201 | } 202 | 203 | $this->totaltime += (microtime(true) - $startts); 204 | 205 | if (is_resource($this->debug)) fwrite($this->debug, "Disconnected from database.\n\nTotal query time: " . sprintf("%.03f", $this->totaltime) . " seconds\n----------\n"); 206 | 207 | return true; 208 | } 209 | 210 | public function Query() 211 | { 212 | $params = func_get_args(); 213 | return $this->InternalQuery($params); 214 | } 215 | 216 | public function GetRow() 217 | { 218 | $params = func_get_args(); 219 | $dbresult = $this->InternalQuery($params); 220 | if ($dbresult === false) return false; 221 | $row = $dbresult->NextRow(); 222 | unset($dbresult); 223 | 224 | return $row; 225 | } 226 | 227 | public function GetRowArray() 228 | { 229 | $params = func_get_args(); 230 | $dbresult = $this->InternalQuery($params); 231 | if ($dbresult === false) return false; 232 | $row = $dbresult->NextRow(PDO::FETCH_BOTH); 233 | unset($dbresult); 234 | 235 | return $row; 236 | } 237 | 238 | public function GetCol() 239 | { 240 | $result = array(); 241 | 242 | $params = func_get_args(); 243 | $dbresult = $this->InternalQuery($params); 244 | if ($dbresult === false) return false; 245 | while ($row = $dbresult->NextRow(PDO::FETCH_NUM)) 246 | { 247 | $result[] = $row[0]; 248 | } 249 | 250 | return $result; 251 | } 252 | 253 | public function GetOne() 254 | { 255 | $params = func_get_args(); 256 | $dbresult = $this->InternalQuery($params); 257 | if ($dbresult === false) return false; 258 | $row = $dbresult->NextRow(PDO::FETCH_NUM); 259 | unset($dbresult); 260 | if ($row === false) return false; 261 | 262 | return $row[0]; 263 | } 264 | 265 | public function GetVersion() 266 | { 267 | return ""; 268 | } 269 | 270 | public function GetInsertID($name = null) 271 | { 272 | return $this->dbobj->lastInsertId($name); 273 | } 274 | 275 | public function TableExists($name) 276 | { 277 | return false; 278 | } 279 | 280 | public function LargeResults($enable) 281 | { 282 | } 283 | 284 | public function NumQueries() 285 | { 286 | return $this->numqueries; 287 | } 288 | 289 | // Execution time with microsecond precision. 290 | public function ExecutionTime() 291 | { 292 | return $this->totaltime; 293 | } 294 | 295 | private function InternalQuery($params) 296 | { 297 | $startts = microtime(true); 298 | 299 | $cmd = array_shift($params); 300 | if ($cmd !== false) $cmd = strtoupper($cmd); 301 | $queryinfo = array_shift($params); 302 | if (count($params) == 1 && is_array($params[0])) $params = $params[0]; 303 | 304 | if ($cmd === false) 305 | { 306 | $master = true; 307 | $sqls = array((string)$queryinfo); 308 | $opts = array($params); 309 | $filteropts = false; 310 | } 311 | else 312 | { 313 | $master = false; 314 | $sqls = ""; 315 | $opts = array(); 316 | $result = $this->GenerateSQL($master, $sqls, $opts, $cmd, $queryinfo, $params, false); 317 | $filteropts = (isset($result["filter_opts"]) ? $result["filter_opts"] : false); 318 | if (!$result["success"]) 319 | { 320 | if ($result["errorcode"] == "skip_sql_query") return new CSDB_PDO_Statement($this, false, $filteropts); 321 | 322 | if (is_resource($this->debug)) fwrite($this->debug, "Error generating '" . $cmd . "' SQL query. " . $result["error"] . " (" . $result["errorcode"] . ")\n\n" . (is_string($sqls) ? $sqls : var_export($sqls, true)) . "\n----------\n"); 323 | 324 | throw new Exception(CSDB::DB_Translate("Error generating '%s' SQL query. %s (%s)", $cmd, $result["error"], $result["errorcode"])); 325 | exit(); 326 | } 327 | 328 | if ($cmd == "USE") $this->currdb = $queryinfo; 329 | } 330 | 331 | // Switch to master database. 332 | if ($master && $this->mdsn !== false) 333 | { 334 | $numcommit = $this->transaction; 335 | while ($this->transaction) $this->Commit(); 336 | 337 | if (!$this->Connect($this->mdsn, $this->musername, $this->mpassword, $this->moptions)) 338 | { 339 | throw new Exception(CSDB::DB_Translate("The connection to the master database failed.")); 340 | exit(); 341 | } 342 | 343 | if (is_resource($this->debug)) fwrite($this->debug, "Connection to master database succeeded.\n----------\n"); 344 | 345 | if ($this->currdb !== false) $this->Query("USE", $this->currdb); 346 | 347 | while ($this->transaction < $numcommit) $this->BeginTransaction(); 348 | } 349 | 350 | $prepareopts = (isset($result["prepare_opts"]) ? $result["prepare_opts"] : array()); 351 | 352 | if (is_string($sqls)) 353 | { 354 | $sqls = array($sqls); 355 | $opts = array($opts); 356 | } 357 | foreach ($sqls as $num => $sql) 358 | { 359 | $opts2 = $opts[$num]; 360 | 361 | $result = $this->dbobj->prepare($sql, $prepareopts); 362 | if ($result === false) 363 | { 364 | $info = $this->dbobj->errorInfo(); 365 | 366 | if (is_resource($this->debug)) fwrite($this->debug, $info[0] . " " . $info[2] . " (" . $info[1] . ").\nError preparing SQL query:\n\n" . $sql . "\n\n" . var_export($opts2, true) . "\n----------\n"); 367 | 368 | if (is_resource($this->debug) || $this->debug) throw new Exception(CSDB::DB_Translate("%s %s (%s). Error preparing SQL query: %s %s", $info[0], $info[2], $info[1], $sql, var_export($opts2, true))); 369 | else throw new Exception(CSDB::DB_Translate("Error preparing SQL query. %s %s (%s)", $info[0], $info[2], $info[1])); 370 | exit(); 371 | } 372 | if (!$result->execute($opts2)) 373 | { 374 | $info = $result->errorInfo(); 375 | 376 | if (is_resource($this->debug)) fwrite($this->debug, $info[0] . " " . $info[2] . " (" . $info[1] . ").\nError executing SQL query:\n\n" . $sql . "\n\n" . var_export($opts2, true) . "\n----------\n"); 377 | 378 | if (is_resource($this->debug) || $this->debug) throw new Exception(CSDB::DB_Translate("%s %s (%s). Error executing SQL query: %s %s", $info[0], $info[2], $info[1], $sql, var_export($opts2, true))); 379 | else throw new Exception(CSDB::DB_Translate("Error executing SQL query. %s %s (%s)", $info[0], $info[2], $info[1])); 380 | exit(); 381 | } 382 | 383 | $this->numqueries++; 384 | 385 | $diff = (microtime(true) - $startts); 386 | $this->totaltime += $diff; 387 | 388 | if (is_resource($this->debug)) 389 | { 390 | ob_start(); 391 | debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 392 | $info = ob_get_contents(); 393 | ob_end_clean(); 394 | fwrite($this->debug, "Query #" . $this->numqueries . "\n\n" . $sql . "\n\n" . var_export($opts2, true) . "\n\n" . $info . "\nCommand: " . $cmd . "\n\n" . sprintf("%.03f", $diff) . " seconds (" . sprintf("%.03f", $this->totaltime) . " total)\n----------\n"); 395 | } 396 | 397 | if ($filteropts !== false) $this->RunStatementFilter($result, $filteropts); 398 | } 399 | 400 | return new CSDB_PDO_Statement($this, $result, $filteropts); 401 | } 402 | 403 | public function Quote($str, $type = PDO::PARAM_STR) 404 | { 405 | if (is_bool($str) && !$str) return "NULL"; 406 | 407 | return $this->dbobj->quote((string)$str, $type); 408 | } 409 | 410 | public function QuoteIdentifier($str) 411 | { 412 | return ""; 413 | } 414 | 415 | protected function ProcessSubqueries(&$result, &$master, $subqueries) 416 | { 417 | foreach ($subqueries as $num => $subquery) 418 | { 419 | $sql2 = ""; 420 | $opts2 = array(); 421 | $queryinfo2 = array_shift($subquery); 422 | if (count($subquery) == 1 && is_array($subquery[0])) $subquery = $subquery[0]; 423 | $result = $this->GenerateSQL($master, $sql2, $opts2, "SELECT", $queryinfo2, $subquery, true); 424 | if (!$result["success"]) return $result; 425 | 426 | $result = str_replace("{" . $num . "}", "(" . $sql2 . ")", $result); 427 | } 428 | 429 | return array("success" => true); 430 | } 431 | 432 | // Derived classes implement this function. 433 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 434 | { 435 | return array("success" => false, "error" => CSDB::DB_Translate("The base class GenerateSQL() was called."), "errorcode" => "wrong_class_used"); 436 | } 437 | 438 | protected function RunStatementFilter(&$stmt, &$filteropts) 439 | { 440 | } 441 | 442 | // Can't be a 'protected' function since this is called from CSDB_PDO_Statement. 443 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 444 | { 445 | if ($row === false) return; 446 | 447 | switch ($filteropts["mode"]) 448 | { 449 | case "SHOW DATABASES": 450 | case "SHOW TABLES": 451 | { 452 | if (isset($row->name) && isset($filteropts["queryinfo"]) && isset($filteropts["queryinfo"][0])) 453 | { 454 | if (stripos($row->name, $filteropts["queryinfo"][0]) === false) $fetchnext = true; 455 | } 456 | 457 | break; 458 | } 459 | case "SELECT EXPORT": 460 | { 461 | if ($row !== false) 462 | { 463 | $opts = array($filteropts["table"], array()); 464 | foreach ($row as $key => $val) 465 | { 466 | $opts[1][$key] = $val; 467 | unset($row->$key); 468 | } 469 | 470 | $row->cmd = "INSERT"; 471 | $row->opts = $opts; 472 | } 473 | 474 | break; 475 | } 476 | } 477 | } 478 | 479 | protected function ProcessColumnDefinition($info) 480 | { 481 | return array("success" => false, "error" => CSDB::DB_Translate("The base class ProcessColumnDefinition() was called."), "errorcode" => "wrong_class_used"); 482 | } 483 | 484 | protected function ProcessKeyDefinition($info) 485 | { 486 | return array("success" => false, "error" => CSDB::DB_Translate("The base class ProcessKeyDefinition() was called."), "errorcode" => "wrong_class_used"); 487 | } 488 | 489 | // Not intended to be overridden, just accessible to the derived class. 490 | protected function ProcessSELECT(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 491 | { 492 | $sql = "SELECT"; 493 | foreach ($supported["PRECOLUMN"] as $key => $mode) 494 | { 495 | if ($key != "SUBQUERIES" && isset($queryinfo[$key])) 496 | { 497 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 498 | } 499 | } 500 | 501 | if (is_array($queryinfo[0])) 502 | { 503 | foreach ($queryinfo[0] as $num => $col) $queryinfo[0][$num] = $this->QuoteIdentifier($col); 504 | $queryinfo[0] = implode(", ", $queryinfo[0]); 505 | } 506 | 507 | if ($supported["PRECOLUMN"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 508 | { 509 | $result = $this->ProcessSubqueries($queryinfo[0], $master, $queryinfo["SUBQUERIES"]); 510 | if (!$result["success"]) return $result; 511 | } 512 | 513 | $sql .= " " . $queryinfo[0]; 514 | 515 | if (isset($queryinfo["FROM"])) $queryinfo[1] = $queryinfo["FROM"]; 516 | 517 | if (isset($queryinfo[1])) 518 | { 519 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 520 | 521 | $pos = strpos($queryinfo[1], "?"); 522 | while ($pos !== false) 523 | { 524 | $queryinfo[1] = substr($queryinfo[1], 0, $pos) . $this->QuoteIdentifier($prefix . array_shift($args)) . substr($queryinfo[1], $pos + 1); 525 | 526 | $pos = strpos($queryinfo[1], "?"); 527 | } 528 | 529 | if ($supported["FROM"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 530 | { 531 | $result = $this->ProcessSubqueries($queryinfo[1], $master, $queryinfo["SUBQUERIES"]); 532 | if (!$result["success"]) return $result; 533 | } 534 | 535 | $sql .= " FROM " . $queryinfo[1]; 536 | } 537 | 538 | if (isset($queryinfo["WHERE"])) 539 | { 540 | if ($supported["WHERE"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 541 | { 542 | $result = $this->ProcessSubqueries($queryinfo["WHERE"], $master, $queryinfo["SUBQUERIES"]); 543 | if (!$result["success"]) return $result; 544 | } 545 | 546 | $sql .= " WHERE " . $queryinfo["WHERE"]; 547 | } 548 | 549 | if (isset($supported["GROUP BY"]) && $supported["GROUP BY"] && isset($queryinfo["GROUP BY"])) $sql .= " GROUP BY " . $queryinfo["GROUP BY"]; 550 | if (isset($supported["HAVING"]) && $supported["HAVING"] && isset($queryinfo["HAVING"])) $sql .= " HAVING " . $queryinfo["HAVING"]; 551 | if (isset($supported["ORDER BY"]) && $supported["ORDER BY"] && isset($queryinfo["ORDER BY"])) $sql .= " ORDER BY " . $queryinfo["ORDER BY"]; 552 | if (isset($supported["LIMIT"]) && $supported["LIMIT"] !== false && !$subquery && isset($queryinfo["LIMIT"])) 553 | { 554 | if (is_array($queryinfo["LIMIT"])) $queryinfo["LIMIT"] = implode($supported["LIMIT"], $queryinfo["LIMIT"]); 555 | $sql .= " LIMIT " . $queryinfo["LIMIT"]; 556 | } 557 | 558 | $opts = $args; 559 | 560 | if (isset($queryinfo["EXPORT ROWS"])) return array("success" => true, "filter_opts" => array("mode" => "SELECT EXPORT", "table" => $queryinfo["EXPORT ROWS"])); 561 | 562 | return array("success" => true); 563 | } 564 | 565 | protected function ProcessINSERT(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 566 | { 567 | $master = true; 568 | 569 | $sql = "INSERT"; 570 | foreach ($supported["PREINTO"] as $key => $mode) 571 | { 572 | if (isset($queryinfo[$key])) 573 | { 574 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 575 | } 576 | } 577 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 578 | $sql .= " INTO " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 579 | 580 | if (isset($queryinfo["FROM"])) $queryinfo[1] = $queryinfo["FROM"]; 581 | 582 | if (isset($queryinfo["SELECT"])) 583 | { 584 | if (!$supported["SELECT"]) return array("success" => false, CSDB::DB_Translate("INSERT INTO SELECT not supported."), "insert_select_unsupported"); 585 | 586 | if (isset($queryinfo[1]) && is_array($queryinfo[1]) && count($queryinfo[1])) 587 | { 588 | $keys = array(); 589 | foreach ($queryinfo[1] as $key) $keys[] = $this->QuoteIdentifier($key); 590 | $sql .= " (" . implode(", ", $keys) . ")"; 591 | } 592 | 593 | $sql2 = ""; 594 | $opts2 = array(); 595 | $queryinfo2 = array_shift($queryinfo["SELECT"]); 596 | if (count($queryinfo["SELECT"]) == 1 && is_array($queryinfo["SELECT"][0])) $queryinfo["SELECT"] = $queryinfo["SELECT"][0]; 597 | $result = $this->GenerateSQL($master, $sql2, $opts2, "SELECT", $queryinfo2, $queryinfo["SELECT"], false); 598 | if (!$result["success"]) return $result; 599 | $sql .= " " . $sql2; 600 | } 601 | else if (isset($queryinfo[1]) && is_array($queryinfo[1]) && count($queryinfo[1])) 602 | { 603 | $keys = array(); 604 | $vals = array(); 605 | foreach ($queryinfo[1] as $key => $val) 606 | { 607 | $keys[] = $this->QuoteIdentifier($key); 608 | $vals[] = "?"; 609 | $args[] = $val; 610 | } 611 | 612 | // Avoid this if possible. 613 | if (isset($queryinfo[2])) 614 | { 615 | foreach ($queryinfo[2] as $key => $val) 616 | { 617 | $keys[] = $this->QuoteIdentifier($key); 618 | $vals[] = $val; 619 | } 620 | } 621 | $sql .= " (" . implode(", ", $keys) . ") VALUES "; 622 | $origsql = $sql; 623 | $sql .= "(" . implode(", ", $vals) . ")"; 624 | 625 | // Handle bulk inserts. 626 | if (isset($queryinfo[3]) && isset($queryinfo[4])) 627 | { 628 | $bulkinsert = (isset($supported["BULKINSERT"]) && $supported["BULKINSERT"]); 629 | $bulkinsertlimit = (isset($supported["BULKINSERTLIMIT"]) ? $supported["BULKINSERTLIMIT"] : false); 630 | $sql = array($sql); 631 | $args = array($args); 632 | $lastpos = 0; 633 | for ($x = 3; isset($queryinfo[$x]) && isset($queryinfo[$x + 1]); $x += 2) 634 | { 635 | if (!$bulkinsert || ($bulkinsertlimit !== false && count($args[$lastpos]) + count($queryinfo[$x]) + count($queryinfo[$x + 1]) >= $bulkinsertlimit)) 636 | { 637 | $sql[] = $origsql; 638 | $args[] = array(); 639 | $lastpos++; 640 | } 641 | else 642 | { 643 | $sql[$lastpos] .= ", "; 644 | } 645 | 646 | $vals = array(); 647 | foreach ($queryinfo[$x] as $key => $val) 648 | { 649 | $vals[] = "?"; 650 | $args[$lastpos][] = $val; 651 | } 652 | 653 | // Avoid this if possible. 654 | foreach ($queryinfo[$x + 1] as $key => $val) $vals[] = $val; 655 | 656 | $sql[$lastpos] .= "(" . implode(", ", $vals) . ")"; 657 | } 658 | } 659 | 660 | if (isset($supported["POSTVALUES"]) && !isset($queryinfo[3])) 661 | { 662 | foreach ($supported["POSTVALUES"] as $key => $mode) 663 | { 664 | if (isset($queryinfo[$key])) 665 | { 666 | if ($mode == "key_identifier" && isset($queryinfo[$key])) $sql .= " " . $key . " " . $this->QuoteIdentifier($queryinfo[$key]); 667 | } 668 | } 669 | } 670 | } 671 | else return array("success" => false, "error" => CSDB::DB_Translate("INSERT command is missing required option or parameter."), "errorcode" => "missing_option_or_parameter"); 672 | 673 | $opts = $args; 674 | 675 | return array("success" => true); 676 | } 677 | 678 | protected function ProcessUPDATE(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 679 | { 680 | $master = true; 681 | 682 | $sql = "UPDATE"; 683 | foreach ($supported["PRETABLE"] as $key => $mode) 684 | { 685 | if (isset($queryinfo[$key])) 686 | { 687 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 688 | } 689 | } 690 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 691 | $sql .= " " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 692 | 693 | $set = array(); 694 | $vals = array(); 695 | foreach ($queryinfo[1] as $key => $val) 696 | { 697 | $set[] = $this->QuoteIdentifier($key) . " = " . (is_bool($val) ? ($val ? "DEFAULT" : "NULL") : "?"); 698 | if (!is_bool($val)) $vals[] = $val; 699 | } 700 | $args = array_merge($vals, $args); 701 | 702 | // Avoid this if possible. 703 | if (isset($queryinfo[2])) 704 | { 705 | foreach ($queryinfo[2] as $key => $val) 706 | { 707 | $set[] = $this->QuoteIdentifier($key) . " = " . $val; 708 | } 709 | } 710 | 711 | $sql .= " SET " . implode(", ", $set); 712 | 713 | if (isset($queryinfo["WHERE"])) 714 | { 715 | if ($supported["WHERE"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 716 | { 717 | $result = $this->ProcessSubqueries($queryinfo["WHERE"], $master, $queryinfo["SUBQUERIES"]); 718 | if (!$result["success"]) return $result; 719 | } 720 | 721 | $sql .= " WHERE " . $queryinfo["WHERE"]; 722 | } 723 | else 724 | { 725 | // Attempt to detect accidental 'WHERE = ...' clauses. 726 | foreach ($queryinfo as $key => $val) 727 | { 728 | if (is_int($key) && is_string($val) && strtoupper(substr($val, 0, 5)) === "WHERE") return array("success" => false, "error" => CSDB::DB_Translate("UPDATE command appears to have a WHERE in a value instead of a key. Query blocked to avoid an unintentional change to the entire table. Did you write 'WHERE something = ...' instead of 'WHERE' => 'something = ...'?"), "errorcode" => "query_blocked_where_clause"); 729 | } 730 | } 731 | 732 | if (isset($supported["ORDER BY"]) && $supported["ORDER BY"] && isset($queryinfo["ORDER BY"])) $sql .= " ORDER BY " . $queryinfo["ORDER BY"]; 733 | if (isset($supported["LIMIT"]) && $supported["LIMIT"] !== false && !$subquery && isset($queryinfo["LIMIT"])) 734 | { 735 | if (is_array($queryinfo["LIMIT"])) $queryinfo["LIMIT"] = implode($supported["LIMIT"], $queryinfo["LIMIT"]); 736 | $sql .= " LIMIT " . $queryinfo["LIMIT"]; 737 | } 738 | 739 | $opts = $args; 740 | 741 | return array("success" => true); 742 | } 743 | 744 | protected function ProcessDELETE(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 745 | { 746 | $master = true; 747 | 748 | $sql = "DELETE"; 749 | foreach ($supported["PREFROM"] as $key => $mode) 750 | { 751 | if (isset($queryinfo[$key])) 752 | { 753 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 754 | } 755 | } 756 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 757 | $sql .= " FROM " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 758 | 759 | if (isset($queryinfo["WHERE"])) 760 | { 761 | if ($supported["WHERE"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 762 | { 763 | $result = $this->ProcessSubqueries($queryinfo["WHERE"], $master, $queryinfo["SUBQUERIES"]); 764 | if (!$result["success"]) return $result; 765 | } 766 | 767 | $sql .= " WHERE " . $queryinfo["WHERE"]; 768 | } 769 | else 770 | { 771 | // Attempt to detect accidental 'WHERE = ...' clauses. 772 | foreach ($queryinfo as $key => $val) 773 | { 774 | if (is_int($key) && is_string($val) && strtoupper(substr($val, 0, 5)) === "WHERE") return array("success" => false, "error" => CSDB::DB_Translate("DELETE command appears to have a WHERE in a value instead of a key. Query blocked to avoid an unintentional deletion of all records in the entire table. Did you write 'WHERE something = ...' instead of 'WHERE' => 'something = ...'?"), "errorcode" => "query_blocked_where_clause"); 775 | } 776 | } 777 | 778 | if (isset($supported["ORDER BY"]) && $supported["ORDER BY"] && isset($queryinfo["ORDER BY"])) $sql .= " ORDER BY " . $queryinfo["ORDER BY"]; 779 | if (isset($supported["LIMIT"]) && $supported["LIMIT"] !== false && !$subquery && isset($queryinfo["LIMIT"])) 780 | { 781 | if (is_array($queryinfo["LIMIT"])) $queryinfo["LIMIT"] = implode($supported["LIMIT"], $queryinfo["LIMIT"]); 782 | $sql .= " LIMIT " . $queryinfo["LIMIT"]; 783 | } 784 | 785 | $opts = $args; 786 | 787 | return array("success" => true); 788 | } 789 | 790 | protected function ProcessCREATE_TABLE(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 791 | { 792 | $master = true; 793 | 794 | if (isset($supported["TEMPORARY"]) && isset($queryinfo["TEMPORARY"]) && $queryinfo["TEMPORARY"]) $cmd = $supported["TEMPORARY"]; 795 | else $cmd = "CREATE TABLE"; 796 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 797 | $sql = $cmd . " " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 798 | 799 | if (isset($queryinfo["SELECT"])) 800 | { 801 | if (!isset($supported["AS_SELECT"]) || !$supported["AS_SELECT"]) return array("success" => false, CSDB::DB_Translate("CREATE TABLE AS SELECT not supported."), "create_table_select_unsupported"); 802 | 803 | $sql2 = ""; 804 | $opts2 = array(); 805 | $queryinfo2 = array_shift($queryinfo["SELECT"]); 806 | if (count($queryinfo["SELECT"]) == 1 && is_array($queryinfo["SELECT"][0])) $queryinfo["SELECT"] = $queryinfo["SELECT"][0]; 807 | $result = $this->GenerateSQL($master, $sql2, $opts2, "SELECT", $queryinfo2, $queryinfo["SELECT"], false); 808 | if (!$result["success"]) return $result; 809 | 810 | if (isset($supported["PRE_AS"])) 811 | { 812 | foreach ($supported["PRE_AS"] as $key => $mode) 813 | { 814 | if (isset($queryinfo[$key])) 815 | { 816 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 817 | } 818 | } 819 | } 820 | 821 | $sql .= " AS " . $sql2; 822 | } 823 | else 824 | { 825 | $sql2 = array(); 826 | foreach ($queryinfo[1] as $key => $info) 827 | { 828 | $sql3 = $this->QuoteIdentifier($key); 829 | $result = $this->ProcessColumnDefinition($info); 830 | if (!$result["success"]) return $result; 831 | 832 | $sql2[] = $sql3 . $result["sql"]; 833 | } 834 | 835 | if (isset($supported["PROCESSKEYS"]) && $supported["PROCESSKEYS"] && isset($queryinfo[2]) && is_array($queryinfo[2])) 836 | { 837 | foreach ($queryinfo[2] as $info) 838 | { 839 | $result = $this->ProcessKeyDefinition($info); 840 | if (!$result["success"]) return $result; 841 | 842 | if ($result["sql"] != "") $sql2[] = $result["sql"]; 843 | } 844 | } 845 | 846 | $sql .= " (\n"; 847 | if (count($sql2)) $sql .= "\t" . implode(",\n\t", $sql2) . "\n"; 848 | $sql .= ")"; 849 | foreach ($supported["POSTCREATE"] as $key => $mode) 850 | { 851 | if (isset($queryinfo[$key])) 852 | { 853 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 854 | else if ($mode == "string") $sql .= " " . $key . " " . $queryinfo[$key]; 855 | } 856 | } 857 | } 858 | 859 | $opts = $args; 860 | 861 | return array("success" => true); 862 | } 863 | 864 | protected function ProcessReferenceDefinition($info) 865 | { 866 | foreach ($info[1] as $num => $colname) $info[1][$num] = $this->QuoteIdentifier($colname); 867 | $sql = $this->QuoteIdentifier($info[0]) . " (" . implode(", ", $info[1]) . ")"; 868 | if (isset($info["MATCH FULL"]) && $info["MATCH FULL"]) $sql .= " MATCH FULL"; 869 | else if (isset($info["MATCH PARTIAL"]) && $info["MATCH PARTIAL"]) $sql .= " MATCH PARTIAL"; 870 | else if (isset($info["MATCH SIMPLE"]) && $info["MATCH SIMPLE"]) $sql .= " MATCH SIMPLE"; 871 | if (isset($info["ON DELETE"])) $sql .= " ON DELETE " . $info["ON DELETE"]; 872 | if (isset($info["ON UPDATE"])) $sql .= " ON UPDATE " . $info["ON UPDATE"]; 873 | 874 | return $sql; 875 | } 876 | 877 | protected static function DB_Translate() 878 | { 879 | $args = func_get_args(); 880 | if (!count($args)) return ""; 881 | 882 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 883 | } 884 | } 885 | 886 | class CSDB_PDO_Statement 887 | { 888 | private $db, $stmt, $filteropts; 889 | 890 | function __construct($db, $stmt, $filteropts) 891 | { 892 | $this->db = $db; 893 | $this->stmt = $stmt; 894 | $this->filteropts = $filteropts; 895 | } 896 | 897 | function __destruct() 898 | { 899 | $this->Free(); 900 | } 901 | 902 | function Free() 903 | { 904 | if ($this->stmt === false) return false; 905 | 906 | $this->stmt = false; 907 | 908 | return true; 909 | } 910 | 911 | function NextRow($fetchtype = PDO::FETCH_OBJ) 912 | { 913 | if ($this->stmt === false && $this->filteropts === false) return false; 914 | 915 | do 916 | { 917 | $fetchnext = false; 918 | $result = ($this->stmt !== false ? $this->stmt->fetch($this->filteropts === false ? $fetchtype : PDO::FETCH_OBJ) : false); 919 | if ($this->filteropts !== false) $this->db->RunRowFilter($result, $this->filteropts, $fetchnext); 920 | } while ($result !== false && $fetchnext); 921 | 922 | if ($result === false) $this->Free(); 923 | else if ($this->filteropts !== false && $fetchtype != PDO::FETCH_OBJ) 924 | { 925 | $result2 = array(); 926 | foreach ($result as $key => $val) 927 | { 928 | if ($fetchtype == PDO::FETCH_NUM || $fetchtype == PDO::FETCH_BOTH) $result2[] = $val; 929 | if ($fetchtype == PDO::FETCH_ASSOC || $fetchtype == PDO::FETCH_BOTH) $result2[$key] = $val; 930 | } 931 | 932 | $result = $result2; 933 | } 934 | 935 | return $result; 936 | } 937 | } 938 | ?> -------------------------------------------------------------------------------- /support/db_mysql_lite.php: -------------------------------------------------------------------------------- 1 | Query("SET", "NAMES 'utf8mb4'"); 25 | } 26 | 27 | public function GetInsertID($name = null) 28 | { 29 | return $this->GetOne("SELECT", array("LAST_INSERT_ID()")); 30 | } 31 | 32 | public function QuoteIdentifier($str) 33 | { 34 | return "`" . str_replace(array("`", "?"), array("``", ""), $str) . "`"; 35 | } 36 | 37 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 38 | { 39 | switch ($cmd) 40 | { 41 | case "SELECT": 42 | { 43 | $supported = array( 44 | "PRECOLUMN" => array("DISTINCT" => "bool", "HIGH_PRIORITY" => "bool", "SUBQUERIES" => true), 45 | "FROM" => array("SUBQUERIES" => true), 46 | "WHERE" => array("SUBQUERIES" => true), 47 | "GROUP BY" => true, 48 | "HAVING" => true, 49 | "ORDER BY" => true, 50 | "LIMIT" => ", " 51 | ); 52 | 53 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 54 | } 55 | case "INSERT": 56 | { 57 | $supported = array( 58 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 59 | "SELECT" => true, 60 | "BULKINSERT" => true 61 | ); 62 | 63 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 64 | } 65 | case "UPDATE": 66 | { 67 | $supported = array( 68 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 69 | "WHERE" => array("SUBQUERIES" => true), 70 | "ORDER BY" => true, 71 | "LIMIT" => ", " 72 | ); 73 | 74 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 75 | } 76 | case "DELETE": 77 | { 78 | $supported = array( 79 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 80 | "WHERE" => array("SUBQUERIES" => true), 81 | "ORDER BY" => true, 82 | "LIMIT" => ", " 83 | ); 84 | 85 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 86 | } 87 | case "SET": 88 | { 89 | $sql = "SET " . $queryinfo; 90 | 91 | return array("success" => true); 92 | } 93 | case "USE": 94 | { 95 | $sql = "USE " . $this->QuoteIdentifier($queryinfo); 96 | 97 | return array("success" => true); 98 | } 99 | case "TRUNCATE TABLE": 100 | { 101 | $master = true; 102 | 103 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 104 | 105 | return array("success" => true); 106 | } 107 | } 108 | 109 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 110 | } 111 | } 112 | ?> -------------------------------------------------------------------------------- /support/db_pgsql_lite.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | 27 | // Set Unicode support. 28 | $this->Query("SET", "client_encoding TO 'UTF-8'"); 29 | } 30 | 31 | public function GetInsertID($name = null) 32 | { 33 | return $this->lastid; 34 | } 35 | 36 | public function QuoteIdentifier($str) 37 | { 38 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 39 | } 40 | 41 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 42 | { 43 | switch ($cmd) 44 | { 45 | case "SELECT": 46 | { 47 | $supported = array( 48 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 49 | "FROM" => array("SUBQUERIES" => true), 50 | "WHERE" => array("SUBQUERIES" => true), 51 | "GROUP BY" => true, 52 | "HAVING" => true, 53 | "ORDER BY" => true, 54 | "LIMIT" => " OFFSET " 55 | ); 56 | 57 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 58 | } 59 | case "INSERT": 60 | { 61 | $supported = array( 62 | "PREINTO" => array(), 63 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 64 | "SELECT" => true, 65 | "BULKINSERT" => true 66 | ); 67 | 68 | // To get the last insert ID via GetInsertID(), the field that contains a 'serial' (auto increment) field must be specified. 69 | if (isset($queryinfo["AUTO INCREMENT"])) $queryinfo["RETURNING"] = $queryinfo["AUTO INCREMENT"]; 70 | 71 | $this->lastid = 0; 72 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 73 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 74 | 75 | return $result; 76 | } 77 | case "UPDATE": 78 | { 79 | // No ORDER BY or LIMIT support. 80 | $supported = array( 81 | "PRETABLE" => array("ONLY" => "bool"), 82 | "WHERE" => array("SUBQUERIES" => true) 83 | ); 84 | 85 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 86 | } 87 | case "DELETE": 88 | { 89 | // No ORDER BY or LIMIT support. 90 | $supported = array( 91 | "PREFROM" => array("ONLY" => "bool"), 92 | "WHERE" => array("SUBQUERIES" => true) 93 | ); 94 | 95 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 96 | } 97 | case "SET": 98 | { 99 | $sql = "SET " . $queryinfo; 100 | 101 | return array("success" => true); 102 | } 103 | case "USE": 104 | { 105 | // Fake multiple databases with PostgreSQL schemas. 106 | // http://www.postgresql.org/docs/7.3/static/ddl-schemas.html 107 | $sql = "SET search_path TO " . ($queryinfo != "" ? $this->QuoteIdentifier($queryinfo) . "," : "") . "\"\$user\",public"; 108 | 109 | return array("success" => true); 110 | } 111 | case "TRUNCATE TABLE": 112 | { 113 | $master = true; 114 | 115 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 116 | 117 | return array("success" => true); 118 | } 119 | } 120 | 121 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 122 | } 123 | 124 | protected function RunStatementFilter(&$stmt, &$filteropts) 125 | { 126 | if ($filteropts["mode"] == "INSERT") 127 | { 128 | // Force the last ID value to be extracted for INSERT queries. 129 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 130 | $row = $result->NextRow(); 131 | 132 | $stmt = false; 133 | } 134 | 135 | parent::RunStatementFilter($stmt, $filteropts); 136 | } 137 | 138 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 139 | { 140 | switch ($filteropts["mode"]) 141 | { 142 | case "INSERT": 143 | { 144 | if ($row !== false) 145 | { 146 | $key = $filteropts["queryinfo"]["AUTO INCREMENT"]; 147 | $this->lastid = $row->$key; 148 | } 149 | 150 | break; 151 | } 152 | } 153 | 154 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 155 | } 156 | } 157 | ?> -------------------------------------------------------------------------------- /support/dir_helper.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /support/en_us_lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "consonants": { "b": 50602, "c": 115771, "d": 90394, "f": 31570, "g": 74641, "h": 72887, "j": 4649, "k": 23626, "l": 146940, "m": 83734, "n": 190792, "p": 86539, "q": 4530, "r": 195116, "s": 261974, "t": 185092, "v": 25672, "w": 19402, "x": 8053, "z": 13142 }, 3 | "vowels": { "a": 222822, "e": 311897, "i": 255488, "o": 193425, "u": 92494, "y": 47630 } 4 | } -------------------------------------------------------------------------------- /support/process_helper.php: -------------------------------------------------------------------------------- 1 | true, 182 | "SCRIPT_NAME" => true, 183 | "SCRIPT_FILENAME" => true, 184 | "PATH_TRANSLATED" => true, 185 | "DOCUMENT_ROOT" => true, 186 | "REQUEST_TIME_FLOAT" => true, 187 | "REQUEST_TIME" => true, 188 | "argv" => true, 189 | "argc" => true, 190 | ); 191 | 192 | $result = array(); 193 | foreach ($_SERVER as $key => $val) 194 | { 195 | if (!isset($ignore[$key]) && is_string($val)) $result[$key] = $val; 196 | } 197 | 198 | return $result; 199 | } 200 | 201 | public static function ConnectTCPPipe($host, $port, $pipenum, $token) 202 | { 203 | $context = stream_context_create(); 204 | 205 | $fp = stream_socket_client("tcp://" . $host . ":" . (int)$port, $errornum, $errorstr, 3, STREAM_CLIENT_CONNECT, $context); 206 | if ($fp === false) return array("success" => false, "error" => "Unable to connect to the server.", "errorcode" => "connect_failed"); 207 | 208 | $result = fwrite($fp, $token . chr($pipenum)); 209 | if ($result != strlen($token) + 1) return array("success" => false, "error" => "Unable to send token data the server.", "errorcode" => "write_failed"); 210 | 211 | return array("success" => true, "fp" => $fp); 212 | } 213 | 214 | public static function StartTCPServer() 215 | { 216 | if (self::$serverfp === false) 217 | { 218 | // Oddly, this server starts up in about 0.002 seconds BUT calling fclose() on this handle takes 0.5 seconds. 219 | // So it doesn't really hurt to keep the server alive. 220 | self::$serverfp = stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); 221 | if (self::$serverfp === false) return array("success" => false, "error" => self::PHTranslate("Failed to start localhost TCP/IP server."), "errorcode" => "bind_failed"); 222 | 223 | stream_set_blocking(self::$serverfp, 0); 224 | } 225 | 226 | $info = stream_socket_get_name(self::$serverfp, false); 227 | $pos = strrpos($info, ":"); 228 | $ip = substr($info, 0, $pos); 229 | $port = (int)substr($info, $pos + 1); 230 | 231 | if (!class_exists("CSPRNG", false)) $token = bin2hex(random_bytes(64)); 232 | else 233 | { 234 | $rng = new CSPRNG(); 235 | $token = $rng->GenerateToken(); 236 | } 237 | 238 | return array("success" => true, "fp" => self::$serverfp, "ip" => $ip, "port" => $port, "token" => $token); 239 | } 240 | 241 | public static function GetTCPPipes(&$pipes, $servertoken, $proc, $waitfor = 0.5, $checkcallback = false) 242 | { 243 | $pipesleft = 0; 244 | foreach ($pipes as $val) 245 | { 246 | if ($val === false) $pipesleft++; 247 | } 248 | 249 | $servertokenlen = strlen($servertoken); 250 | 251 | $ts = microtime(true); 252 | $clients = array(); 253 | while ($pipesleft) 254 | { 255 | $readfps = array(self::$serverfp); 256 | foreach ($clients as $client) $readfps[] = $client->fp; 257 | $writefps = array(); 258 | $exceptfps = NULL; 259 | $result = @stream_select($readfps, $writefps, $exceptfps, 1); 260 | if ($result === false) break; 261 | 262 | if (in_array(self::$serverfp, $readfps) && ($fp = stream_socket_accept(self::$serverfp)) !== false) 263 | { 264 | stream_set_blocking($fp, 0); 265 | 266 | $client = new stdClass(); 267 | $client->fp = $fp; 268 | $client->data = ""; 269 | 270 | $clients[] = $client; 271 | } 272 | 273 | foreach ($clients as $num => $client) 274 | { 275 | $data = @fread($client->fp, $servertokenlen + 1 - strlen($client->data)); 276 | if ($data === false || ($data === "" && feof($client->fp))) 277 | { 278 | fclose($client->fp); 279 | 280 | unset($clients[$num]); 281 | } 282 | else 283 | { 284 | $client->data .= $data; 285 | 286 | if (strlen($client->data) == $servertokenlen + 1) 287 | { 288 | // Compare the input token to the one sent to the process. 289 | if (self::CTstrcmp($servertoken, substr($client->data, 0, -1))) fclose($client->fp); 290 | else 291 | { 292 | $num2 = ord(substr($client->data, -1)); 293 | 294 | if (!isset($pipes[$num2]) || $pipes[$num2] !== false) fclose($client->fp); 295 | else 296 | { 297 | $pipes[$num2] = $client->fp; 298 | 299 | $pipesleft--; 300 | 301 | $ts = microtime(true); 302 | } 303 | } 304 | 305 | unset($clients[$num]); 306 | } 307 | } 308 | } 309 | 310 | // If the process died, then bail out. 311 | if ($pipesleft && microtime(true) - $ts > $waitfor) 312 | { 313 | if ($proc !== false) 314 | { 315 | $pinfo = @proc_get_status($proc); 316 | if (!$pinfo["running"]) break; 317 | } 318 | else if (!is_callable($checkcallback) || !call_user_func_array($checkcallback, array($pipes))) 319 | { 320 | break; 321 | } 322 | 323 | $ts = microtime(true); 324 | } 325 | } 326 | 327 | if ($pipesleft) return array("success" => false, "error" => self::PHTranslate("The process started but failed to connect to the localhost TCP/IP server before terminating."), "errorcode" => "broken_tcp_pipe"); 328 | 329 | return array("success" => true); 330 | } 331 | 332 | // Starts a process with non-blocking pipes. Windows may require 'createprocess.exe' from: https://github.com/cubiclesoft/createprocess-windows 333 | // Non-blocking is required for scenarios when using more than one pipe or a deadlock can happen. 334 | // For example, one process blocks on stdin to be read while another is blocking on stdout to be read. 335 | public static function StartProcess($cmd, $options = array()) 336 | { 337 | $os = php_uname("s"); 338 | $windows = (strtoupper(substr($os, 0, 3)) == "WIN"); 339 | 340 | // Determine how input and output will be handled. 341 | $procpipes = array(); 342 | 343 | if (!isset($options["stdin"])) $options["stdin"] = true; 344 | if (!isset($options["stdout"])) $options["stdout"] = true; 345 | if (!isset($options["stderr"])) $options["stderr"] = true; 346 | 347 | if (!is_string($options["stdin"]) || $options["stdin"] !== "") 348 | { 349 | if (is_string($options["stdin"])) $procpipes[0] = array("file", $options["stdin"], "r"); 350 | else if (is_resource($options["stdin"]) || is_array($options["stdin"])) $procpipes[0] = $options["stdin"]; 351 | else if ($options["stdin"] === false) $procpipes[0] = array("file", ($windows ? "NUL" : "/dev/null"), "r"); 352 | else $procpipes[0] = array("pipe", "r"); 353 | } 354 | 355 | if (!is_string($options["stdout"]) || $options["stdout"] !== "") 356 | { 357 | if (is_string($options["stdout"])) $procpipes[1] = array("file", $options["stdout"], "w"); 358 | else if (is_resource($options["stdout"]) || is_array($options["stdout"])) $procpipes[1] = $options["stdout"]; 359 | else if ($options["stdout"] === false) $procpipes[1] = array("file", ($windows ? "NUL" : "/dev/null"), "w"); 360 | else $procpipes[1] = array("pipe", "w"); 361 | } 362 | 363 | if (!is_string($options["stderr"]) || $options["stderr"] !== "") 364 | { 365 | if (is_string($options["stderr"])) $procpipes[2] = array("file", $options["stderr"], "w"); 366 | else if (is_resource($options["stderr"]) || is_array($options["stderr"])) $procpipes[2] = $options["stderr"]; 367 | else if ($options["stderr"] === false) $procpipes[2] = array("file", ($windows ? "NUL" : "/dev/null"), "w"); 368 | else $procpipes[2] = array("pipe", "w"); 369 | } 370 | 371 | // Windows requires redirecting pipes through sockets so they can be configured to be non-blocking. 372 | if ($windows) 373 | { 374 | // Don't open a socket if the application really does want a pipe. 375 | if (!isset($options["tcpstdin"])) $options["tcpstdin"] = true; 376 | if (!isset($options["tcpstdout"])) $options["tcpstdout"] = true; 377 | if (!isset($options["tcpstderr"])) $options["tcpstderr"] = true; 378 | $tcpused = ((isset($procpipes[0]) && is_array($procpipes[0]) && $procpipes[0][0] === "pipe" && $options["tcpstdin"]) || (isset($procpipes[1]) && is_array($procpipes[1]) && $procpipes[1][0] === "pipe" && $options["tcpstdout"]) || (isset($procpipes[2]) && is_array($procpipes[2]) && $procpipes[2][0] === "pipe" && $options["tcpstderr"])); 379 | 380 | // Attempt to locate 'createprocess.exe'. 381 | if (isset($options["createprocess_exe"]) && !file_exists($options["createprocess_exe"])) unset($options["createprocess_exe"]); 382 | 383 | if (!isset($options["createprocess_exe"])) 384 | { 385 | $filename = str_replace("\\", "/", dirname(__FILE__)) . "/windows/createprocess.exe"; 386 | if (!file_exists($filename)) $filename = str_replace("\\", "/", dirname(__FILE__)) . "/createprocess.exe"; 387 | if (file_exists($filename)) $options["createprocess_exe"] = $filename; 388 | } 389 | 390 | if (!isset($options["createprocess_exe"])) 391 | { 392 | $filename = str_replace("\\", "/", dirname(__FILE__)) . "/windows/createprocess-win.exe"; 393 | if (!file_exists($filename)) $filename = str_replace("\\", "/", dirname(__FILE__)) . "/createprocess-win.exe"; 394 | if (file_exists($filename)) $options["createprocess_exe"] = $filename; 395 | } 396 | 397 | if (!isset($options["createprocess_exe"])) return array("success" => false, "error" => self::PHTranslate("Required executable 'createprocess.exe' or 'createprocess-win.exe' was not found. See: https://github.com/cubiclesoft/createprocess-windows"), "errorcode" => "missing_createprocess_exe"); 398 | 399 | $cmd2 = escapeshellarg(str_replace("/", "\\", $options["createprocess_exe"])); 400 | $cmd2 .= (isset($options["createprocess_exe_opts"]) ? " " . $options["createprocess_exe_opts"] : " /f=SW_HIDE /f=DETACHED_PROCESS") . " /w"; 401 | 402 | if ($tcpused) 403 | { 404 | $result = self::StartTCPServer(); 405 | if (!$result["success"]) return $result; 406 | 407 | $serverport = $result["port"]; 408 | $servertoken = $result["token"]; 409 | 410 | $cmd2 .= " /socketip=127.0.0.1 /socketport=" . $serverport . " /sockettoken=" . $servertoken; 411 | if (isset($procpipes[0]) && is_array($procpipes[0]) && $procpipes[0][0] === "pipe" && $options["tcpstdin"]) $cmd2 .= " /stdin=socket"; 412 | if (isset($procpipes[1]) && is_array($procpipes[1]) && $procpipes[1][0] === "pipe" && $options["tcpstdout"]) $cmd2 .= " /stdout=socket"; 413 | if (isset($procpipes[2]) && is_array($procpipes[2]) && $procpipes[2][0] === "pipe" && $options["tcpstderr"]) $cmd2 .= " /stderr=socket"; 414 | } 415 | 416 | $cmd = $cmd2 . " " . $cmd; 417 | } 418 | else if (function_exists("posix_geteuid")) 419 | { 420 | // Set effective user and group (*NIX only). 421 | $prevuid = posix_geteuid(); 422 | $prevgid = posix_getegid(); 423 | 424 | if (isset($options["user"])) 425 | { 426 | $userinfo = self::GetUserInfoByName($options["user"]); 427 | if ($userinfo !== false) 428 | { 429 | posix_seteuid($userinfo["uid"]); 430 | posix_setegid($userinfo["gid"]); 431 | } 432 | } 433 | 434 | if (isset($options["group"])) 435 | { 436 | $groupinfo = self::GetGroupInfoByName($options["group"]); 437 | if ($groupinfo !== false) posix_setegid($groupinfo["gid"]); 438 | } 439 | } 440 | 441 | // Start the process. 442 | if (!isset($options["env"])) $options["env"] = self::GetCleanEnvironment(); 443 | $proc = @proc_open($cmd, $procpipes, $pipes, (isset($options["dir"]) ? $options["dir"] : NULL), $options["env"], array("suppress_errors" => true, "bypass_shell" => true)); 444 | 445 | // Restore effective user and group. 446 | if (!$windows && function_exists("posix_geteuid")) 447 | { 448 | posix_seteuid($prevuid); 449 | posix_setegid($prevgid); 450 | } 451 | 452 | // Verify that the process started. 453 | if (!is_resource($proc)) return array("success" => false, "error" => self::PHTranslate("Failed to start the process."), "errorcode" => "proc_open_failed", "info" => array("cmd" => $cmd, "dir" => (isset($options["dir"]) ? $options["dir"] : NULL), "env" => $options["env"])); 454 | 455 | // Rebuild the pipes on Windows by waiting for a valid inbound TCP/IP connection for each pipe. 456 | if ($windows && $tcpused) 457 | { 458 | if (isset($procpipes[0]) && is_array($procpipes[0]) && $procpipes[0][0] === "pipe" && $options["tcpstdin"]) 459 | { 460 | fclose($pipes[0]); 461 | 462 | $pipes[0] = false; 463 | } 464 | if (isset($procpipes[1]) && is_array($procpipes[1]) && $procpipes[1][0] === "pipe" && $options["tcpstdout"]) 465 | { 466 | fclose($pipes[1]); 467 | 468 | $pipes[1] = false; 469 | } 470 | if (isset($procpipes[2]) && is_array($procpipes[2]) && $procpipes[2][0] === "pipe" && $options["tcpstderr"]) 471 | { 472 | fclose($pipes[2]); 473 | 474 | $pipes[2] = false; 475 | } 476 | 477 | $result = self::GetTCPPipes($pipes, $servertoken, $proc); 478 | if (!$result["success"]) 479 | { 480 | $result["info"] = array("cmd" => $cmd, "dir" => (isset($options["dir"]) ? $options["dir"] : NULL), "env" => $options["env"]); 481 | 482 | return $result; 483 | } 484 | } 485 | 486 | // Change all pipes to non-blocking. 487 | if (!isset($options["blocking"]) || !$options["blocking"]) 488 | { 489 | foreach ($pipes as $fp) stream_set_blocking($fp, 0); 490 | } 491 | 492 | $pinfo = @proc_get_status($proc); 493 | 494 | return array("success" => true, "proc" => $proc, "pid" => $pinfo["pid"], "pipes" => $pipes, "info" => array("cmd" => $cmd, "dir" => (isset($options["dir"]) ? $options["dir"] : NULL), "env" => $options["env"])); 495 | } 496 | 497 | public static function DisplayAllOutput($str) 498 | { 499 | echo $str; 500 | } 501 | 502 | public static function Wait($proc, &$pipes, $stdindata = "", $timeout = -1, $outputcallback = false) 503 | { 504 | $stdindata = (string)$stdindata; 505 | $stdoutdata = ""; 506 | $stdoutpos = 0; 507 | $stderrdata = ""; 508 | $stderrpos = 0; 509 | 510 | if ($outputcallback === true) $outputcallback = __CLASS__ . "::DisplayAllOutput"; 511 | 512 | $startts = microtime(true); 513 | do 514 | { 515 | $readfps = array(); 516 | if (isset($pipes[1])) $readfps[] = $pipes[1]; 517 | if (isset($pipes[2])) $readfps[] = $pipes[2]; 518 | 519 | $writefps = array(); 520 | if (isset($pipes[0])) 521 | { 522 | if ($stdindata !== "") $writefps[] = $pipes[0]; 523 | else 524 | { 525 | fclose($pipes[0]); 526 | 527 | unset($pipes[0]); 528 | } 529 | } 530 | 531 | $ts = microtime(true); 532 | if ($timeout < 0) $timeleft = false; 533 | else 534 | { 535 | $timeleft = $timeout - ($ts - $startts); 536 | if ($timeleft < 0) $timeleft = 0; 537 | } 538 | 539 | if (!count($readfps) && !count($writefps)) usleep(($timeleft !== false && $timeleft < 0.25 ? (int)($timeleft * 1000000) : 250000)); 540 | else 541 | { 542 | $exceptfps = NULL; 543 | if ($timeleft === false) $timeleft = 3; 544 | $result = @stream_select($readfps, $writefps, $exceptfps, ($timeleft > 1 ? 1 : 0), ($timeleft > 1 ? 0 : ($timeleft - (int)$timeleft) * 1000000)); 545 | if ($result === false) break; 546 | 547 | // Handle stdin. 548 | if (isset($pipes[0]) && $stdindata !== "") 549 | { 550 | $result = @fwrite($pipes[0], substr($stdindata, 0, 4096)); 551 | if ($result) $stdindata = (string)substr($stdindata, $result); 552 | } 553 | 554 | // Handle stdout. 555 | if (isset($pipes[1])) 556 | { 557 | $data = @fread($pipes[1], 65536); 558 | if ($data === false || ($data === "" && feof($pipes[1]))) 559 | { 560 | fclose($pipes[1]); 561 | 562 | unset($pipes[1]); 563 | } 564 | else 565 | { 566 | $stdoutdata .= $data; 567 | 568 | if (is_callable($outputcallback)) 569 | { 570 | if ($stderrpos < strlen($stderrdata) && strpos($stderrdata, "\n", $stderrpos) !== false && strpos($stdoutdata, "\n", $stdoutpos) !== false) 571 | { 572 | $pos = strrpos($stdoutdata, "\n") + 1; 573 | call_user_func($outputcallback, substr($stdoutdata, $stdoutpos, $pos - $stdoutpos), 1); 574 | $stdoutpos = $pos; 575 | 576 | $pos = strrpos($stderrdata, "\n") + 1; 577 | call_user_func($outputcallback, substr($stderrdata, $stderrpos, $pos - $stderrpos), 2); 578 | $stderrpos = $pos; 579 | } 580 | 581 | $pos = strlen($stdoutdata); 582 | call_user_func($outputcallback, substr($stdoutdata, $stdoutpos, $pos - $stdoutpos), 1); 583 | $stdoutpos = $pos; 584 | } 585 | } 586 | } 587 | 588 | // Handle stderr. 589 | if (isset($pipes[2])) 590 | { 591 | $data = @fread($pipes[2], 65536); 592 | if ($data === false || ($data === "" && feof($pipes[2]))) 593 | { 594 | fclose($pipes[2]); 595 | 596 | unset($pipes[2]); 597 | } 598 | else 599 | { 600 | $stderrdata .= $data; 601 | } 602 | } 603 | } 604 | 605 | // Verify that the process is stll running. 606 | if ($proc !== false) 607 | { 608 | $pinfo = @proc_get_status($proc); 609 | if (!$pinfo["running"]) 610 | { 611 | if (isset($pipes[0])) fclose($pipes[0]); 612 | unset($pipes[0]); 613 | 614 | $proc = false; 615 | } 616 | } 617 | 618 | if ($timeleft === 0) break; 619 | } while ($proc !== false || count($pipes)); 620 | 621 | if (is_callable($outputcallback) && $stderrpos < strlen($stderrdata)) call_user_func($outputcallback, substr($stderrdata, $stderrpos), 2); 622 | 623 | return array("success" => true, "proc" => $proc, "stdinleft" => $stdindata, "stdout" => $stdoutdata, "stderr" => $stderrdata); 624 | } 625 | 626 | public static function FindProcessIDsByFilename($filename) 627 | { 628 | $os = php_uname("s"); 629 | $windows = (strtoupper(substr($os, 0, 3)) == "WIN"); 630 | 631 | if ($windows) 632 | { 633 | $filename = str_replace("/", "\\", $filename); 634 | $fullpath = (strpos($filename, "\\") !== false); 635 | 636 | if (($exefile = self::FindExecutable("wmic.exe")) !== false) 637 | { 638 | // Included with Windows XP Pro and later. 639 | $cmd = escapeshellarg($exefile); 640 | $cmd .= " process get ExecutablePath,ProcessId"; 641 | 642 | @exec($cmd, $lines); 643 | 644 | $ids = array(); 645 | foreach ($lines as $line) 646 | { 647 | if (preg_match('/(\d+)$/', $line, $matches)) 648 | { 649 | $line = rtrim(substr($line, 0, -strlen($matches[1]))); 650 | if (!$fullpath) $line = substr($line, strrpos($line, "\\") + 1); 651 | 652 | if (strcasecmp($line, $filename) == 0) $ids[] = (int)$matches[1]; 653 | } 654 | } 655 | 656 | return array("success" => true, "ids" => $ids); 657 | } 658 | } 659 | else 660 | { 661 | $filename = str_replace("\\", "/", $filename); 662 | $fullpath = (strpos($filename, "/") !== false); 663 | 664 | $ps = self::FindExecutable("ps", "/bin"); 665 | 666 | $cmd = escapeshellarg($ps) . " -ax -o pid"; 667 | @exec($cmd, $lines); 668 | 669 | $ids = array(); 670 | foreach ($lines as $line) 671 | { 672 | if (preg_match('/^\s*?(\d+)\s*$/', $line, $matches)) 673 | { 674 | $pid = (int)$matches[1]; 675 | 676 | $line = @readlink("/proc/" . $pid . "/exe"); 677 | if (!$fullpath) $line = substr($line, strrpos($line, "/") + 1); 678 | 679 | if ($line === $filename) $ids[] = $pid; 680 | } 681 | } 682 | 683 | return array("success" => true, "ids" => $ids); 684 | } 685 | 686 | return array("success" => false, "error" => self::PHTranslate("Expected platform process tool not found."), "errorcode" => "missing_platform_process_tool"); 687 | } 688 | 689 | public static function TerminateProcess($id, $children = true, $force = true) 690 | { 691 | $id = (int)$id; 692 | $os = php_uname("s"); 693 | $windows = (strtoupper(substr($os, 0, 3)) == "WIN"); 694 | 695 | if ($windows) 696 | { 697 | if (($exefile = self::FindExecutable("taskkill.exe")) !== false) 698 | { 699 | // Included with Windows XP Pro and later. 700 | $cmd = escapeshellarg($exefile); 701 | if ($children) $cmd .= " /T"; 702 | if ($force) $cmd .= " /F"; 703 | $cmd .= " /PID " . $id . " 2>&1 > NUL"; 704 | 705 | ob_start(); 706 | @system($cmd); 707 | ob_end_clean(); 708 | 709 | return true; 710 | } 711 | else if (($exefile = self::FindExecutable("pskill.exe", __DIR__)) !== false) 712 | { 713 | // Gently terminating isn't possible with pskill. Taskkill is more frequently available these days though. 714 | $cmd = escapeshellarg($exefile); 715 | if ($children) $cmd .= " -t"; 716 | $cmd .= " " . $id . " 2>&1 > NUL"; 717 | 718 | ob_start(); 719 | @system($cmd); 720 | ob_end_clean(); 721 | 722 | return true; 723 | } 724 | } 725 | else 726 | { 727 | // Other OSes require parsing output from 'ps'. 728 | $ps = self::FindExecutable("ps", "/bin"); 729 | 730 | if ($ps !== false && (function_exists("posix_kill") || ($kill = self::FindExecutable("kill", "/bin")) !== false)) 731 | { 732 | $ids = array($id); 733 | 734 | if ($children) 735 | { 736 | $lines = array(); 737 | $cmd = escapeshellarg($ps) . " -ax -o ppid,pid"; 738 | @exec($cmd, $lines); 739 | 740 | $childmap = array(); 741 | foreach ($lines as $line) 742 | { 743 | if (preg_match('/^\s*?(\d+)\s+?(\d+)\s*$/', $line, $matches)) 744 | { 745 | $ppid = (int)$matches[1]; 746 | 747 | if (!isset($childmap[$ppid])) $childmap[$ppid] = array(); 748 | $childmap[$ppid][] = (int)$matches[2]; 749 | } 750 | } 751 | 752 | $ids2 = $ids; 753 | while (count($ids2)) 754 | { 755 | $id = array_shift($ids2); 756 | 757 | if (isset($childmap[$id])) 758 | { 759 | foreach ($childmap[$id] as $id2) 760 | { 761 | $ids[] = $id2; 762 | $ids2[] = $id2; 763 | } 764 | } 765 | } 766 | } 767 | 768 | foreach ($ids as $id) 769 | { 770 | if (function_exists("posix_kill")) posix_kill($id, ($force ? 9 : 15)); 771 | else 772 | { 773 | $cmd = escapeshellarg($kill) . ($force ? " -9" : "") . " " . $id . " 2>&1 >/dev/null"; 774 | 775 | ob_start(); 776 | @system($cmd); 777 | ob_end_clean(); 778 | } 779 | } 780 | 781 | return true; 782 | } 783 | } 784 | 785 | return false; 786 | } 787 | 788 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 789 | // Copied from string basics file. 790 | protected static function CTstrcmp($secret, $userinput) 791 | { 792 | $sx = 0; 793 | $sy = strlen($secret); 794 | $uy = strlen($userinput); 795 | $result = $sy - $uy; 796 | for ($ux = 0; $ux < $uy; $ux++) 797 | { 798 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 799 | $sx = ($sx + 1) % $sy; 800 | } 801 | 802 | return $result; 803 | } 804 | 805 | protected static function PHTranslate() 806 | { 807 | $args = func_get_args(); 808 | if (!count($args)) return ""; 809 | 810 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 811 | } 812 | } 813 | ?> -------------------------------------------------------------------------------- /support/random.php: -------------------------------------------------------------------------------- 1 | mode = false; 15 | $this->fp = false; 16 | $this->cryptosafe = $cryptosafe; 17 | 18 | // Native first (PHP 7 and later). 19 | if (function_exists("random_bytes")) $this->mode = "native"; 20 | 21 | // OpenSSL fallback. 22 | if ($this->mode === false && function_exists("openssl_random_pseudo_bytes")) 23 | { 24 | // PHP 5.4.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for performance. 25 | @openssl_random_pseudo_bytes(4, $strong); 26 | if ($strong) $this->mode = "openssl"; 27 | } 28 | 29 | // Locate a (relatively) suitable source of entropy or raise an exception. 30 | if (strtoupper(substr(PHP_OS, 0, 3)) === "WIN") 31 | { 32 | // PHP 5.3.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for functionality. 33 | if ($this->mode === false && PHP_VERSION_ID > 50300 && function_exists("mcrypt_create_iv")) $this->mode = "mcrypt"; 34 | } 35 | else 36 | { 37 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/arandom")) 38 | { 39 | // OpenBSD. mcrypt doesn't attempt to use this despite claims of higher quality entropy with performance. 40 | $this->fp = @fopen("/dev/arandom", "rb"); 41 | if ($this->fp !== false) $this->mode = "file"; 42 | } 43 | 44 | if ($cryptosafe && $this->mode === false && file_exists("/dev/random")) 45 | { 46 | // Everything else. 47 | $this->fp = @fopen("/dev/random", "rb"); 48 | if ($this->fp !== false) $this->mode = "file"; 49 | } 50 | 51 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/urandom")) 52 | { 53 | // Everything else. 54 | $this->fp = @fopen("/dev/urandom", "rb"); 55 | if ($this->fp !== false) $this->mode = "file"; 56 | } 57 | 58 | if ($this->mode === false && function_exists("mcrypt_create_iv")) 59 | { 60 | // mcrypt_create_iv() is last because it opens and closes a file handle every single call. 61 | $this->mode = "mcrypt"; 62 | } 63 | } 64 | 65 | // Throw an exception if unable to find a suitable entropy source. 66 | if ($this->mode === false) 67 | { 68 | throw new Exception(self::RNG_Translate("Unable to locate a suitable entropy source.")); 69 | exit(); 70 | } 71 | } 72 | 73 | public function __destruct() 74 | { 75 | if ($this->mode === "file") fclose($this->fp); 76 | } 77 | 78 | public function GetBytes($length) 79 | { 80 | if ($this->mode === false) return false; 81 | 82 | $length = (int)$length; 83 | if ($length < 1) return false; 84 | 85 | $result = ""; 86 | do 87 | { 88 | switch ($this->mode) 89 | { 90 | case "native": $data = @random_bytes($length); break; 91 | case "openssl": $data = @openssl_random_pseudo_bytes($length, $strong); if (!$strong) $data = false; break; 92 | case "mcrypt": $data = @mcrypt_create_iv($length, ($this->cryptosafe ? MCRYPT_DEV_RANDOM : MCRYPT_DEV_URANDOM)); break; 93 | case "file": $data = @fread($this->fp, $length); break; 94 | default: $data = false; 95 | } 96 | if ($data === false) return false; 97 | 98 | $result .= $data; 99 | } while (strlen($result) < $length); 100 | 101 | return substr($result, 0, $length); 102 | } 103 | 104 | public function GenerateToken($length = 64) 105 | { 106 | $data = $this->GetBytes($length); 107 | if ($data === false) return false; 108 | 109 | return bin2hex($data); 110 | } 111 | 112 | // Get a random number between $min and $max (inclusive). 113 | public function GetInt($min, $max) 114 | { 115 | $min = (int)$min; 116 | $max = (int)$max; 117 | if ($max < $min) return false; 118 | if ($min == $max) return $min; 119 | 120 | $range = $max - $min + 1; 121 | 122 | $bits = 1; 123 | while ((1 << $bits) <= $range) $bits++; 124 | 125 | $numbytes = (int)(($bits + 7) / 8); 126 | $mask = (1 << $bits) - 1; 127 | 128 | do 129 | { 130 | $data = $this->GetBytes($numbytes); 131 | if ($data === false) return false; 132 | 133 | $result = 0; 134 | for ($x = 0; $x < $numbytes; $x++) 135 | { 136 | $result = ($result * 256) + ord($data[$x]); 137 | } 138 | 139 | $result = $result & $mask; 140 | } while ($result >= $range); 141 | 142 | return $result + $min; 143 | } 144 | 145 | // Convenience method to generate a random alphanumeric string. 146 | public function GenerateString($size = 32) 147 | { 148 | $result = ""; 149 | for ($x = 0; $x < $size; $x++) 150 | { 151 | $data = $this->GetInt(0, 61); 152 | if ($data === false) return false; 153 | 154 | $result .= self::$alphanum[$data]; 155 | } 156 | 157 | return $result; 158 | } 159 | 160 | public function GenerateWordLite(&$freqmap, $len) 161 | { 162 | $totalc = 0; 163 | $totalv = 0; 164 | foreach ($freqmap["consonants"] as $chr => $num) $totalc += $num; 165 | foreach ($freqmap["vowels"] as $chr => $num) $totalv += $num; 166 | 167 | if ($totalc <= 0 || $totalv <= 0) return false; 168 | 169 | $result = ""; 170 | for ($x = 0; $x < $len; $x++) 171 | { 172 | if ($x % 2) 173 | { 174 | $data = $this->GetInt(0, $totalv - 1); 175 | if ($data === false) return false; 176 | 177 | foreach ($freqmap["vowels"] as $chr => $num) 178 | { 179 | if ($num > $data) 180 | { 181 | $result .= $chr; 182 | 183 | break; 184 | } 185 | 186 | $data -= $num; 187 | } 188 | } 189 | else 190 | { 191 | $data = $this->GetInt(0, $totalc - 1); 192 | if ($data === false) return false; 193 | 194 | foreach ($freqmap["consonants"] as $chr => $num) 195 | { 196 | if ($num > $data) 197 | { 198 | $result .= $chr; 199 | 200 | break; 201 | } 202 | 203 | $data -= $num; 204 | } 205 | } 206 | } 207 | 208 | return $result; 209 | } 210 | 211 | public function GenerateWord(&$freqmap, $len, $separator = "-") 212 | { 213 | $result = ""; 214 | $queue = array(); 215 | $threshold = $freqmap["threshold"]; 216 | $state = "start"; 217 | while ($len) 218 | { 219 | //echo $state . " - " . $len . ": " . $result . "\n"; 220 | switch ($state) 221 | { 222 | case "start": 223 | { 224 | // The start of the word (or restart). 225 | $path = &$freqmap["start"]; 226 | while (count($queue) < $threshold && $len) 227 | { 228 | if ($len > 1 || !$path["*"]) 229 | { 230 | // Some part of the word. 231 | $found = false; 232 | if ($path[""]) 233 | { 234 | $pos = $this->GetInt(0, $path[""] - 1); 235 | 236 | foreach ($path as $chr => &$info) 237 | { 238 | if (!is_array($info)) continue; 239 | 240 | if ($info["+"] > $pos) 241 | { 242 | $result .= $chr; 243 | $queue[] = $chr; 244 | $path = &$path[$chr]; 245 | $len--; 246 | 247 | $found = true; 248 | 249 | break; 250 | } 251 | 252 | $pos -= $info["+"]; 253 | } 254 | } 255 | 256 | if (!$found) 257 | { 258 | $state = (count($queue) ? "recovery" : "restart"); 259 | 260 | break; 261 | } 262 | } 263 | else 264 | { 265 | // Last letter of the word. 266 | $found = false; 267 | if ($path["*"]) 268 | { 269 | $pos = $this->GetInt(0, $path["*"] - 1); 270 | 271 | foreach ($path as $chr => &$info) 272 | { 273 | if (!is_array($info)) continue; 274 | 275 | if ($info["-"] > $pos) 276 | { 277 | $result .= $chr; 278 | $queue[] = $chr; 279 | $path = &$path[$chr]; 280 | $len--; 281 | 282 | $found = true; 283 | 284 | break; 285 | } 286 | 287 | $pos -= $info["-"]; 288 | } 289 | } 290 | 291 | if (!$found) 292 | { 293 | $state = (count($queue) ? "end" : "restart"); 294 | 295 | break; 296 | } 297 | } 298 | } 299 | 300 | if (count($queue) >= $threshold) $state = ($len >= $threshold ? "middle" : "end"); 301 | 302 | break; 303 | } 304 | case "middle": 305 | { 306 | // The middle of the word. 307 | $str = implode("", $queue); 308 | 309 | if (!isset($freqmap["middle"][$str])) $state = "recovery"; 310 | else 311 | { 312 | $found = false; 313 | 314 | if ($freqmap["middle"][$str][""]) 315 | { 316 | $pos = $this->GetInt(0, $freqmap["middle"][$str][""] - 1); 317 | 318 | foreach ($freqmap["middle"][$str] as $chr => $num) 319 | { 320 | if ($chr === "") continue; 321 | 322 | if ($num > $pos) 323 | { 324 | $result .= $chr; 325 | $queue[] = $chr; 326 | array_shift($queue); 327 | $len--; 328 | 329 | if ($len < $threshold) $state = "end"; 330 | 331 | $found = true; 332 | 333 | break; 334 | } 335 | 336 | $pos -= $num; 337 | } 338 | } 339 | 340 | if (!$found) $state = "recovery"; 341 | } 342 | 343 | break; 344 | } 345 | case "end": 346 | { 347 | if (!isset($freqmap["end"][$len]) || !count($queue) || !isset($freqmap["end"][$len][$queue[count($queue) - 1]])) $state = "restart"; 348 | else 349 | { 350 | $path = &$freqmap["end"][$len][$queue[count($queue) - 1]]; 351 | 352 | $found = false; 353 | 354 | if ($path[""]) 355 | { 356 | $pos = $this->GetInt(0, $path[""] - 1); 357 | 358 | foreach ($path as $str => $num) 359 | { 360 | if ($str === "") continue; 361 | 362 | if ($num > $pos) 363 | { 364 | $result .= $str; 365 | $len = 0; 366 | 367 | $found = true; 368 | 369 | break; 370 | } 371 | 372 | $pos -= $num; 373 | } 374 | } 375 | 376 | if (!$found) $state = "restart"; 377 | } 378 | 379 | break; 380 | } 381 | case "recovery": 382 | { 383 | if (!count($queue) || !isset($freqmap["recovery"][$queue[count($queue) - 1]])) $state = "restart"; 384 | else 385 | { 386 | $path = &$freqmap["recovery"][$queue[count($queue) - 1]]; 387 | 388 | $found = false; 389 | 390 | if ($path[""]) 391 | { 392 | $pos = $this->GetInt(0, $path[""] - 1); 393 | 394 | foreach ($path as $chr => $num) 395 | { 396 | if ($chr === "") continue; 397 | 398 | if ($num > $pos) 399 | { 400 | $result .= $chr; 401 | $queue[] = $chr; 402 | array_shift($queue); 403 | $len--; 404 | 405 | $state = ($len >= $threshold ? "middle" : "end"); 406 | 407 | $found = true; 408 | 409 | break; 410 | } 411 | 412 | $pos -= $num; 413 | } 414 | } 415 | 416 | if (!$found) $state = "restart"; 417 | } 418 | 419 | break; 420 | } 421 | case "restart": 422 | { 423 | $result .= $separator; 424 | $queue = array(); 425 | $len -= strlen($separator); 426 | 427 | $state = "start"; 428 | 429 | break; 430 | } 431 | } 432 | } 433 | 434 | return $result; 435 | } 436 | 437 | public function GetMode() 438 | { 439 | return $this->mode; 440 | } 441 | 442 | protected static function RNG_Translate() 443 | { 444 | $args = func_get_args(); 445 | if (!count($args)) return ""; 446 | 447 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 448 | } 449 | } 450 | ?> --------------------------------------------------------------------------------