#!/usr/bin/perl # i-MSCP - internet Multi Server Control Panel # Copyright (C) 2010-2015 by internet Multi Server Control Panel # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. use strict; use warnings; use FindBin; use lib "$FindBin::Bin/..", "$FindBin::Bin/../PerlLib", "$FindBin::Bin/../PerlVendor"; use iMSCP::Debug; use iMSCP::Config; use iMSCP::Bootstrapper; use iMSCP::Dialog; use iMSCP::Stepper; use iMSCP::Database; use iMSCP::Crypt; use iMSCP::File; use iMSCP::Dir; use iMSCP::SystemUser; use iMSCP::SystemGroup; use iMSCP::Execute; use iMSCP::TemplateParser; use iMSCP::Service; use File::Basename; use Data::Dumper; use version; # Re-encrypt the given password with i-MSCP database key sub _reencryptPassword($$$$$) { my ($tableName, $idFieldName, $pwdFieldName, $row, $errors) = @_; my $crypt = iMSCP::Crypt->getInstance(); # Switch to ispCP database key $crypt->set('key', $main::ispcpDBKey); $crypt->set('iv', $main::ispcpDBiv); # Decrypt password using ispCP database key my $password = $crypt->decrypt_db_password($row->{$pwdFieldName}); # Switch to i-MSCP database keys $crypt->set('key', $main::imscpDBKey); $crypt->set('iv', $main::imscpDBiv); # Encrypt password using i-MSCP database key $password = $crypt->encrypt_db_password($password); my $database = iMSCP::Database->factory(); my $result = $database->doQuery( 'dummy', "UPDATE `$tableName` SET `$pwdFieldName` = ? WHERE `$idFieldName` = ?", $password, $row->{$idFieldName} ); unless(ref $result eq 'HASH'){ push @{$errors}, $result; } undef; } # Connect to the ispCP database sub _ispCPdbConnect { my $crypt = iMSCP::Crypt->getInstance(); $crypt->set('key', $main::ispcpDBKey); $crypt->set('iv', $main::ispcpDBiv); my $database = iMSCP::Database->factory(); $database->set('DATABASE_NAME', $main::ispcpConfig{'DATABASE_NAME'}); $database->set('DATABASE_HOST', $main::ispcpConfig{'DATABASE_HOST'}); $database->set('DATABASE_USER', $main::ispcpConfig{'DATABASE_USER'}); $database->set('DATABASE_PASSWORD', $crypt->decrypt_db_password($main::ispcpConfig{'DATABASE_PASSWORD'})); $database->connect(); } # Remove any ispCP file (including daemons) sub removeIspcpFiles { my ($rs, $stdout, $stderr, @errors); # Disabling ispCP fcgid and fastcgi modules if(-x '/usr/sbin/a2enmod') { $rs = execute("/usr/sbin/a2dismod fastcgi_ispcp fcgid_ispcp", \$stdout, \$stderr); debug($stdout) if $stdout; push @errors, $stderr if $stderr && $rs; } # Deleting ispCP Apache module files for (qw/fastcgi_ispcp.conf fastcgi_ispcp.load fcgid_ispcp.conf fcgid_ispcp.load/) { if(-f "$main::ispcpConfig{'HTTPD_MODS_AVAILABLE_DIR'}/$_") { unlink "$main::ispcpConfig{'HTTPD_MODS_AVAILABLE_DIR'}/$_" or push @errors, "Unable to delete $main::ispcpConfig{'HTTPD_MODS_AVAILABLE_DIR'}/$_ file: $!"; } } # Disabling ispcp.conf Apache configuration file if(-f "$main::ispcpConfig{'HTTPD_SITES_AVAILABLE_DIR'}/ispcp.conf") { if(-x '/usr/sbin/a2dissite') { $rs = execute("/usr/sbin/a2dissite ispcp.conf", \$stdout, \$stderr); debug($stdout) if $stdout; push @errors, $stderr if $stderr && $rs; } # Deleting ispcp.conf Apache configuration file unlink "$main::ispcpConfig{'HTTPD_SITES_AVAILABLE_DIR'}/ispcp.conf" or push @errors, "Unable to delete $main::ispcpConfig{'HTTPD_SITES_AVAILABLE_DIR'}/ispcp.conf file: $!"; } # Remove ispCP services my $serviceMngr = iMSCP::Service->getInstance(); for my $service('imscp_daemon', 'imscp_network') { local $@; eval { $serviceMngr->remove(); }; push @errors, "Unable to remove the $service service" if $@; } # Remove ispCP directories for( $main::ispcpConfig{'CONF_DIR'}, # eg. /etc/ispcp $main::ispcpConfig{'LOG_DIR'}, # eg. /var/log/ispcp $main::ispcpConfig{'ROOT_DIR'}, # eg. /var/www/ispcp $main::ispcpConfig{'HTTPD_CUSTOM_SITES_DIR'}, # eg. /etc/apache2/ispcp $main::ispcpConfig{'MTA_VIRTUAL_CONF_DIR'} # eg. /etc/postfix/ispcp ) { if(-d) { my $dir = iMSCP::Dir->new('dirname' => $_); push @errors, getLastError() if $dir->remove(); } } # Remove ispCP files for( ($^O =~ /bsd$/ ? '/usr/local/etc/' : '/etc/') . 'cron.d/ispcp', #/etc/cron.d/ispcp ($^O =~ /bsd$/ ? '/usr/local/etc/' : '/etc/') . 'logrotate.d/ispcp', #/etc/logrotate.d/ispcp ) { if(-f $_) { unlink $_ or push @errors, "Unable to delete $_ file: $!"; } } # Remmove ispCP PHP starter directory content $rs = execute("rm -fR $main::ispcpConfig{'PHP_STARTER_DIR'}/*", \$stdout, \$stderr); debug($stdout) if $stdout; push @errors, $stderr if $stderr && $rs; if (@errors) { my $errors = join "\n", @errors; iMSCP::Dialog->getInstance()->msgbox(<factory(); my $result = $database->doQuery( 'logo', "SELECT DISTINCT `logo` FROM `user_gui_props` WHERE logo <> '0' AND logo <> ''" ); unless (ref $result eq 'HASH') { push @errors, $result; } else { for (keys %$result) { my $fileName = $result->{$_}->{'logo'}; my $filePath = "$main::ispcpConfig{'GUI_ROOT_DIR'}/themes/user_logos/$fileName"; if(-f $filePath) { my $ispcpFile = iMSCP::File->new('filename' => $filePath); if ($ispcpFile->copyFile("$main::imscpConfig{'GUI_ROOT_DIR'}/data/persistent/ispLogos/$fileName")) { push @errors, getLastError(); } } else { push @errors, "File $filePath not found"; } } } if (@errors) { my $errors = join "\n", @errors; iMSCP::Dialog->getInstance()->msgbox(<factory(); my $result = $database->doQuery( 'mail_id', "UPDATE `config` SET `value` = '45' WHERE `name` = 'DATABASE_REVISION' AND `value` > '45'" ); unless(ref $result eq 'HASH') { push @errors, $result; } $result = $database->doQuery( 'mail_id', "SELECT `mail_id`, `mail_pass` FROM `mail_users` WHERE `mail_pass` != '_no_'" ); unless (ref $result eq 'HASH'){ push @errors, $result; } elsif (%{$result}) { _reencryptPassword('mail_users', 'mail_id', 'mail_pass', $_, \@errors) for values %{$result}; } $result = $database->doQuery('sqlu_id', 'SELECT `sqlu_id`, `sqlu_pass` FROM `sql_user`'); unless (ref $result eq 'HASH') { push @errors, $result; } elsif (%{$result}) { _reencryptPassword('sql_user', 'sqlu_id', 'sqlu_pass', $_, \@errors) for values %{$result}; } if (@errors) { my $errors = join "\n", @errors; iMSCP::Dialog->getInstance()->msgbox(<getInstance(); my $password = $crypt->decrypt_db_password($main::ispcpConfig{'DATABASE_PASSWORD'}); $crypt->set('key', $main::imscpDBKey); $crypt->set('iv', $main::imscpDBiv); $main::imscpConfig{'DATABASE_PASSWORD'} = $crypt->encrypt_db_password($password); for( qw/ DEFAULT_ADMIN_ADDRESS SERVER_HOSTNAME BASE_SERVER_IP BASE_SERVER_VHOST DATABASE_TYPE DATABASE_HOST DATABASE_NAME DATABASE_USER ZIP / ) { $main::imscpConfig{$_} = $main::ispcpConfig{$_}; } $main::imscpConfig{'SYSTEM_USER_PREFIX'} = $main::ispcpConfig{'APACHE_SUEXEC_USER_PREF'}; $main::imscpConfig{'SYSTEM_USER_MIN_UID'} = $main::ispcpConfig{'APACHE_SUEXEC_MIN_UID'}; 0; } # Save needed configuration files sub saveIspcpConfigFiles { my ($stdout, $stderr, @errors); my $rs = execute("find $main::ispcpConfig{'CONF_DIR'} -type f -name '*.system'", \$stdout, \$stderr); debug($stdout) if $stdout; push @errors, $stderr if $stderr && $rs; my @files = split /\n/, $stdout; for (@files){ my $fileName = fileparse($_); $rs = execute("cp -f $_ $main::imscpConfig{'CONF_DIR'}/$fileName", \$stdout, \$stderr); debug($stdout) if $stdout; push @errors, $stderr if $stderr && $rs; } my $result = iMSCP::Database->factory()->doQuery( 'name', " SELECT `domain_name` AS 'name' FROM `domain` UNION SELECT `alias_name` AS 'name' FROM `domain_aliasses` " ); unless (ref $result eq 'HASH') { push @errors, $result; } elsif (%{$result}){ local $Data::Dumper::Terse = 1; for (values %{$result}){ if (-f "$main::ispcpConfig{'BIND_DB_DIR'}/$_->{'name'}.db") { $rs = execute( "cp -f $main::ispcpConfig{'BIND_DB_DIR'}/$_->{'name'}.db $main::imscpConfig{'CONF_DIR'}/bind/working/", \$stdout, \$stderr ); debug($stdout) if $stdout; push @errors, $stderr if $stderr && $rs; } } } if (@errors) { my $errors = join "\n", @errors; iMSCP::Dialog->getInstance()->msgbox(<new('keepHome' => 'yes'); my $groupH = iMSCP::SystemGroup->getInstance(); # Master unix user/group my $userName = $main::ispcpConfig{'APACHE_SUEXEC_USER_PREF'} . $main::ispcpConfig{'APACHE_SUEXEC_MIN_UID'}; $rs = $userH->delSystemUser($userName); push @errors, "Unable to delete $userName unix user" if $rs; # Only needed to cover the case where the admin added other users to the unix group my $groupName = $main::ispcpConfig{'APACHE_SUEXEC_USER_PREF'} . $main::ispcpConfig{'APACHE_SUEXEC_MIN_GID'}; $rs = $groupH->delSystemGroup($groupName); push @errors, "Unable to delete $groupName unix group" if $rs; # Customer unix users/groups my $database = iMSCP::Database->factory(); my $result = $database->doQuery('domain_uid', 'SELECT `domain_uid`, `domain_gid` FROM `domain`'); unless (ref $result eq 'HASH'){ push @errors, $result; } elsif (%{$result}) { for(values %{$result}) { my $userName = getpwuid($_->{'domain_uid'}); $rs = $userH->delSystemUser($userName) if $userName; push @errors, "Unable to delete $userName unix user" if $rs; # Only needed to cover the case where the admin added other users to the unix group my $groupName = getgrgid($_->{'domain_gid'}); $rs = $groupH->delSystemGroup($groupName) if $groupName; push @errors, "Unable to delete $groupName unix group" if $rs; } } if (@errors) { my $errors = join "\n", @errors; iMSCP::Dialog->getInstance()->msgbox(< 'admin_id', childTable => 'admin', parentTable => 'admin', childField => 'created_by', parentField => 'admin_id', limit => "AND t1.`admin_type` = 'user'" }, { group => 'admin_id', childTable => 'admin', parentTable => 'admin', childField => 'created_by', parentField => 'admin_id', limit => "AND t1.`admin_type` = 'reseller'"}, { group => 'admin_id', childTable => 'admin', parentTable => 'domain', childField => 'admin_id', parentField => 'domain_admin_id', limit => "AND t1.`admin_type` = 'user'"}, { group => 'domain_id', childTable => 'domain', parentTable => 'admin', childField => 'domain_admin_id', parentField => 'admin_id', limit => '' }, { group => 'domain_id', childTable => 'domain', parentTable => 'server_ips', childField => 'domain_ip_id', parentField => 'ip_id', limit => '' }, { group => 'alias_id', childTable => 'domain_aliasses', parentTable => 'domain', childField => 'domain_id', parentField => 'domain_id', limit => '' }, { group => 'domain_dns_id', childTable => 'domain_dns', parentTable => 'domain', childField => 'domain_id', parentField => 'domain_id', limit => '' }, { group => 'domain_dns_id', childTable => 'domain_dns', parentTable => 'domain_aliasses', childField => 'alias_id', parentField => 'alias_id', limit => "AND t1.`alias_id` != 0" }, { group => 'userid', childTable => 'ftp_users', parentTable => 'domain', childField => 'uid', parentField => 'domain_uid', limit => '' }, { group => 'userid', childTable => 'ftp_users', parentTable => 'domain', childField => 'gid', parentField => 'domain_gid', limit => '' }, { group => 'id', childTable => 'htaccess', parentTable => 'domain', childField => 'dmn_id', parentField => 'domain_id', limit => '' }, { group => 'id', childTable => 'htaccess', parentTable => 'htaccess_users', childField => 'user_id', parentField => 'id', limit => 'AND t1.`user_id` != 0' }, { group => 'id', childTable => 'htaccess', parentTable => 'htaccess_groups', childField => 'group_id', parentField => 'id', limit => 'AND t1.`group_id` != 0' }, { group => 'id', childTable => 'htaccess_groups', parentTable => 'domain', childField => 'dmn_id', parentField => 'domain_id', limit => '' }, { group => 'id', childTable => 'htaccess_users', parentTable => 'domain', childField => 'dmn_id', parentField => 'domain_id', limit => '' }, { group => 'mail_id', childTable => 'mail_users', parentTable => 'domain', childField => 'domain_id', parentField => 'domain_id', limit => '' }, { group => 'mail_id', childTable => 'mail_users', parentTable => 'domain_aliasses', childField => 'sub_id', parentField => 'alias_id', limit => "AND t1.`mail_type` LIKE 'alias_%'" }, { group => 'mail_id', childTable => 'mail_users', parentTable => 'subdomain', childField => 'sub_id', parentField => 'subdomain_id', limit => "AND t1.`mail_type` LIKE 'subdom_%'" }, { group => 'mail_id', childTable => 'mail_users', parentTable => 'subdomain_alias', childField => 'sub_id', parentField => 'subdomain_alias_id', limit => "AND t1.`mail_type` LIKE 'alssub_%'" }, { group => 'subdomain_id', childTable => 'subdomain', parentTable => 'domain', childField => 'domain_id', parentField => 'domain_id', limit => '' }, { group => 'subdomain_alias_id', childTable => 'subdomain_alias', parentTable => 'domain_aliasses', childField => 'alias_id', parentField => 'alias_id', limit => '' }, { group => 'groupname', childTable => 'ftp_group', parentTable => 'domain', childField => 'gid', parentField => 'domain_gid', limit => '' }, { group => 'name', childTable => 'quotalimits', parentTable => 'domain', childField => 'name', parentField => 'domain_name', limit => '' }, { group => 'name', childTable => 'quotatallies', parentTable => 'domain', childField => 'name', parentField => 'domain_name', limit => '' }, { group => 'sqld_id', childTable => 'sql_database', parentTable => 'domain', childField => 'domain_id', parentField => 'domain_id', limit => '' }, { group => 'sqlu_id', childTable => 'sql_user', parentTable => 'sql_database', childField => 'sqld_id', parentField => 'sqld_id', limit => '' } ); my $database = iMSCP::Database->factory(); my @errors = (); local $Data::Dumper::Terse = 1; for my $test (@tests) { my $pQuery = sprintf( $query, $test->{'childTable'}, $test->{'parentTable'}, $test->{'childField'}, $test->{'parentField'}, $test->{'parentField'}, $test->{'limit'} ); my $result = $database->doQuery($test->{'group'}, $pQuery); unless (ref $result eq 'HASH') { push @errors, $result; } elsif (%{$result}) { for (values %{$result}) { push @errors, "Orphaned entry found in table $test->{'childTable'}: ". (Dumper $_); } } } # Check for entities status @tests = ( { group => 'domain_id', table => 'domain', statusField => 'domain_status' }, { group => 'alias_id', table => 'domain_aliasses', statusField => 'alias_status' }, { group => 'id', table => 'htaccess', statusField => 'status' }, { group => 'id', table => 'htaccess_groups', statusField => 'status' }, { group => 'id', table => 'htaccess_users', statusField => 'status' }, { group => 'mail_id', table => 'mail_users', statusField => 'status' }, { group => 'ip_id', table => 'server_ips', statusField => 'ip_status' }, { group => 'subdomain_id', table => 'subdomain', statusField => 'subdomain_status' }, { group => 'subdomain_alias_id', table => 'subdomain_alias', statusField => 'subdomain_alias_status' } ); for my $test (@tests) { my $query = sprintf("SELECT * FROM `%s` WHERE `%s` != 'ok'", $test->{'table'}, $test->{'statusField'}); my $result = $database->doQuery($test->{group}, $query); unless (ref $result eq 'HASH') { push @errors, $result; } elsif (%{$result}) { for (values %{$result}) { push @errors, "Wrong status for the $test->{'table'} database table: " . (Dumper $_); } } } # Test for databases and SQL users my $result = $database->doQuery( 'sqlu_id', 'SELECT * FROM `sql_user` AS `t1` LEFT JOIN `sql_database` AS `t2` ON `t1`.`sqld_id` = `t2`.`sqld_id`' ); unless (ref $result eq 'HASH') { push @errors, $result; } else { my $crypt = iMSCP::Crypt->getInstance(); $crypt->set('key', $main::ispcpDBKey); $crypt->set('iv', $main::ispcpDBiv); if (%{$result}) { for (values %{$result}) { $database->set('DATABASE_USER', $_->{'sqlu_name'}); $database->set('DATABASE_PASSWORD', $crypt->decrypt_db_password($_->{'sqlu_pass'})); $database->set('DATABASE_NAME', ''); my $dbConnect = $database->connect(); if($dbConnect) { # Cannot connect to the SQL server with the given user push @errors, $dbConnect; } else { $database->set('DATABASE_NAME', $_->{'sqld_name'}); $dbConnect = $database->connect(); # Cannot connect to the given database using the given user push @errors, $dbConnect if $dbConnect; } } } } if (@errors) { my $errors = join "\n", @errors; iMSCP::Dialog->getInstance()->msgbox(<new('filename' => $_)->get(); push @errors, 'Unable to read $_ file' unless defined $fileContent; if($fileContent =~ /db_pass_key\s=\s'(.+)'/ || $fileContent =~ /DB_PASS_KEY=(.+)/i) { $main::ispcpDBKey = $1; if( $fileContent =~ /db_pass_iv\s=\s'(.+)'/ || $fileContent =~ /DB_PASS_IV=(.+)/i) { $main::ispcpDBiv = $1; my $dbConnection = _ispCPdbConnect(); unless(!$dbConnection) { push @errors, "Unable to connect to the ispCP database using the $_ file"; } else { return 0; } } } } if(@existentKeyFiles) { my $keyFiles = join "\n", @existentKeyFiles; iMSCP::Dialog->getInstance()->msgbox(<getInstance()->msgbox(<parse($1) < version->parse('1.0.7')) { iMSCP::Dialog->getInstance()->set('defaultno', ''); if(iMSCP::Dialog->getInstance()->yesno(<getInstance()->msgbox(<getInstance(); $dialog->set('title', 'i-MSCP Migration Dialog'); exit 0 if $dialog->yesno(< "$configDir/ispcp/ispcp.conf"; my @steps = ( [\&welcomeMessage, 'Welcome message'], [\&checkIspcpVersion, 'Checking for ispCP version'], [\&checkDbPassword, 'Checking for database password'], [\&databaseIntegrityTests, 'Checking for database integrity'], [\&deleteIspCPunixUsers, 'Deleting ispCP unix users'], [\&saveIspcpConfigFiles, 'saving system configuration files'], [\&saveIspcpMainConfiguration, 'Saving main ispCP configuration parameters'], [\&dbUpgrade, 'Database upgrade'], [\&saveIspLogo, 'Saving ISP logos'], [\&removeIspcpFiles, 'Remove ispCP files'] ); my $step = 1; my $nbSteps = @steps; # Process all migration steps for (@steps) { my $rs = step($_->[0], $_->[1], $nbSteps, $step); return $rs if $rs; $step++; } iMSCP::Dialog->getInstance()->msgbox(<getInstance()->boot({ mode => 'setup', nodatabase => 'yes' }); exit processMigration();