/** * Check files are writable - make them writable if necessary... * * @param array $files */ function makeFilesWritable(&$files) { global $upcontext; if (empty($files)) { return true; } $failure = false; // On linux, it's easy - just use is_writable! if (substr(__FILE__, 1, 2) != ':\\') { foreach ($files as $k => $file) { if (!is_writable($file)) { @chmod($file, 0755); // Well, 755 hopefully worked... if not, try 777. if (!is_writable($file) && !@chmod($file, 0777)) { $failure = true; } else { unset($files[$k]); } } else { unset($files[$k]); } } } else { foreach ($files as $k => $file) { // Folders can't be opened for write... but the index.php in them can ;). if (is_dir($file)) { $file .= '/index.php'; } // Funny enough, chmod actually does do something on windows - it removes the read only attribute. @chmod($file, 0777); $fp = @fopen($file, 'r+'); // Hmm, okay, try just for write in that case... if (!$fp) { $fp = @fopen($file, 'w'); } if (!$fp) { $failure = true; } else { unset($files[$k]); } @fclose($fp); } } if (empty($files)) { return true; } if (!isset($_SERVER)) { return !$failure; } // What still needs to be done? $upcontext['chmod']['files'] = $files; // If it's windows it's a mess... if ($failure && substr(__FILE__, 1, 2) == ':\\') { $upcontext['chmod']['ftp_error'] = 'total_mess'; return false; } elseif ($failure) { // Load any session data we might have... if (!isset($_POST['ftp_username']) && isset($_SESSION['installer_temp_ftp'])) { $upcontext['chmod']['server'] = $_SESSION['installer_temp_ftp']['server']; $upcontext['chmod']['port'] = $_SESSION['installer_temp_ftp']['port']; $upcontext['chmod']['username'] = $_SESSION['installer_temp_ftp']['username']; $upcontext['chmod']['password'] = $_SESSION['installer_temp_ftp']['password']; $upcontext['chmod']['path'] = $_SESSION['installer_temp_ftp']['path']; } elseif (isset($_POST['ftp_username'])) { $upcontext['chmod']['server'] = $_POST['ftp_server']; $upcontext['chmod']['port'] = $_POST['ftp_port']; $upcontext['chmod']['username'] = $_POST['ftp_username']; $upcontext['chmod']['password'] = $_POST['ftp_password']; $upcontext['chmod']['path'] = $_POST['ftp_path']; } if (isset($upcontext['chmod']['username'])) { $ftp = new Ftp_Connection($upcontext['chmod']['server'], $upcontext['chmod']['port'], $upcontext['chmod']['username'], $upcontext['chmod']['password']); if ($ftp->error === false) { // Try it without /home/abc just in case they messed up. if (!$ftp->chdir($upcontext['chmod']['path'])) { $upcontext['chmod']['ftp_error'] = $ftp->last_message; $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $upcontext['chmod']['path'])); } } } if (!isset($ftp) || $ftp->error !== false) { if (!isset($ftp)) { $ftp = new Ftp_Connection(null); } elseif ($ftp->error !== false && !isset($upcontext['chmod']['ftp_error'])) { $upcontext['chmod']['ftp_error'] = $ftp->last_message === null ? '' : $ftp->last_message; } list($username, $detect_path, $found_path) = $ftp->detect_path(dirname(__FILE__)); if ($found_path || !isset($upcontext['chmod']['path'])) { $upcontext['chmod']['path'] = $detect_path; } if (!isset($upcontext['chmod']['username'])) { $upcontext['chmod']['username'] = $username; } return false; } else { // We want to do a relative path for FTP. if (!in_array($upcontext['chmod']['path'], array('', '/'))) { $ftp_root = strtr(BOARDDIR, array($upcontext['chmod']['path'] => '')); if (substr($ftp_root, -1) == '/' && ($upcontext['chmod']['path'] == '' || $upcontext['chmod']['path'][0] === '/')) { $ftp_root = substr($ftp_root, 0, -1); } } else { $ftp_root = BOARDDIR; } // Save the info for next time! $_SESSION['installer_temp_ftp'] = array('server' => $upcontext['chmod']['server'], 'port' => $upcontext['chmod']['port'], 'username' => $upcontext['chmod']['username'], 'password' => $upcontext['chmod']['password'], 'path' => $upcontext['chmod']['path'], 'root' => $ftp_root); foreach ($files as $k => $file) { if (!is_writable($file)) { $ftp->chmod($file, 0755); } if (!is_writable($file)) { $ftp->chmod($file, 0777); } // Assuming that didn't work calculate the path without the boarddir. if (!is_writable($file)) { if (strpos($file, BOARDDIR) === 0) { $ftp_file = strtr($file, array($_SESSION['installer_temp_ftp']['root'] => '')); $ftp->chmod($ftp_file, 0755); if (!is_writable($file)) { $ftp->chmod($ftp_file, 0777); } // Sometimes an extra slash can help... $ftp_file = '/' . $ftp_file; if (!is_writable($file)) { $ftp->chmod($ftp_file, 0755); } if (!is_writable($file)) { $ftp->chmod($ftp_file, 0777); } } } if (is_writable($file)) { unset($files[$k]); } } $ftp->close(); } } // What remains? $upcontext['chmod']['files'] = $files; if (empty($files)) { return true; } return false; }
/** * Use FTP functions to work with a package download/install * * @package Packages * @param string $destination_url * @param string[]|null $files = none * @param bool $return = false */ function packageRequireFTP($destination_url, $files = null, $return = false) { global $context, $modSettings, $package_ftp, $txt; // Try to make them writable the manual way. if ($files !== null) { foreach ($files as $k => $file) { // If this file doesn't exist, then we actually want to look at the directory, no? if (!file_exists($file)) { $file = dirname($file); } // This looks odd, but it's an attempt to work around PHP suExec. if (!@is_writable($file)) { @chmod($file, 0755); } if (!@is_writable($file)) { @chmod($file, 0777); } if (!@is_writable(dirname($file))) { @chmod($file, 0755); } if (!@is_writable(dirname($file))) { @chmod($file, 0777); } $fp = is_dir($file) ? @opendir($file) : @fopen($file, 'rb'); if (@is_writable($file) && $fp) { unset($files[$k]); if (!is_dir($file)) { fclose($fp); } else { closedir($fp); } } } // No FTP required! if (empty($files)) { return array(); } } // They've opted to not use FTP, and try anyway. if (isset($_SESSION['pack_ftp']) && $_SESSION['pack_ftp'] == false) { if ($files === null) { return array(); } foreach ($files as $k => $file) { // This looks odd, but it's an attempt to work around PHP suExec. if (!file_exists($file)) { mktree(dirname($file), 0755); @touch($file); @chmod($file, 0755); } if (!@is_writable($file)) { @chmod($file, 0777); } if (!@is_writable(dirname($file))) { @chmod(dirname($file), 0777); } if (@is_writable($file)) { unset($files[$k]); } } return $files; } elseif (isset($_SESSION['pack_ftp'])) { // Load the file containing the Ftp_Connection class. require_once SUBSDIR . '/FtpConnection.class.php'; $package_ftp = new Ftp_Connection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], package_crypt($_SESSION['pack_ftp']['password'])); if ($files === null) { return array(); } foreach ($files as $k => $file) { $ftp_file = strtr($file, array($_SESSION['pack_ftp']['root'] => '')); // This looks odd, but it's an attempt to work around PHP suExec. if (!file_exists($file)) { mktree(dirname($file), 0755); $package_ftp->create_file($ftp_file); $package_ftp->chmod($ftp_file, 0755); } // Still not writable, true full permissions if (!@is_writable($file)) { $package_ftp->chmod($ftp_file, 0777); } // Directory not writable, try to chmod to 777 then if (!@is_writable(dirname($file))) { $package_ftp->chmod(dirname($ftp_file), 0777); } if (@is_writable($file)) { unset($files[$k]); } } return $files; } if (isset($_POST['ftp_none'])) { $_SESSION['pack_ftp'] = false; $files = packageRequireFTP($destination_url, $files, $return); return $files; } elseif (isset($_POST['ftp_username'])) { // Attempt to make a new FTP connection require_once SUBSDIR . '/FtpConnection.class.php'; $ftp = new Ftp_Connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); if ($ftp->error === false) { // Common mistake, so let's try to remedy it... if (!$ftp->chdir($_POST['ftp_path'])) { $ftp_error = $ftp->last_message; $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); } } } if (!isset($ftp) || $ftp->error !== false) { if (!isset($ftp)) { require_once SUBSDIR . '/FtpConnection.class.php'; $ftp = new Ftp_Connection(null); } elseif ($ftp->error !== false && !isset($ftp_error)) { $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message; } list($username, $detect_path, $found_path) = $ftp->detect_path(BOARDDIR); if ($found_path) { $_POST['ftp_path'] = $detect_path; } elseif (!isset($_POST['ftp_path'])) { $_POST['ftp_path'] = isset($modSettings['package_path']) ? $modSettings['package_path'] : $detect_path; } if (!isset($_POST['ftp_username'])) { $_POST['ftp_username'] = $username; } $context['package_ftp'] = array('server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'), 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'), 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''), 'path' => $_POST['ftp_path'], 'error' => empty($ftp_error) ? null : $ftp_error, 'destination' => $destination_url); // If we're returning dump out here. if ($return) { return $files; } $context['page_title'] = $txt['package_ftp_necessary']; $context['sub_template'] = 'ftp_required'; obExit(); } else { if (!in_array($_POST['ftp_path'], array('', '/'))) { $ftp_root = strtr(BOARDDIR, array($_POST['ftp_path'] => '')); if (substr($ftp_root, -1) == '/' && ($_POST['ftp_path'] == '' || $_POST['ftp_path'][0] == '/')) { $ftp_root = substr($ftp_root, 0, -1); } } else { $ftp_root = BOARDDIR; } $_SESSION['pack_ftp'] = array('server' => $_POST['ftp_server'], 'port' => $_POST['ftp_port'], 'username' => $_POST['ftp_username'], 'password' => package_crypt($_POST['ftp_password']), 'path' => $_POST['ftp_path'], 'root' => $ftp_root); if (!isset($modSettings['package_path']) || $modSettings['package_path'] != $_POST['ftp_path']) { updateSettings(array('package_path' => $_POST['ftp_path'])); } $files = packageRequireFTP($destination_url, $files, $return); } return $files; }
/** * Verify and try to make writable the files and folders that need to be. */ function action_checkFilesWritable() { global $txt, $incontext; $incontext['page_title'] = $txt['ftp_checking_writable']; $incontext['sub_template'] = 'chmod_files'; $writable_files = array('attachments', 'avatars', 'cache', 'packages', 'packages/installed.list', 'smileys', 'themes', 'agreement.txt', 'db_last_error.php', 'Settings.php', 'Settings_bak.php'); foreach ($incontext['detected_languages'] as $lang => $temp) { $extra_files[] = 'themes/default/languages/' . $lang; } // With mod_security installed, we could attempt to fix it with .htaccess. if (function_exists('apache_get_modules') && in_array('mod_security', apache_get_modules())) { $writable_files[] = file_exists(dirname(__FILE__) . '/.htaccess') ? '.htaccess' : '.'; } $failed_files = array(); // On linux, it's easy - just use is_writable! if (substr(__FILE__, 1, 2) != ':\\') { foreach ($writable_files as $file) { if (!is_writable(dirname(__FILE__) . '/' . $file)) { @chmod(dirname(__FILE__) . '/' . $file, 0755); // Well, 755 hopefully worked... if not, try 777. if (!is_writable(dirname(__FILE__) . '/' . $file) && !@chmod(dirname(__FILE__) . '/' . $file, 0777)) { $failed_files[] = $file; } } } foreach ($extra_files as $file) { @chmod(dirname(__FILE__) . (empty($file) ? '' : '/' . $file), 0777); } } else { foreach ($writable_files as $file) { // Folders can't be opened for write... but the index.php in them can ;) if (is_dir(dirname(__FILE__) . '/' . $file)) { $file .= '/index.php'; } // Funny enough, chmod actually does do something on windows - it removes the read only attribute. @chmod(dirname(__FILE__) . '/' . $file, 0777); $fp = @fopen(dirname(__FILE__) . '/' . $file, 'r+'); // Hmm, okay, try just for write in that case... if (!is_resource($fp)) { $fp = @fopen(dirname(__FILE__) . '/' . $file, 'w'); } if (!is_resource($fp)) { $failed_files[] = $file; } @fclose($fp); } foreach ($extra_files as $file) { @chmod(dirname(__FILE__) . (empty($file) ? '' : '/' . $file), 0777); } } $failure = count($failed_files) >= 1; if (!isset($_SERVER)) { return !$failure; } // Put the list into context. $incontext['failed_files'] = $failed_files; // It's not going to be possible to use FTP on windows to solve the problem... if ($failure && substr(__FILE__, 1, 2) == ':\\') { $incontext['error'] = $txt['error_windows_chmod'] . ' <ul style="margin: 2.5ex; font-family: monospace;"> <li>' . implode('</li> <li>', $failed_files) . '</li> </ul>'; return false; } elseif ($failure) { // Load any session data we might have... if (!isset($_POST['ftp_username']) && isset($_SESSION['installer_temp_ftp'])) { $_POST['ftp_server'] = $_SESSION['installer_temp_ftp']['server']; $_POST['ftp_port'] = $_SESSION['installer_temp_ftp']['port']; $_POST['ftp_username'] = $_SESSION['installer_temp_ftp']['username']; $_POST['ftp_password'] = $_SESSION['installer_temp_ftp']['password']; $_POST['ftp_path'] = $_SESSION['installer_temp_ftp']['path']; } $incontext['ftp_errors'] = array(); if (isset($_POST['ftp_username'])) { $ftp = new Ftp_Connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); if ($ftp->error === false) { // Try it without /home/abc just in case they messed up. if (!$ftp->chdir($_POST['ftp_path'])) { $incontext['ftp_errors'][] = $ftp->last_message; $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); } } } if (!isset($ftp) || $ftp->error !== false) { if (!isset($ftp)) { $ftp = new Ftp_Connection(null); } elseif ($ftp->error !== false && empty($incontext['ftp_errors']) && !empty($ftp->last_message)) { $incontext['ftp_errors'][] = $ftp->last_message; } list($username, $detect_path, $found_path) = $ftp->detect_path(dirname(__FILE__)); if (empty($_POST['ftp_path']) && $found_path) { $_POST['ftp_path'] = $detect_path; } if (!isset($_POST['ftp_username'])) { $_POST['ftp_username'] = $username; } // Set the username etc, into context. $incontext['ftp'] = array('server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : 'localhost', 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : '21', 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : '', 'path' => isset($_POST['ftp_path']) ? $_POST['ftp_path'] : '/', 'path_msg' => !empty($found_path) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info']); return false; } else { $_SESSION['installer_temp_ftp'] = array('server' => $_POST['ftp_server'], 'port' => $_POST['ftp_port'], 'username' => $_POST['ftp_username'], 'password' => $_POST['ftp_password'], 'path' => $_POST['ftp_path']); $failed_files_updated = array(); foreach ($failed_files as $file) { if (!is_writable(dirname(__FILE__) . '/' . $file)) { $ftp->chmod($file, 0755); } if (!is_writable(dirname(__FILE__) . '/' . $file)) { $ftp->chmod($file, 0777); } if (!is_writable(dirname(__FILE__) . '/' . $file)) { $failed_files_updated[] = $file; $incontext['ftp_errors'][] = rtrim($ftp->last_message) . ' -> ' . $file . "\n"; } } $ftp->close(); // Are there any errors left? if (count($failed_files_updated) >= 1) { // Guess there are... $incontext['failed_files'] = $failed_files_updated; // Set the username etc, into context. $incontext['ftp'] = $_SESSION['installer_temp_ftp'] += array('path_msg' => $txt['ftp_path_info']); return false; } } } return true; }
/** * This method attempts to chmod packages and installed.list * * - uses FTP if necessary. * - It sets the $context['package_download_broken'] status for the template. * - Used by package servers pages. */ public function ftp_connect() { global $context, $modSettings; // Try to chmod from PHP first @chmod(BOARDDIR . '/packages', 0777); @chmod(BOARDDIR . '/packages/installed.list', 0777); $unwritable = !is_writable(BOARDDIR . '/packages') || !is_writable(BOARDDIR . '/packages/installed.list'); // Let's initialize $context $context['package_ftp'] = array('server' => '', 'port' => '', 'username' => '', 'path' => '', 'error' => ''); if ($unwritable) { // Are they connecting to their FTP account already? if (isset($_POST['ftp_username'])) { require_once SUBSDIR . '/FtpConnection.class.php'; $ftp = new Ftp_Connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); if ($ftp->error === false) { // I know, I know... but a lot of people want to type /home/xyz/... which is wrong, but logical. if (!$ftp->chdir($_POST['ftp_path'])) { $ftp_error = $ftp->error; $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); } } } // No attempt yet, or we had an error last time if (!isset($ftp) || $ftp->error !== false) { // Maybe we didn't even try yet if (!isset($ftp)) { require_once SUBSDIR . '/FtpConnection.class.php'; $ftp = new Ftp_Connection(null); } elseif ($ftp->error !== false && !isset($ftp_error)) { $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message; } list($username, $detect_path, $found_path) = $ftp->detect_path(BOARDDIR); if ($found_path || !isset($_POST['ftp_path'])) { $_POST['ftp_path'] = $detect_path; } if (!isset($_POST['ftp_username'])) { $_POST['ftp_username'] = $username; } // Fill the boxes for a FTP connection with data from the previous attempt too, if any $context['package_ftp'] = array('server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'), 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'), 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''), 'path' => $_POST['ftp_path'], 'error' => empty($ftp_error) ? null : $ftp_error); // Announce the template it's time to display the ftp connection box. $context['package_download_broken'] = true; } else { // FTP connection has succeeded $context['package_download_broken'] = false; // Try to chmod packages folder and our list file. $ftp->chmod('packages', 0777); $ftp->chmod('packages/installed.list', 0777); $ftp->close(); } } }