$value) { $next = $key + 1; if (0 === strpos($value, $opt)) { if ($optLength === strlen($value) && isset($argv[$next])) { return trim($argv[$next]); } else { return trim(substr($value, $optLength + 1)); } } } return $default; } /** * Checks that user-supplied params are valid * * @param mixed $installDir The required istallation directory * @param mixed $cafile Certificate Authority file * * @return bool True if the supplied params are okay */ function checkParams($installDir, $cafile) { $result = true; if (false !== $installDir && !is_dir($installDir)) { out("The defined install dir ({$installDir}) does not exist.", 'info'); $result = false; } if (false !== $cafile && (!file_exists($cafile) || !is_readable($cafile))) { out("The defined Certificate Authority (CA) cert file ({$cafile}) does not exist or is not readable.", 'info'); $result = false; } return $result; } /** * Checks the platform for possible issues running the installer * * Errors are written to the output, warnings are saved for later display. * * @param array $warnings Populated by method, to be shown later * @param bool $quiet Quiet mode * @param bool $disableTls Bypass tls * * @return bool True if there are no errors */ function checkPlatform(&$warnings, $quiet, $disableTls) { getPlatformIssues($errors, $warnings); // Make openssl warning an error if tls has not been specifically disabled if (isset($warnings['openssl']) && !$disableTls) { $errors['openssl'] = $warnings['openssl']; unset($warnings['openssl']); } if (!empty($errors)) { out('Some settings on your machine make OctoberCMS unable to install properly.', 'error'); out('Make sure that you fix the issues listed below and run this script again:', 'error'); outputIssues($errors); return false; } if (empty($warnings) && !$quiet) { out('All settings correct for installing OctoberCMS', 'success'); } return true; } /** * Checks platform configuration for common incompatibility issues * * @param array $errors Populated by method * @param array $warnings Populated by method * * @return bool If any errors or warnings have been found */ function getPlatformIssues(&$errors, &$warnings) { $errors = array(); $warnings = array(); if ($iniPath = php_ini_loaded_file()) { $iniMessage = PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath; } else { $iniMessage = PHP_EOL.'A php.ini file does not exist. You will have to create one.'; } $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; if (ini_get('detect_unicode')) { $errors['unicode'] = array( 'The detect_unicode setting must be disabled.', 'Add the following to the end of your `php.ini`:', ' detect_unicode = Off', $iniMessage ); } if (!function_exists('json_decode')) { $errors['json'] = array( 'The json extension is missing.', 'Install it or recompile php without --disable-json' ); } if (!class_exists('ZipArchive')) { $errors['zip'] = array( 'The ZipArchive extension is missing.', 'Install it to continue' ); } if (!extension_loaded('filter')) { $errors['filter'] = array( 'The filter extension is missing.', 'Install it or recompile php without --disable-filter' ); } if (!extension_loaded('hash')) { $errors['hash'] = array( 'The hash extension is missing.', 'Install it or recompile php without --disable-hash' ); } if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { $errors['iconv_mbstring'] = array( 'The iconv OR mbstring extension is required and both are missing.', 'Install either of them or recompile php without --disable-iconv' ); } if (!ini_get('allow_url_fopen')) { $errors['allow_url_fopen'] = array( 'The allow_url_fopen setting is incorrect.', 'Add the following to the end of your `php.ini`:', ' allow_url_fopen = On', $iniMessage ); } if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { $ioncube = ioncube_loader_version(); $errors['ioncube'] = array( 'Your ionCube Loader extension ('.$ioncube.') is incompatible with Phar files.', 'Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:', ' zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so', $iniMessage ); } if (version_compare(PHP_VERSION, '5.3.2', '<')) { $errors['php'] = array( 'Your PHP ('.PHP_VERSION.') is too old, you must upgrade to PHP 5.3.2 or higher.' ); } if (version_compare(PHP_VERSION, '5.3.4', '<')) { $warnings['php'] = array( 'Your PHP ('.PHP_VERSION.') is quite old, upgrading to PHP 5.3.4 or higher is recommended.', 'This installer works with 5.3.2+ for most people, but there might be edge case issues.' ); } if (!extension_loaded('openssl')) { $warnings['openssl'] = array( 'The openssl extension is missing, which means that secure HTTPS transfers are impossible.', 'If possible you should enable it or recompile php with --with-openssl' ); } if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { // Attempt to parse version number out, fallback to whole string value. $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' ')); $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' ')); $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT; $warnings['openssl_version'] = array( 'The OpenSSL library ('.$opensslVersion.') used by PHP does not support TLSv1.2 or TLSv1.1.', 'If possible you should upgrade OpenSSL to version 1.0.1 or above.' ); } if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { $warnings['apc_cli'] = array( 'The apc.enable_cli setting is incorrect.', 'Add the following to the end of your `php.ini`:', ' apc.enable_cli = Off', $iniMessage ); } ob_start(); phpinfo(INFO_GENERAL); $phpinfo = ob_get_clean(); if (preg_match('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { $configure = $match[1]; if (false !== strpos($configure, '--enable-sigchild')) { $warnings['sigchild'] = array( 'PHP was compiled with --enable-sigchild which can cause issues on some platforms.', 'Recompile it without this flag if possible, see also:', ' https://bugs.php.net/bug.php?id=22999' ); } if (false !== strpos($configure, '--with-curlwrappers')) { $warnings['curlwrappers'] = array( 'PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.', 'Recompile it without this flag if possible' ); } } // Stringify the message arrays foreach ($errors as $key => $value) { $errors[$key] = PHP_EOL.implode(PHP_EOL, $value); } foreach ($warnings as $key => $value) { $warnings[$key] = PHP_EOL.implode(PHP_EOL, $value); } return !empty($errors) || !empty($warnings); } /** * Outputs an array of issues * * @param array $issues */ function outputIssues($issues) { foreach ($issues as $issue) { out($issue, 'info'); } out(''); } /** * Outputs any warnings found * * @param array $warnings */ function showWarnings($warnings) { if (!empty($warnings)) { out('Some settings on your machine may cause stability issues with OctoberCMS.', 'error'); out('If you encounter issues, try to change the following:', 'error'); outputIssues($warnings); } } /** * Outputs an end of process warning if tls has been bypassed * * @param bool $disableTls Bypass tls */ function showSecurityWarning($disableTls) { if ($disableTls) { out('You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.', 'info'); out('This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks', 'info'); } } /** * Installs October to the current working directory */ function installOctober($installDir, $quiet, $disableTls, $cafile, $channel) { $installDir = realpath($installDir) ? realpath($installDir) : getcwd(); $file = $installDir.DIRECTORY_SEPARATOR.'oc-core-installer-temp.pak'; if (is_readable($file)) { @unlink($file); } $home = getHomeDir(); if (!is_dir($home)) { @mkdir($home, 0777, true); } if (false === $disableTls && empty($cafile) && !HttpClient::getSystemCaRootBundlePath()) { $errorHandler = new ErrorHandler(); set_error_handler(array($errorHandler, 'handleError')); $target = $home . '/cacert.pem'; $write = file_put_contents($target, HttpClient::getPackagedCaFile(), LOCK_EX); @chmod($target, 0644); restore_error_handler(); if (!$write) { throw new RuntimeException('Unable to write bundled cacert.pem to: '.$target); } $cafile = $target; } $httpClient = new HttpClient($disableTls, $cafile); $uriScheme = false === $disableTls ? 'https' : 'http'; $downloadUrl = "/api/installer/".$channel; $retries = 3; while ($retries--) { if (!$quiet) { out("Downloading OctoberCMS...", 'info'); } $url = "{$uriScheme}://octobercms.com{$downloadUrl}"; $errorHandler = new ErrorHandler(); set_error_handler(array($errorHandler, 'handleError')); $fh = fopen($file, 'w'); if (!$fh) { out('Could not create file '.$file.': '.$errorHandler->message, 'error'); } if (!fwrite($fh, $httpClient->get($url))) { out('Download failed: '.$errorHandler->message, 'error'); } fclose($fh); restore_error_handler(); if ($errorHandler->message) { continue; } $zip = new ZipArchive; if ($zip->open($file) === true) { $zip->extractTo($installDir); $zip->close(); unlink($file); break; } else { unlink($file); if ($retries) { if (!$quiet) { out('Unable to extract zip file, retrying...', 'error'); } } else { out('Unable to extract zip file, aborting.', 'error'); exit(1); } } } if ($errorHandler->message) { out('The download failed repeatedly, aborting.', 'error'); exit(1); } if (!$quiet) { out(PHP_EOL."OctoberCMS successfully installed to: " . $installDir, 'success'); } } /** * colorize output */ function out($text, $color = null, $newLine = true) { $styles = array( 'success' => "\033[0;32m%s\033[0m", 'error' => "\033[31;31m%s\033[0m", 'info' => "\033[33;33m%s\033[0m" ); $format = '%s'; if (isset($styles[$color]) && USE_ANSI) { $format = $styles[$color]; } if ($newLine) { $format .= PHP_EOL; } printf($format, $text); } /** * Returns the system-dependent Composer home location, which may not exist. * We piggy back on Composer here, for OctoberCMS. * * @return string */ function getHomeDir() { $home = getenv('COMPOSER_HOME'); if (!$home) { $userDir = getUserDir(); if (defined('PHP_WINDOWS_VERSION_MAJOR')) { $home = $userDir.'/Composer'; } else { $home = $userDir.'/.composer'; if (!is_dir($home) && useXdg()) { // XDG Base Directory Specifications if (!($xdgConfig = getenv('XDG_CONFIG_HOME'))) { $xdgConfig = $userDir.'/.config'; } $home = $xdgConfig.'/composer'; } } } return $home; } /** * Returns the location of the user directory from the environment * @throws Runtime Exception If the environment value does not exists * * @return string */ function getUserDir() { $userEnv = defined('PHP_WINDOWS_VERSION_MAJOR') ? 'APPDATA' : 'HOME'; $userDir = getenv($userEnv); if (!$userDir) { throw new RuntimeException('The '.$userEnv.' or COMPOSER_HOME environment variable must be set for composer to run correctly'); } return rtrim(strtr($userDir, '\\', '/'), '/'); } /** * @return bool */ function useXdg() { foreach (array_keys($_SERVER) as $key) { if (substr($key, 0, 4) === 'XDG_') { return true; } } return false; } function validateCaFile($contents) { // assume the CA is valid if php is vulnerable to // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html if ( PHP_VERSION_ID <= 50327 || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422) || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506) ) { return !empty($contents); } return (bool) openssl_x509_parse($contents); } class ErrorHandler { public $message = ''; public function handleError($code, $msg) { if ($this->message) { $this->message .= "\n"; } $this->message .= preg_replace('{^copy\(.*?\): }', '', $msg); } } class HttpClient { private $options = array('http' => array()); private $disableTls = false; public function __construct($disableTls = false, $cafile = false) { $this->disableTls = $disableTls; if ($this->disableTls === false) { if (!empty($cafile) && !is_dir($cafile)) { if (!is_readable($cafile) || !validateCaFile(file_get_contents($cafile))) { throw new RuntimeException('The configured cafile (' .$cafile. ') was not valid or could not be read.'); } } $options = $this->getTlsStreamContextDefaults($cafile); $this->options = array_replace_recursive($this->options, $options); } } public function get($url) { $context = $this->getStreamContext($url); $result = file_get_contents($url, null, $context); if ($result && extension_loaded('zlib')) { $decode = false; foreach ($http_response_header as $header) { if (preg_match('{^content-encoding: *gzip *$}i', $header)) { $decode = true; continue; } elseif (preg_match('{^HTTP/}i', $header)) { $decode = false; } } if ($decode) { if (version_compare(PHP_VERSION, '5.4.0', '>=')) { $result = zlib_decode($result); } else { // work around issue with gzuncompress & co that do not work with all gzip checksums $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); } if (!$result) { throw new RuntimeException('Failed to decode zlib stream'); } } } return $result; } protected function getStreamContext($url) { if ($this->disableTls === false) { $host = parse_url($url, PHP_URL_HOST); if (PHP_VERSION_ID < 50600) { $this->options['ssl']['CN_match'] = $host; $this->options['ssl']['SNI_server_name'] = $host; } } // Keeping the above mostly isolated from the code copied from Composer. return $this->getMergedStreamContext($url); } protected function getTlsStreamContextDefaults($cafile) { $ciphers = implode(':', array( 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-DSS-AES128-GCM-SHA256', 'kEDH+AESGCM', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA', 'ECDHE-ECDSA-AES128-SHA', 'ECDHE-RSA-AES256-SHA384', 'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA', 'DHE-RSA-AES128-SHA256', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA256', 'DHE-RSA-AES256-SHA256', 'DHE-DSS-AES256-SHA', 'DHE-RSA-AES256-SHA', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'ECDHE-RSA-RC4-SHA', 'ECDHE-ECDSA-RC4-SHA', 'AES128', 'AES256', 'RC4-SHA', 'HIGH', '!aNULL', '!eNULL', '!EXPORT', '!DES', '!3DES', '!MD5', '!PSK' )); /** * CN_match and SNI_server_name are only known once a URL is passed. * They will be set in the getOptionsForUrl() method which receives a URL. * * cafile or capath can be overridden by passing in those options to constructor. */ $options = array( 'ssl' => array( 'ciphers' => $ciphers, 'verify_peer' => true, 'verify_depth' => 7, 'SNI_enabled' => true, ) ); /** * Attempt to find a local cafile or throw an exception. * The user may go download one if this occurs. */ if (!$cafile) { $cafile = self::getSystemCaRootBundlePath(); } if (is_dir($cafile)) { $options['ssl']['capath'] = $cafile; } elseif ($cafile) { $options['ssl']['cafile'] = $cafile; } else { throw new RuntimeException('A valid cafile could not be located automatically.'); } /** * Disable TLS compression to prevent CRIME attacks where supported. */ if (version_compare(PHP_VERSION, '5.4.13') >= 0) { $options['ssl']['disable_compression'] = true; } return $options; } /** * function copied from Composer\Util\StreamContextFactory::getContext * * Any changes should be applied there as well, or backported here. * * @param string $url URL the context is to be used for * @return resource Default context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled */ protected function getMergedStreamContext($url) { $options = $this->options; // Handle system proxy if (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy'])) { // Some systems seem to rely on a lowercased version instead... $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); } if (!empty($proxy)) { $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : ''; $proxyURL .= isset($proxy['host']) ? $proxy['host'] : ''; if (isset($proxy['port'])) { $proxyURL .= ":" . $proxy['port']; } elseif ('http://' == substr($proxyURL, 0, 7)) { $proxyURL .= ":80"; } elseif ('https://' == substr($proxyURL, 0, 8)) { $proxyURL .= ":443"; } // http(s):// is not supported in proxy $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL); if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) { throw new RuntimeException('You must enable the openssl extension to use a proxy over https'); } $options['http'] = array( 'proxy' => $proxyURL, ); // enabled request_fulluri unless it is explicitly disabled switch (parse_url($url, PHP_URL_SCHEME)) { case 'http': // default request_fulluri to true $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { $options['http']['request_fulluri'] = true; } break; case 'https': // default request_fulluri to true $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { $options['http']['request_fulluri'] = true; } break; } if (isset($proxy['user'])) { $auth = urldecode($proxy['user']); if (isset($proxy['pass'])) { $auth .= ':' . urldecode($proxy['pass']); } $auth = base64_encode($auth); $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n"; } } if (isset($options['http']['header'])) { $options['http']['header'] .= "Connection: close\r\n"; } else { $options['http']['header'] = "Connection: close\r\n"; } if (extension_loaded('zlib')) { $options['http']['header'] .= "Accept-Encoding: gzip\r\n"; } $options['http']['header'] .= "User-Agent: OctoberCMS Installer\r\n"; $options['http']['protocol_version'] = 1.1; return stream_context_create($options); } /** * This method was adapted from Sslurp. * https://github.com/EvanDotPro/Sslurp * * (c) Evan Coury * * For the full copyright and license information, please see below: * * Copyright (c) 2013, Evan Coury * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ public static function getSystemCaRootBundlePath() { static $caPath = null; if ($caPath !== null) { return $caPath; } // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. $envCertFile = getenv('SSL_CERT_FILE'); if ($envCertFile && is_readable($envCertFile) && validateCaFile(file_get_contents($envCertFile))) { return $caPath = $envCertFile; } // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. $envCertDir = getenv('SSL_CERT_DIR'); if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) { return $caPath = $envCertDir; } $configured = ini_get('openssl.cafile'); if ($configured && strlen($configured) > 0 && is_readable($configured) && validateCaFile(file_get_contents($configured))) { return $caPath = $configured; } $configured = ini_get('openssl.capath'); if ($configured && is_dir($configured) && is_readable($configured)) { return $caPath = $configured; } $caBundlePaths = array( '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) '/usr/ssl/certs/ca-bundle.crt', // Cygwin '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? '/etc/ssl/cert.pem', // OpenBSD '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x ); foreach ($caBundlePaths as $caBundle) { if (@is_readable($caBundle) && validateCaFile(file_get_contents($caBundle))) { return $caPath = $caBundle; } } foreach ($caBundlePaths as $caBundle) { $caBundle = dirname($caBundle); if (is_dir($caBundle) && glob($caBundle.'/*')) { return $caPath = $caBundle; } } return $caPath = false; } public static function getPackagedCaFile() { return <<