CHANGELOG
@@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== - Interactive update script with improved DB schema check - jQuery 1.5.1 - Fix problem with contactgroupmembers table creation on MySQL 4.x, add index on contact_id column - Add LDAP SASL bind and proxy authentication (#1486692) SQL/mssql.upgrade.sql
@@ -97,7 +97,7 @@ ALTER TABLE [dbo].[contacts] ALTER COLUMN [email] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL GO -- Updates from version 0.5.x -- Updates from version 0.5.1 ALTER TABLE [dbo].[contacts] ADD [words] [text] COLLATE Latin1_General_CI_AI NULL GO SQL/mysql.update.sql
@@ -133,7 +133,7 @@ TRUNCATE TABLE `messages`; -- Updates from version 0.5.* -- Updates from version 0.5.1 ALTER TABLE `contacts` ADD `words` TEXT NULL AFTER `vcard`; ALTER TABLE `contactgroupmembers` ADD INDEX `contactgroupmembers_contact_index` (`contact_id`); SQL/postgres.update.sql
@@ -90,7 +90,7 @@ TRUNCATE messages; -- Updates from version 0.5.x -- Updates from version 0.5.1 ALTER TABLE contacts ADD words TEXT NULL; CREATE INDEX contactgroupmembers_contact_id_idx ON contactgroupmembers (contact_id); SQL/sqlite.update.sql
@@ -183,7 +183,7 @@ DELETE FROM messages; -- Updates from version 0.5.x -- Updates from version 0.5.1 CREATE TABLE contacts_tmp ( contact_id integer NOT NULL PRIMARY KEY, UPGRADING
@@ -5,6 +5,26 @@ of Roundcube Webmail. We recommend to carefully backup the existing installation as well as the database before executig the following steps. Using the update script ----------------------- There is a shell script (for unix based systems) that does the job for you. To use it, unpack the archive of the new Roundcube version to a temporary location (don't replace the Roundcube installation you want to update) and cd into that directory. From there, run the following command in a shell: ./bin/installto.sh <TARGET-FOLDER> For <TARGET-FOLDER> you specify the path to the Roundcube installation which should be updated. The update script will then copy all new files to the target location and check and update the configuration and database schema. After all is done, the temporary folder with the new Roundcube files can be removed again. Updating manually ----------------- If you don't have shell access to the Roundcube instalaltion or if not running it on a unix system, you need to do the following operations by hand: 1. Replace index.php and all files in - ./bin/ - ./SQL/ @@ -14,14 +34,11 @@ - ./plugins/ 2. Run ./bin/update.sh from the commandline OR open http://url-to-roundcube/installer/ in a browser and choose "3 Test config". To enable the latter one, you have to temporary set 'enable_installer' to true in your local config/main.inc.php file. To enable the latter one, you have to temporary set 'enable_installer' to true in your local config/main.inc.php file. 3. Let the update script/installer check your configuration and update your config files as suggested by the updater. 4. If suggested by the update script, run all commands in ./SQL/[yourdbtype].update.sql that are superscribed with the currently installed version number. 5. Make sure 'enable_installer' is set to false again. 6. Check .htaccess settings (some php settings could become required) update your config files and database schema as suggested by the updater. 4. Make sure 'enable_installer' is set to false again. 5. Check .htaccess settings (some php settings could become required) bin/installto.sh
New file @@ -0,0 +1,73 @@ #!/usr/bin/env php <?php /* +-----------------------------------------------------------------------+ | bin/installto.sh | | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2011, The Roundcube Dev Team | | Licensed under the GNU GPL | | | | PURPOSE: | | Update an existing Roundcube installation with files from | | this version | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | +-----------------------------------------------------------------------+ $Id$ */ if (php_sapi_name() != 'cli') { die('Not on the "shell" (php-cli).'); } define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' ); require_once INSTALL_PATH . 'program/include/iniset.php'; $target_dir = unslashify($_SERVER['argv'][1]); if (empty($target_dir) || !is_dir(realpath($target_dir))) die("Invalid target: not a directory\nUsage: installto.sh <TARGET>\n"); // read version from iniset.php $iniset = @file_get_contents($target_dir . '/program/include/iniset.php'); if (!preg_match('/define\(.RCMAIL_VERSION.,\s*.([0-9.]+[a-z-]*)/', $iniset, $m)) die("No valid Roundcube installation found at $target_dir\n"); $oldversion = $m[1]; if (version_compare($oldversion, RCMAIL_VERSION, '>=')) die("Installation at target location is up-to-date!\n"); echo "Upgrading from $oldversion. Do you want to continue? (y/N)\n"; $input = trim(fgets(STDIN)); if (strtolower($input) == 'y') { $err = false; echo "Copying files to target location..."; foreach (array('program','installer','bin','SQL','plugins','skins/default') as $dir) { if (!system("rsync -avuC " . INSTALL_PATH . "$dir/* $target_dir/$dir/")) { $err = true; break; } } foreach (array('index.php','.htaccess','config/main.inc.php.dist','config/db.inc.php.dist','CHANGELOG','README','UPGRADING') as $file) { if (!system("rsync -avu " . INSTALL_PATH . "$file $target_dir/$file")) { $err = true; break; } } echo "done.\n\n"; if (!$err) { echo "Running update script at target...\n"; system("cd $target_dir && bin/update.sh --version=$oldversion"); echo "All done.\n"; } } else echo "Update cancelled. See ya!\n"; ?> bin/update.sh
@@ -1,12 +1,45 @@ #!/usr/bin/env php <?php /* +-----------------------------------------------------------------------+ | bin/update.sh | | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2010-2011, The Roundcube Dev Team | | Licensed under the GNU GPL | | | | PURPOSE: | | Check local configuration and database schema after upgrading | | to a new version | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | +-----------------------------------------------------------------------+ $Id$ */ if (php_sapi_name() != 'cli') { die('Not on the "shell" (php-cli).'); } define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' ); require_once INSTALL_PATH . 'program/include/iniset.php'; require_once INSTALL_PATH . 'program/include/clisetup.php'; require_once INSTALL_PATH . 'installer/rcube_install.php'; // get arguments $opts = get_opt(array('v' => 'version')); // ask user if no version is specified if (!$opts['version']) { echo "What version are you upgrading from? Type '?' if you don't know.\n"; if (($input = trim(fgets(STDIN))) && preg_match('/^[0-9.]+[a-z-]*$/', $input)) $opts['version'] = $input; } if ($opts['version'] && version_compare($opts['version'], RCMAIL_VERSION, '>')) die("Nothing to be done here. Bye!\n"); $RCI = rcube_install::get_instance(); $RCI->load_config(); @@ -88,7 +121,7 @@ } } else { echo "Please update your config files manually according to the above messages.\n"; echo "Please update your config files manually according to the above messages.\n\n"; } } @@ -113,12 +146,22 @@ echo "Error connecting to database: $db_error_msg\n"; $success = false; } else if ($RCI->db_schema_check($DB, false)) { $db_map = array('pgsql' => 'postgres', 'mysqli' => 'mysql', 'sqlsrv' => 'mssql'); $updatefile = INSTALL_PATH . 'SQL/' . (isset($db_map[$DB->db_provider]) ? $db_map[$DB->db_provider] : $DB->db_provider) . '.update.sql'; else if ($err = $RCI->db_schema_check($DB, false)) { $updatefile = INSTALL_PATH . 'SQL/' . (isset($RCI->db_map[$DB->db_provider]) ? $RCI->db_map[$DB->db_provider] : $DB->db_provider) . '.update.sql'; echo "WARNING: Database schema needs to be updated!\n"; echo "Open $updatefile and execute all queries that are superscribed with the currently installed version number\n"; echo join("\n", $err) . "\n\n"; $success = false; if ($opts['version']) { echo "Do you want to run the update queries to get the schmea fixed? (y/N)\n"; $input = trim(fgets(STDIN)); if (strtolower($input) == 'y') { $success = $RCI->update_db($DB, $opts['version']); } } if (!$success) echo "Open $updatefile and execute all queries below the comment with the currently installed version number.\n"; } } installer/check.php
@@ -22,6 +22,7 @@ 'PEAR' => 'PEAR.php', 'MDB2' => 'MDB2.php', 'Net_SMTP' => 'Net/SMTP.php', 'Net_IDNA2' => 'Net/IDNA2.php', 'Mail_mime' => 'Mail/mime.php', ); installer/images/banner_bg.gifBinary files differ
installer/images/banner_gradient.gif
installer/images/banner_logo.gifBinary files differ
installer/images/banner_right.gifBinary files differ
installer/images/banner_schraffur.gif
installer/images/rcube_logo.gif
installer/index.php
@@ -68,14 +68,13 @@ <body> <div id="banner"> <div id="header"> <div class="banner-logo"><a href="http://www.roundcube.net"><img src="images/banner_logo.gif" width="200" height="56" border="0" alt="Roundcube Webmal Project" /></a></div> <div class="banner-right"><img src="images/banner_right.gif" width="10" height="56" alt="" /></div> </div> <div id="topnav"> <a href="http://trac.roundcube.net/wiki/Howto_Install">How-to Wiki</a> </div> </div> <div class="banner-bg"></div> <div class="banner-logo"><a href="http://roundcube.net"><img src="images/rcube_logo.gif" width="210" height="55" border="0" alt="Roundcube - Open source webmail project" /></a></div> </div> <div id="topnav"> <a href="http://trac.roundcube.net/wiki/Howto_Install">How-to Wiki</a> </div> <div id="content"> @@ -120,7 +119,7 @@ </div> <div id="footer"> Installer by the Roundcube Dev Team. Copyright © 2008 - Published under the GNU Public License; Installer by the Roundcube Dev Team. Copyright © 2008-2011 - Published under the GNU Public License; Icons by <a href="http://famfamfam.com">famfamfam</a> </div> </body> installer/rcube_install.php
@@ -29,10 +29,11 @@ var $config = array(); var $configured = false; var $last_error = null; var $db_map = array('pgsql' => 'postgres', 'mysqli' => 'mysql', 'sqlsrv' => 'mssql'); var $email_pattern = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9])'; var $bool_config_props = array(); var $obsolete_config = array('db_backend'); var $obsolete_config = array('db_backend', 'double_auth'); var $replaced_config = array( 'skin_path' => 'skin', 'locale_string' => 'language', @@ -42,7 +43,10 @@ ); // these config options are required for a working system var $required_config = array('db_dsnw', 'db_table_contactgroups', 'db_table_contactgroupmembers', 'des_key'); var $required_config = array( 'db_dsnw', 'db_table_contactgroups', 'db_table_contactgroupmembers', 'des_key', 'session_lifetime', ); /** * Constructor @@ -294,7 +298,7 @@ $this->config = array(); $this->load_defaults(); foreach ($this->replaced_config as $prop => $replacement) foreach ($this->replaced_config as $prop => $replacement) { if (isset($current[$prop])) { if ($prop == 'skin_path') $this->config[$replacement] = preg_replace('#skins/(\w+)/?$#', '\\1', $current[$prop]); @@ -302,8 +306,8 @@ $this->config[$replacement] = $current[$prop] ? 2 : 0; else $this->config[$replacement] = $current[$prop]; unset($current[$prop]); } unset($current[$prop]); } foreach ($this->obsolete_config as $prop) { @@ -319,6 +323,9 @@ } } } if ($current['keep_alive'] && $current['session_lifetime'] < $current['keep_alive']) $current['session_lifetime'] = max(10, ceil($current['keep_alive'] / 60) * 2); $this->config = array_merge($this->config, $current); @@ -339,18 +346,8 @@ if (!$this->configured) return false; // simple ad hand-made db schema $db_schema = array( 'users' => array(), 'identities' => array(), 'contacts' => array(), 'contactgroups' => array(), 'contactgroupmembers' => array(), 'cache' => array(), 'messages' => array(), 'session' => array(), ); // read reference schema from mysql.initial.sql $db_schema = $this->db_read_schema(INSTALL_PATH . 'SQL/mysql.initial.sql'); $errors = array(); // check list of tables @@ -358,13 +355,43 @@ foreach ($db_schema as $table => $cols) { $table = !empty($this->config['db_table_'.$table]) ? $this->config['db_table_'.$table] : $table; if (!in_array($table, $existing_tables)) $errors[] = "Missing table ".$table; // TODO: check cols and indices if (!in_array($table, $existing_tables)) { $errors[] = "Missing table '".$table."'"; } else { // compare cols $db_cols = $DB->list_cols($table); $diff = array_diff(array_keys($cols), $db_cols); if (!empty($diff)) $errors[] = "Missing columns in table '$table': " . join(',', $diff); } } return !empty($errors) ? $errors : false; } /** * Utility function to read database schema from an .sql file */ private function db_read_schema($schemafile) { $lines = file($schemafile); $table_block = false; $schema = array(); foreach ($lines as $line) { if (preg_match('/^\s*create table `?([a-z0-9_]+)`?/i', $line, $m)) { $table_block = $m[1]; } else if ($table_block && preg_match('/^\s*`?([a-z0-9_-]+)`?\s+([a-z]+)/', $line, $m)) { $col = $m[1]; if (!in_array(strtoupper($col), array('PRIMARY','KEY','INDEX','UNIQUE','CONSTRAINT','REFERENCES','FOREIGN'))) { $schema[$table_block][$col] = $m[2]; } } } return $schema; } /** * Compare the local database schema with the reference schema @@ -472,6 +499,16 @@ } return $out; } /** * Create a HTML dropdown to select a previous version of Roundcube */ function versions_select($attrib = array()) { $select = new html_select($attrib); $select->add(array('0.1-stable', '0.1.1', '0.2-alpha', '0.2-beta', '0.2-stable', '0.3-stable', '0.3.1', '0.4-beta', '0.4.2', '0.5-beta', '0.5', '0.5.1')); return $select; } @@ -592,25 +629,12 @@ */ function init_db($DB) { $db_map = array('pgsql' => 'postgres', 'mysqli' => 'mysql'); $engine = isset($db_map[$DB->db_provider]) ? $db_map[$DB->db_provider] : $DB->db_provider; $engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider; // read schema file from /SQL/* $fname = "../SQL/$engine.initial.sql"; if ($lines = @file($fname, FILE_SKIP_EMPTY_LINES)) { $buff = ''; foreach ($lines as $i => $line) { if (preg_match('/^--/', $line)) continue; $buff .= $line . "\n"; if (preg_match('/;$/', trim($line))) { $DB->query($buff); $buff = ''; if ($this->get_error()) break; } } $fname = INSTALL_PATH . "SQL/$engine.initial.sql"; if ($sql = @file_get_contents($fname)) { $this->exec_sql($sql, $DB); } else { $this->fail('DB Schema', "Cannot read the schema file: $fname"); @@ -625,6 +649,78 @@ return true; } /** * Update database with SQL statements from SQL/*.update.sql * * @param object rcube_db Database connection * @param string Version to update from * @return boolen True on success, False on error */ function update_db($DB, $version) { $version = strtolower($version); $engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider; // read schema file from /SQL/* $fname = INSTALL_PATH . "SQL/$engine.update.sql"; if ($lines = @file($fname, FILE_SKIP_EMPTY_LINES)) { $from = false; $sql = ''; foreach ($lines as $line) { $is_comment = preg_match('/^--/', $line); if (!$from && $is_comment && preg_match('/from version\s([0-9.]+[a-z-]*)/', $line, $m)) { $v = strtolower($m[1]); if ($v == $version || version_compare($version, $v, '<=')) $from = true; } if ($from && !$is_comment) $sql .= $line. "\n"; } if ($sql) $this->exec_sql($sql, $DB); } else { $this->fail('DB Schema', "Cannot read the update file: $fname"); return false; } if ($err = $this->get_error()) { $this->fail('DB Schema', "Error updating database: $err"); return false; } return true; } /** * Execute the given SQL queries on the database connection * * @param string SQL queries to execute * @param object rcube_db Database connection * @return boolen True on success, False on error */ function exec_sql($sql, $DB) { $buff = ''; foreach (explode("\n", $sql) as $line) { if (preg_match('/^--/', $line) || trim($line) == '') continue; $buff .= $line . "\n"; if (preg_match('/(;|^GO)$/', trim($line))) { $DB->query($buff); $buff = ''; if ($DB->is_error()) break; } } return !$DB->is_error(); } /** * Handler for Roundcube errors */ installer/styles.css
@@ -1,62 +1,53 @@ body { margin: 1em 2em 2em 2em; background-color: #fff; } body, td, th, div, p { font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; font-size: small; color: #000; background: white; font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; font-size: small; color: black; margin: 0; } #banner { position: relative; position: relative; height: 58px; margin: 0 0 1em 0; padding: 10px 20px; background: url('images/banner_gradient.gif') top left repeat-x #d8edfd; overflow: hidden; } #header { position: relative; height: 56px; background: url('images/banner_bg.gif') top left repeat-x #fff; #banner .banner-bg { position: absolute; top: 0; right: 0; width: 630px; height: 78px; background: url('images/banner_schraffur.gif') top right no-repeat; z-index: 0; } #header div.banner-logo { position: absolute; top: 0px; left: 0px; width: 200px; height: 56px; #banner .banner-logo { position: absolute; top: 10px; left: 20px; z-index: 4; } #header div.banner-right { position: absolute; right: 0px; top: 0px; width: 10px; height: 56px; #banner .banner-logo a { border: 0; } #topnav { position: absolute; right: 20px; bottom: 8px; text-align: right; color: #ebebeb; font-size: smaller; position: absolute; top: 3.6em; right: 20px; } #topnav a { color: #ebebeb; font-size: 11px; text-decoration: none; } #topnav a:hover { text-decoration: underline; color: #666; } #content { margin: 8px 20px; margin: 2em 20px; } #footer { installer/test.php
@@ -156,6 +156,14 @@ } } else if ($db_working && $_POST['updatedb']) { if (!($success = $RCI->update_db($DB, $_POST['version']))) { $updatefile = INSTALL_PATH . 'SQL/' . (isset($RCI->db_map[$DB->db_provider]) ? $RCI->db_map[$DB->db_provider] : $DB->db_provider) . '.update.sql'; echo '<p class="warning">Please manually execute the SQL statements from '.$updatefile.' on your database.<br/>'; echo 'See comments in the file and execute queries below the comment with the currently installed version number.</p>'; } } // test database if ($db_working) { $db_read = $DB->query("SELECT count(*) FROM {$RCI->config['db_table_users']}"); @@ -164,12 +172,13 @@ echo '<p><input type="submit" name="initdb" value="Initialize database" /></p>'; $db_working = false; } else if ($RCI->db_schema_check($DB, $update = !empty($_POST['updatedb']))) { else if ($err = $RCI->db_schema_check($DB, $update = !empty($_POST['updatedb']))) { $RCI->fail('DB Schema', "Database schema differs"); $db_map = array('pgsql' => 'postgres', 'mysqli' => 'mysql', 'sqlsrv' => 'mssql'); $updatefile = INSTALL_PATH . 'SQL/' . (isset($db_map[$DB->db_provider]) ? $db_map[$DB->db_provider] : $DB->db_provider) . '.update.sql'; echo '<p class="warning">Please manually execute the SQL statements from '.$updatefile.' on your database.<br/>'; echo 'See comments in the file and execute queries that are superscribed with the currently installed version number.</p>'; echo '<ul style="margin:0"><li>' . join("</li>\n<li>", $err) . "</li></ul>"; $select = $RCI->versions_select(array('name' => 'version')); echo '<p class="suggestion">You should run the update queries to get the schmea fixed.<br/><br/>Version to update from: ' . $select->show() . ' <input type="submit" name="updatedb" value="Update" /></p>'; // echo '<p class="warning">Please manually execute the SQL statements from '.$updatefile.' on your database.<br/>'; // echo 'See comments in the file and execute queries that are superscribed with the currently installed version number.</p>'; $db_working = false; } else { @@ -412,7 +421,7 @@ After completing the installation and the final tests please <b>remove</b> the whole installer folder from the document root of the webserver or make sure that enable_installer option in main.inc.php is disabled.<br /> <tt>enable_installer</tt> option in config/main.inc.php is disabled.<br /> <br /> These files may expose sensitive configuration data like server passwords and encryption keys program/include/rcube_mdb2.php
@@ -413,6 +413,23 @@ /** * Wrapper for SHOW COLUMNS command * * @param string Table name * @return array List of table cols */ function list_cols($table) { $this->db_handle->loadModule('Manager'); if (!PEAR::isError($result = $this->db_handle->listTableFields($table))) { return $result; } return null; } /** * Formats input so it can be safely used in a query * * @param mixed $input Value to quote program/lib/MDB2/Driver/Reverse/sqlite.php
@@ -95,7 +95,7 @@ return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, 'unexpected empty table column definition list', __FUNCTION__); } $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|TINYINT|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; $regexp2 = '/^\s*([^ ]+) +(PRIMARY|UNIQUE|CHECK)$/i'; for ($i=0, $j=0; $i<$count; ++$i) { if (!preg_match($regexp, trim($column_sql[$i]), $matches)) {