Files
scripts/Perl OTRS/Kernel/Modules/Installer.pm
2024-10-14 00:08:40 +02:00

1493 lines
50 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::Modules::Installer;
## nofilter(TidyAll::Plugin::OTRS::Perl::DBObject)
## nofilter(TidyAll::Plugin::OTRS::Perl::Print)
use strict;
use warnings;
use DBI;
use Net::Domain qw(hostfqdn);
use Kernel::Language qw(Translatable);
our $ObjectManagerDisabled = 1;
use vars qw(%INC);
sub new {
my ( $Type, %Param ) = @_;
# Allocate new hash for object.
my $Self = {%Param};
bless( $Self, $Type );
return $Self;
}
sub Run {
my ( $Self, %Param ) = @_;
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
if ( $Kernel::OM->Get('Kernel::Config')->Get('SecureMode') ) {
$LayoutObject->FatalError(
Message => Translatable('SecureMode active!'),
Comment => Translatable(
'If you want to re-run the Installer, disable the SecureMode in the SysConfig.'
),
);
}
# Check environment directories.
$Self->{Path} = $ConfigObject->Get('Home');
if ( !-d $Self->{Path} ) {
$LayoutObject->FatalError(
Message => $LayoutObject->{LanguageObject}->Translate( 'Directory "%s" doesn\'t exist!', $Self->{Path} ),
Comment => Translatable('Configure "Home" in Kernel/Config.pm first!'),
);
}
if ( !-f "$Self->{Path}/Kernel/Config.pm" ) {
$LayoutObject->FatalError(
Message =>
$LayoutObject->{LanguageObject}->Translate( 'File "%s/Kernel/Config.pm" not found!', $Self->{Path} ),
Comment => Translatable('Please contact the administrator.'),
);
}
# Check/get SQL schema directory
my $DirOfSQLFiles = $Self->{Path} . '/scripts/database';
if ( !-d $DirOfSQLFiles ) {
$LayoutObject->FatalError(
Message => $LayoutObject->{LanguageObject}->Translate( 'Directory "%s" not found!', $DirOfSQLFiles ),
Comment => Translatable('Please contact the administrator.'),
);
}
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
# TODO: This seams to be deprecated now
# Read installer.json if it exists.
# It contains options set by Windows Installer
if ( -f "$Self->{Path}/var/tmp/installer.json" ) {
my $JSONString = $MainObject->FileRead(
Location => "$Self->{Path}/var/tmp/installer.json",
);
$Self->{Options} = $Kernel::OM->Get('Kernel::System::JSON')->Decode(
Data => $$JSONString,
);
}
# Check if License option needs to be skipped.
if ( $Self->{Subaction} eq 'License' && $Self->{Options}->{SkipLicense} ) {
$Self->{Subaction} = 'Start';
}
# Check if Database option needs to be skipped.
if ( $Self->{Subaction} eq 'Start' && $Self->{Options}->{DBType} ) {
$Self->{Subaction} = 'DBCreate';
}
$Self->{Subaction} = 'Intro' if !$Self->{Subaction};
# Build steps.
my @Steps = qw(License Database General Finish);
my $StepCounter;
# TODO: This seams to be deprecated now
# No license step needed if defined in .json file.
shift @Steps if $Self->{Options}->{SkipLicense};
# Build header - but only if we're not in AJAX mode.
if ( $Self->{Subaction} ne 'CheckRequirements' ) {
$LayoutObject->Block(
Name => 'Steps',
Data => {
Steps => scalar @Steps,
},
);
# Mapping of sub-actions to steps.
my %Steps = (
Intro => 'Intro',
License => 'License',
Start => 'Database',
DB => 'Database',
DBCreate => 'Database',
ConfigureMail => 'General',
System => 'General',
Finish => 'Finish',
);
# On the intro screen no steps should be highlighted.
my $Highlight = ( $Self->{Subaction} eq 'Intro' ) ? '' : 'Highlighted NoLink';
my $Counter;
for my $Step (@Steps) {
$Counter++;
# Is the current step active?
my $Active = ( $Steps{ $Self->{Subaction} } eq $Step ) ? 'Active' : '';
$LayoutObject->Block(
Name => 'Step' . $Step,
Data => {
Step => $Counter,
Highlight => $Highlight,
Active => $Active,
},
);
# If this is the actual step.
if ( $Steps{ $Self->{Subaction} } eq $Step ) {
# No more highlights from now on.
$Highlight = '';
# Step calculation: 2/5 etc.
$StepCounter = $Counter . "/" . scalar @Steps;
}
}
}
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
# Print intro form.
my $Title = $LayoutObject->{LanguageObject}->Translate('Install OTRS');
if ( $Self->{Subaction} eq 'Intro' ) {
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('Intro')
);
$LayoutObject->Block(
Name => 'Intro',
Data => {}
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
# Print license from.
elsif ( $Self->{Subaction} eq 'License' ) {
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('License')
);
$LayoutObject->Block(
Name => 'License',
Data => {
Item => Translatable('License'),
Step => $StepCounter,
},
);
$LayoutObject->Block(
Name => 'LicenseText',
Data => {},
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
# Database selection screen.
elsif ( $Self->{Subaction} eq 'Start' ) {
if ( !-w "$Self->{Path}/Kernel/Config.pm" ) {
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('Error')
);
$Output .= $LayoutObject->Warning(
Message => Translatable('Kernel/Config.pm isn\'t writable!'),
Comment => Translatable(
'If you want to use the installer, set the Kernel/Config.pm writable for the webserver user!'
),
);
$Output .= $LayoutObject->Footer();
return $Output;
}
my %Databases = (
mysql => "MySQL",
postgresql => "PostgreSQL",
oracle => "Oracle",
);
# Build the select field for the InstallerDBStart.tt.
$Param{SelectDBType} = $LayoutObject->BuildSelection(
Data => \%Databases,
Name => 'DBType',
Class => 'Modernize',
Size => scalar keys %Databases,
SelectedID => 'mysql',
);
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('Database Selection')
);
$LayoutObject->Block(
Name => 'DatabaseStart',
Data => {
Item => Translatable('Database Selection'),
Step => $StepCounter,
SelectDBType => $Param{SelectDBType},
},
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
# Check different requirements (AJAX).
elsif ( $Self->{Subaction} eq 'CheckRequirements' ) {
my $CheckMode = $ParamObject->GetParam( Param => 'CheckMode' );
my %Result;
# Check DB requirements.
if ( $CheckMode eq 'DB' ) {
my %DBCredentials;
for my $Param (
qw(DBUser DBPassword DBHost DBType DBPort DBSID DBName InstallType OTRSDBUser OTRSDBPassword)
)
{
$DBCredentials{$Param} = $ParamObject->GetParam( Param => $Param ) || '';
}
%Result = $Self->CheckDBRequirements(
%DBCredentials,
);
}
# Check mail configuration.
elsif ( $CheckMode eq 'Mail' ) {
%Result = $Self->CheckMailConfiguration();
}
# No adequate check method found.
else {
%Result = (
Successful => 0,
Message => Translatable('Unknown Check!'),
Comment => $LayoutObject->{LanguageObject}->Translate( 'The check "%s" doesn\'t exist!', $CheckMode ),
);
}
# Return JSON-String because of AJAX-Mode.
my $OutputJSON = $LayoutObject->JSONEncode( Data => \%Result );
return $LayoutObject->Attachment(
ContentType => 'application/json; charset='
. $LayoutObject->{Charset},
Content => $OutputJSON,
Type => 'inline',
NoCache => 1,
);
}
elsif ( $Self->{Subaction} eq 'DB' ) {
my $DBType = $ParamObject->GetParam( Param => 'DBType' );
my $DBInstallType = $ParamObject->GetParam( Param => 'DBInstallType' );
# Use MainObject to generate a password.
my $GeneratedPassword = $MainObject->GenerateRandomString();
if ( $DBType eq 'mysql' ) {
my $PasswordExplanation = $DBInstallType eq 'CreateDB'
? $LayoutObject->{LanguageObject}->Translate(
'If you have set a root password for your database, it must be entered here. If not, leave this field empty.',
)
: $LayoutObject->{LanguageObject}->Translate('Enter the password for the database user.');
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate( 'Database %s', 'MySQL' )
);
$LayoutObject->Block(
Name => 'DatabaseMySQL',
Data => {
Item => Translatable('Configure MySQL'),
Step => $StepCounter,
InstallType => $DBInstallType,
DefaultDBUser => $DBInstallType eq 'CreateDB' ? 'root' : 'otrs',
PasswordExplanation => $PasswordExplanation,
},
);
if ( $DBInstallType eq 'CreateDB' ) {
$LayoutObject->Block(
Name => 'DatabaseMySQLCreate',
Data => {
Password => $GeneratedPassword,
},
);
}
else {
$LayoutObject->Block(
Name => 'DatabaseMySQLUseExisting',
);
}
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {
Item => Translatable('Configure MySQL'),
Step => $StepCounter,
},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
elsif ( $DBType eq 'postgresql' ) {
my $PasswordExplanation = $DBInstallType eq 'CreateDB'
? $LayoutObject->{LanguageObject}->Translate('Enter the password for the administrative database user.')
: $LayoutObject->{LanguageObject}->Translate('Enter the password for the database user.');
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate( 'Database %s', 'PostgreSQL' )
);
$LayoutObject->Block(
Name => 'DatabasePostgreSQL',
Data => {
Item => Translatable('Database'),
Step => $StepCounter,
InstallType => $DBInstallType,
DefaultDBUser => $DBInstallType eq 'CreateDB' ? 'postgres' : 'otrs',
},
);
if ( $DBInstallType eq 'CreateDB' ) {
$LayoutObject->Block(
Name => 'DatabasePostgreSQLCreate',
Data => {
Password => $GeneratedPassword,
},
);
}
else {
$LayoutObject->Block(
Name => 'DatabasePostgreSQLUseExisting',
);
}
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {
Item => Translatable('Configure PostgreSQL'),
Step => $StepCounter,
},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
elsif ( $DBType eq 'oracle' ) {
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate( 'Database %s', 'Oracle' )
);
$LayoutObject->Block(
Name => 'DatabaseOracle',
Data => {
Item => Translatable('Database'),
Step => $StepCounter,
},
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {
Item => Translatable('Configure Oracle'),
Step => $StepCounter,
},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
else {
$LayoutObject->FatalError(
Message => $LayoutObject->{LanguageObject}->Translate( 'Unknown database type "%s".', $DBType ),
Comment => Translatable('Please go back.'),
);
}
}
# Do database settings.
elsif ( $Self->{Subaction} eq 'DBCreate' ) {
my %DBCredentials;
for my $Param (
qw(DBUser DBPassword DBHost DBType DBName DBSID DBPort InstallType OTRSDBUser OTRSDBPassword)
)
{
$DBCredentials{$Param} = $ParamObject->GetParam( Param => $Param ) || '';
}
%DBCredentials = %{ $Self->{Options} } if $Self->{Options}->{DBType};
# Get and check params and connect to DB.
my %Result = $Self->ConnectToDB(%DBCredentials);
my %DB;
my $DBH;
if ( ref $Result{DB} ne 'HASH' || !$Result{DBH} ) {
$LayoutObject->FatalError(
Message => $Result{Message},
Comment => $Result{Comment},
);
}
else {
%DB = %{ $Result{DB} };
$DBH = $Result{DBH};
}
my $Output = $LayoutObject->Header(
Title => $Title . '-'
. $LayoutObject->{LanguageObject}->Translate(
'Create Database'
),
);
$LayoutObject->Block(
Name => 'DatabaseResult',
Data => {
Item => Translatable('Create Database'),
Step => $StepCounter,
},
);
my @Statements;
# Create database, add user.
if ( $DB{DBType} eq 'mysql' ) {
if ( $DB{InstallType} eq 'CreateDB' ) {
# Determine current host for MySQL account.
my $ConnectionID;
my $StatementHandle = $DBH->prepare("select connection_id()");
$StatementHandle->execute();
while ( my @Row = $StatementHandle->fetchrow_array() ) {
$ConnectionID = $Row[0];
}
$StatementHandle = $DBH->prepare("show processlist");
$StatementHandle->execute();
PROCESSLIST:
while ( my @Row = $StatementHandle->fetchrow_array() ) {
if ( $Row[0] eq $ConnectionID ) {
$DB{Host} = $Row[2];
last PROCESSLIST;
}
}
# Strip off port, i.e. 'localhost:14962' should become 'localhost'.
$DB{Host} =~ s{:\d*\z}{}xms;
@Statements = (
"CREATE DATABASE `$DB{DBName}` charset utf8",
"GRANT ALL PRIVILEGES ON `$DB{DBName}`.* TO `$DB{OTRSDBUser}`\@`$DB{Host}` IDENTIFIED BY '$DB{OTRSDBPassword}' WITH GRANT OPTION",
"FLUSH PRIVILEGES",
);
}
# Set DSN for Config.pm.
$DB{ConfigDSN} = 'DBI:mysql:database=$Self->{Database};host=$Self->{DatabaseHost}';
$DB{DSN} = "DBI:mysql:database=$DB{DBName};host=$DB{DBHost}";
}
elsif ( $DB{DBType} eq 'postgresql' ) {
if ( $DB{InstallType} eq 'CreateDB' ) {
@Statements = (
"CREATE ROLE \"$DB{OTRSDBUser}\" WITH LOGIN PASSWORD '$DB{OTRSDBPassword}'",
"CREATE DATABASE \"$DB{DBName}\" OWNER=\"$DB{OTRSDBUser}\" ENCODING 'utf-8'",
);
}
# Set DSN for Config.pm.
$DB{ConfigDSN} = 'DBI:Pg:dbname=$Self->{Database};host=$Self->{DatabaseHost}';
$DB{DSN} = "DBI:Pg:dbname=$DB{DBName};host=$DB{DBHost}";
}
elsif ( $DB{DBType} eq 'oracle' ) {
# Set DSN for Config.pm.
$DB{ConfigDSN} = 'DBI:Oracle://$Self->{DatabaseHost}:' . $DB{DBPort} . '/$Self->{Database}';
$DB{DSN} = "DBI:Oracle://$DB{DBHost}:$DB{DBPort}/$DB{DBSID}";
$ConfigObject->Set(
Key => 'Database::Connect',
Value => "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'",
);
}
# Execute database statements.
for my $Statement (@Statements) {
my @Description = split( ' ', $Statement );
# Prevent uninitialized variables.
for my $Index ( 0 .. 2 ) {
$Description[$Index] //= '';
}
$LayoutObject->Block(
Name => 'DatabaseResultItem',
Data => { Item => "$Description[0] $Description[1] $Description[2]" },
);
if ( !$DBH->do($Statement) ) {
$LayoutObject->Block(
Name => 'DatabaseResultItemFalse',
Data => {},
);
$LayoutObject->Block(
Name => 'DatabaseResultItemMessage',
Data => {
Message => $DBI::errstr,
},
);
$LayoutObject->Block(
Name => 'DatabaseResultBack',
Data => {},
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
else {
$LayoutObject->Block(
Name => 'DatabaseResultItemDone',
Data => {},
);
}
}
# ReConfigure Config.pm.
my $ReConfigure;
if ( $DB{DBType} eq 'oracle' ) {
$ReConfigure = $Self->ReConfigure(
DatabaseDSN => $DB{ConfigDSN},
DatabaseHost => $DB{DBHost},
Database => $DB{DBSID},
DatabaseUser => $DB{OTRSDBUser},
DatabasePw => $DB{OTRSDBPassword},
);
}
else {
$ReConfigure = $Self->ReConfigure(
DatabaseDSN => $DB{ConfigDSN},
DatabaseHost => $DB{DBHost},
Database => $DB{DBName},
DatabaseUser => $DB{OTRSDBUser},
DatabasePw => $DB{OTRSDBPassword},
);
}
if ($ReConfigure) {
my $Output =
$LayoutObject->Header(
Title => Translatable('Install OTRS - Error')
);
$Output .= $LayoutObject->Warning(
Message => Translatable('Kernel/Config.pm isn\'t writable!'),
Comment => Translatable(
'If you want to use the installer, set the Kernel/Config.pm writable for the webserver user!'
),
);
$Output .= $LayoutObject->Footer();
return $Output;
}
$Kernel::OM->ObjectsDiscard( Objects => ['Kernel::System::DB'] );
# We need a database object to be able to parse the XML
# connect to database using given credentials.
$Kernel::OM->ObjectParamAdd(
'Kernel::System::DB' => {
DatabaseDSN => $DB{DSN},
DatabaseUser => $DB{OTRSDBUser},
DatabasePw => $DB{OTRSDBPassword},
Type => $DB{DBType},
},
);
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# Create database tables and insert initial values.
my @SQLPost;
for my $SchemaFile (qw(otrs-schema otrs-initial_insert)) {
if ( !-f "$DirOfSQLFiles/$SchemaFile.xml" ) {
$LayoutObject->FatalError(
Message => $LayoutObject->{LanguageObject}
->Translate( 'File "%s/%s.xml" not found!', $DirOfSQLFiles, $SchemaFile ),
Comment => Translatable('Contact your Admin!'),
);
}
$LayoutObject->Block(
Name => 'DatabaseResultItem',
Data => { Item => "Processing $SchemaFile" },
);
my $XML = $MainObject->FileRead(
Directory => $DirOfSQLFiles,
Filename => $SchemaFile . '.xml',
);
my @XMLArray = $Kernel::OM->Get('Kernel::System::XML')->XMLParse(
String => $XML,
);
my @SQL = $DBObject->SQLProcessor(
Database => \@XMLArray,
);
# If we parsed the schema, catch post instructions.
@SQLPost = $DBObject->SQLProcessorPost() if $SchemaFile eq 'otrs-schema';
for my $SQL (@SQL) {
$DBObject->Do( SQL => $SQL );
}
$LayoutObject->Block(
Name => 'DatabaseResultItemDone',
);
}
# Execute post SQL statements (indexes, constraints).
$LayoutObject->Block(
Name => 'DatabaseResultItem',
Data => { Item => "Processing post statements" },
);
for my $SQL (@SQLPost) {
$DBObject->Do( SQL => $SQL );
}
$LayoutObject->Block(
Name => 'DatabaseResultItemDone',
);
# If running under PerlEx, reload the application (and thus the configuration).
if (
exists $ENV{'GATEWAY_INTERFACE'}
&& $ENV{'GATEWAY_INTERFACE'} eq "CGI-PerlEx"
)
{
PerlEx::ReloadAll();
}
$LayoutObject->Block(
Name => 'DatabaseResultSuccess',
);
$LayoutObject->Block(
Name => 'DatabaseResultNext',
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
);
$Output .= $LayoutObject->Footer();
return $Output;
}
# Show system settings page, pre-install packages.
elsif ( $Self->{Subaction} eq 'System' ) {
if ( !$Kernel::OM->Get('Kernel::System::DB') ) {
$LayoutObject->FatalError();
}
# Take care that default config is in the database.
if ( !$Self->_CheckConfig() ) {
return $LayoutObject->FatalError();
}
# Install default files.
if ( $MainObject->Require('Kernel::System::Package') ) {
my $PackageObject = Kernel::System::Package->new( %{$Self} );
if ($PackageObject) {
$PackageObject->PackageInstallDefaultFiles();
}
}
my @SystemIDs = map { sprintf "%02d", $_ } ( 0 .. 99 );
$Param{SystemIDString} = $LayoutObject->BuildSelection(
Data => \@SystemIDs,
Name => 'SystemID',
Class => 'Modernize',
SelectedID => $SystemIDs[ int( rand(100) ) ], # random system ID
);
$Param{LanguageString} = $LayoutObject->BuildSelection(
Data => $ConfigObject->Get('DefaultUsedLanguages'),
Name => 'DefaultLanguage',
Class => 'Modernize',
HTMLQuote => 0,
SelectedID => $LayoutObject->{UserLanguage},
);
# Build the selection field for the MX check.
$Param{SelectCheckMXRecord} = $LayoutObject->BuildSelection(
Data => {
1 => Translatable('Yes'),
0 => Translatable('No'),
},
Name => 'CheckMXRecord',
Class => 'Modernize',
SelectedID => '1',
);
# Read FQDN using Net::Domain and pre-populate the field.
$Param{FQDN} = hostfqdn();
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('System Settings'),
);
$LayoutObject->Block(
Name => 'System',
Data => {
Item => Translatable('System Settings'),
Step => $StepCounter,
%Param,
},
);
if ( !$Self->{Options}->{SkipLog} ) {
$Param{LogModuleString} = $LayoutObject->BuildSelection(
Data => {
'Kernel::System::Log::SysLog' => Translatable('Syslog'),
'Kernel::System::Log::File' => Translatable('File'),
},
Name => 'LogModule',
Class => 'Modernize',
HTMLQuote => 0,
SelectedID => $ConfigObject->Get('LogModule'),
);
$LayoutObject->Block(
Name => 'LogModule',
Data => \%Param,
);
}
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
# Do system settings action.
elsif ( $Self->{Subaction} eq 'ConfigureMail' ) {
if ( !$Kernel::OM->Get('Kernel::System::DB') ) {
$LayoutObject->FatalError();
}
# Take care that default config is in the database.
if ( !$Self->_CheckConfig() ) {
return $LayoutObject->FatalError();
}
my $SysConfigObject = $Kernel::OM->Get('Kernel::System::SysConfig');
my $ExclusiveLockGUID = $SysConfigObject->SettingLock(
LockAll => 1,
Force => 1,
UserID => 1,
);
for my $SettingName (
qw(SystemID FQDN AdminEmail Organization LogModule LogModule::LogFile
DefaultLanguage CheckMXRecord)
)
{
my $EffectiveValue = $ParamObject->GetParam( Param => $SettingName );
# Update config item via sys config object.
$SysConfigObject->SettingUpdate(
Name => $SettingName,
IsValid => 1,
EffectiveValue => $EffectiveValue,
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => 1,
);
}
my $Success = $SysConfigObject->SettingUnlock(
UnlockAll => 1,
);
# Get mail account object and check available back-ends.
my $MailAccount = $Kernel::OM->Get('Kernel::System::MailAccount');
my %MailBackends = $MailAccount->MailAccountBackendList();
my $OutboundMailTypeSelection = $LayoutObject->BuildSelection(
Data => {
sendmail => 'Sendmail',
smtp => 'SMTP',
smtps => 'SMTPS',
smtptls => 'SMTPTLS',
},
Name => 'OutboundMailType',
Class => 'Modernize',
);
my $OutboundMailDefaultPorts = $LayoutObject->BuildSelection(
Class => 'Hidden',
Data => {
sendmail => '25',
smtp => '25',
smtps => '465',
smtptls => '587',
},
Name => 'OutboundMailDefaultPorts',
);
my $InboundMailTypeSelection = $LayoutObject->BuildSelection(
Data => \%MailBackends,
Name => 'InboundMailType',
Class => 'Modernize',
);
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('Configure Mail')
);
$LayoutObject->Block(
Name => 'ConfigureMail',
Data => {
Item => $LayoutObject->{LanguageObject}->Translate('Mail Configuration'),
Step => $StepCounter,
InboundMailType => $InboundMailTypeSelection,
OutboundMailType => $OutboundMailTypeSelection,
OutboundPorts => $OutboundMailDefaultPorts,
},
);
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
elsif ( $Self->{Subaction} eq 'Finish' ) {
# Take care that default config is in the database.
if ( !$Self->_CheckConfig() ) {
return $LayoutObject->FatalError();
}
my $SysConfigObject = $Kernel::OM->Get('Kernel::System::SysConfig');
my $SettingName = 'SecureMode';
my $ExclusiveLockGUID = $SysConfigObject->SettingLock(
Name => $SettingName,
Force => 1,
UserID => 1,
);
# Update config item via SysConfig object.
my $Result = $SysConfigObject->SettingUpdate(
Name => $SettingName,
IsValid => 1,
EffectiveValue => 1,
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => 1,
);
if ( !$Result ) {
$LayoutObject->FatalError(
Message => Translatable('Can\'t write Config file!'),
);
}
# There is no need to unlock the setting as it was already unlocked in the update.
# 'Rebuild' the configuration.
$SysConfigObject->ConfigurationDeploy(
Comments => "Installer deployment",
AllSettings => 1,
Force => 1,
UserID => 1,
);
# If running under PerlEx, reload the application (and thus the configuration).
if (
exists $ENV{'GATEWAY_INTERFACE'}
&& $ENV{'GATEWAY_INTERFACE'} eq "CGI-PerlEx"
)
{
PerlEx::ReloadAll();
}
# Set a generated password for the 'root@localhost' account.
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $Password = $UserObject->GenerateRandomPassword( Size => 16 );
$UserObject->SetPassword(
UserLogin => 'root@localhost',
PW => $Password,
);
# TODO: This seams to be deprecated now.
# Remove installer file with pre-configured options.
if ( -f "$Self->{Path}/var/tmp/installer.json" ) {
unlink "$Self->{Path}/var/tmp/installer.json";
}
# check web server - is a restart needed?
my $Webserver;
# Only if we have mod_perl we have to restart.
if ( exists $ENV{MOD_PERL} ) {
eval 'require mod_perl'; ## no critic
if ( defined $mod_perl::VERSION ) { ## no critic
$Webserver = 'Apache2 + mod_perl';
if ( -f '/etc/SuSE-release' ) {
$Webserver = 'rcapache2 restart';
}
elsif ( -f '/etc/redhat-release' ) {
$Webserver = 'service httpd restart';
}
}
}
# Check if Apache::Reload is loaded.
for my $Module ( sort keys %INC ) {
$Module =~ s/\//::/g;
$Module =~ s/\.pm$//g;
if ( $Module eq 'Apache2::Reload' ) {
$Webserver = '';
}
}
my $OTRSHandle = $ENV{SCRIPT_NAME};
$OTRSHandle =~ s/\/(.*)\/installer\.pl/$1/;
my $Output =
$LayoutObject->Header(
Title => "$Title - "
. $LayoutObject->{LanguageObject}->Translate('Finished')
);
$LayoutObject->Block(
Name => 'Finish',
Data => {
Item => Translatable('Finished'),
Step => $StepCounter,
Host => $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN'),
OTRSHandle => $OTRSHandle,
Webserver => $Webserver,
Password => $Password,
},
);
if ($Webserver) {
$LayoutObject->Block(
Name => 'Restart',
Data => {
Webserver => $Webserver,
},
);
}
$Output .= $LayoutObject->Output(
TemplateFile => 'Installer',
Data => {},
);
$Output .= $LayoutObject->Footer();
return $Output;
}
# Else error!
return $LayoutObject->FatalError(
Message => $LayoutObject->{LanguageObject}->Translate( 'Unknown Subaction %s!', $Self->{Subaction} ),
Comment => Translatable('Please contact the administrator.'),
);
}
sub ReConfigure {
my ( $Self, %Param ) = @_;
# Perl quote and set via ConfigObject.
for my $Key ( sort keys %Param ) {
$Kernel::OM->Get('Kernel::Config')->Set(
Key => $Key,
Value => $Param{$Key},
);
if ( $Param{$Key} ) {
$Param{$Key} =~ s/'/\\'/g;
}
}
# Read config file.
my $ConfigFile = "$Self->{Path}/Kernel/Config.pm";
## no critic
open( my $In, '<', $ConfigFile )
|| return "Can't open $ConfigFile: $!";
## use critic
my $Config = '';
while (<$In>) {
# Skip empty lines or comments.
if ( !$_ || $_ =~ /^\s*#/ || $_ =~ /^\s*$/ ) {
$Config .= $_;
}
else {
my $NewConfig = $_;
# Replace config with %Param.
for my $Key ( sort keys %Param ) {
# Database passwords can contain characters like '@' or '$' and should be single-quoted
# same goes for database hosts which can be like 'myserver\instance name' for MS SQL.
if ( $Key eq 'DatabasePw' || $Key eq 'DatabaseHost' ) {
$NewConfig =~
s/(\$Self->\{("|'|)$Key("|'|)} =.+?('|"));/\$Self->{'$Key'} = '$Param{$Key}';/g;
}
else {
$NewConfig =~
s/(\$Self->\{("|'|)$Key("|'|)} =.+?('|"));/\$Self->{'$Key'} = "$Param{$Key}";/g;
}
}
$Config .= $NewConfig;
}
}
close $In;
# Write new config file.
## no critic
open( my $Out, '>:utf8', $ConfigFile )
|| return "Can't open $ConfigFile: $!";
print $Out $Config;
## use critic
close $Out;
return;
}
sub ConnectToDB {
my ( $Self, %Param ) = @_;
# Check params.
my @NeededKeys = qw(DBType DBHost DBUser DBPassword);
if ( $Param{InstallType} eq 'CreateDB' ) {
push @NeededKeys, qw(OTRSDBUser OTRSDBPassword);
}
# For Oracle we require DBSID and DBPort.
if ( $Param{DBType} eq 'oracle' ) {
push @NeededKeys, qw(DBSID DBPort);
}
# For existing databases we require the database name.
if ( $Param{DBType} ne 'oracle' && $Param{InstallType} eq 'UseDB' ) {
push @NeededKeys, 'DBName';
}
for my $Key (@NeededKeys) {
if ( !$Param{$Key} && $Key !~ /^(DBPassword)$/ ) {
return (
Successful => 0,
Message => "You need '$Key'!!",
DB => undef,
DBH => undef,
);
}
}
# If we do not need to create a database for OTRS OTRSDBuser equals DBUser.
if ( $Param{InstallType} ne 'CreateDB' ) {
$Param{OTRSDBUser} = $Param{DBUser};
$Param{OTRSDBPassword} = $Param{DBPassword};
}
# Create DSN string for backend.
if ( $Param{DBType} eq 'mysql' && $Param{InstallType} eq 'CreateDB' ) {
$Param{DSN} = "DBI:mysql:database=;host=$Param{DBHost};";
}
elsif ( $Param{DBType} eq 'mysql' && $Param{InstallType} eq 'UseDB' ) {
$Param{DSN} = "DBI:mysql:database=;host=$Param{DBHost};database=$Param{DBName}";
}
elsif ( $Param{DBType} eq 'postgresql' && $Param{InstallType} eq 'CreateDB' ) {
$Param{DSN} = "DBI:Pg:host=$Param{DBHost};";
}
elsif ( $Param{DBType} eq 'postgresql' && $Param{InstallType} eq 'UseDB' ) {
$Param{DSN} = "DBI:Pg:host=$Param{DBHost};dbname=$Param{DBName}";
}
elsif ( $Param{DBType} eq 'oracle' ) {
$Param{DSN} = "DBI:Oracle://$Param{DBHost}:$Param{DBPort}/$Param{DBSID}";
}
# Extract driver to load for install test.
my ($Driver) = ( $Param{DSN} =~ /^DBI:(.*?):/ );
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( 'DBD::' . $Driver ) ) {
return (
Successful => 0,
Message => $Kernel::OM->Get('Kernel::Output::HTML::Layout')->{LanguageObject}
->Translate( "Can't connect to database, Perl module DBD::%s not installed!", $Driver ),
Comment => "",
DB => undef,
DBH => undef,
);
}
my $DBH = DBI->connect(
$Param{DSN}, $Param{DBUser}, $Param{DBPassword},
);
if ( !$DBH ) {
return (
Successful => 0,
Message => $Kernel::OM->Get('Kernel::Output::HTML::Layout')->{LanguageObject}
->Translate("Can't connect to database, read comment!"),
Comment => "$DBI::errstr",
DB => undef,
DBH => undef,
);
}
# If we use an existing database, check if it already contains tables.
if ( $Param{InstallType} ne 'CreateDB' ) {
my $Data = $DBH->selectall_arrayref('SELECT * FROM valid');
if ($Data) {
return (
Successful => 0,
Message => $Kernel::OM->Get('Kernel::Output::HTML::Layout')->{LanguageObject}
->Translate("Database already contains data - it should be empty!"),
Comment => "",
DB => undef,
DBH => undef,
);
}
}
return (
Successful => 1,
Message => '',
Comment => '',
DB => \%Param,
DBH => $DBH,
);
}
sub CheckDBRequirements {
my ( $Self, %Param ) = @_;
my %Result = $Self->ConnectToDB(
%Param,
);
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
# If mysql, check some more values.
if ( $Param{DBType} eq 'mysql' && $Result{Successful} == 1 ) {
# Set max_allowed_packet.
my $MySQLMaxAllowedPacket = 0;
my $MySQLMaxAllowedPacketRecommended = 64;
my $Data = $Result{DBH}->selectall_arrayref("SHOW variables WHERE Variable_name = 'max_allowed_packet'");
$MySQLMaxAllowedPacket = $Data->[0]->[1] / 1024 / 1024;
if ( $MySQLMaxAllowedPacket < $MySQLMaxAllowedPacketRecommended ) {
$Result{Successful} = 0;
$Result{Message} = $LayoutObject->{LanguageObject}->Translate(
"Error: Please make sure your database accepts packages over %s MB in size (it currently only accepts packages up to %s MB). Please adapt the max_allowed_packet setting of your database in order to avoid errors.",
$MySQLMaxAllowedPacketRecommended, $MySQLMaxAllowedPacket
);
}
}
if ( $Param{DBType} eq 'mysql' && $Result{Successful} == 1 ) {
# Set innodb_log_file_size.
my $MySQLInnoDBLogFileSize = 0;
my $MySQLInnoDBLogFileSizeMinimum = 256;
my $MySQLInnoDBLogFileSizeRecommended = 512;
# Default storage engine variable has changed its name in MySQL 5.5.3, we need to support both of them for now.
# <= 5.5.2 storage_engine
# >= 5.5.3 default_storage_engine
my $DataOld = $Result{DBH}->selectall_arrayref("SHOW variables WHERE Variable_name = 'storage_engine'");
my $DataNew = $Result{DBH}->selectall_arrayref("SHOW variables WHERE Variable_name = 'default_storage_engine'");
my $DefaultStorageEngine = ( $DataOld->[0] && $DataOld->[0]->[1] ? $DataOld->[0]->[1] : undef )
// ( $DataNew->[0] && $DataNew->[0]->[1] ? $DataNew->[0]->[1] : '' );
if ( lc $DefaultStorageEngine eq 'innodb' ) {
my $Data = $Result{DBH}->selectall_arrayref("SHOW variables WHERE Variable_name = 'innodb_log_file_size'");
$MySQLInnoDBLogFileSize = $Data->[0]->[1] / 1024 / 1024;
if ( $MySQLInnoDBLogFileSize < $MySQLInnoDBLogFileSizeMinimum ) {
$Result{Successful} = 0;
$Result{Message} = $LayoutObject->{LanguageObject}->Translate(
"Error: Please set the value for innodb_log_file_size on your database to at least %s MB (current: %s MB, recommended: %s MB). For more information, please have a look at %s.",
$MySQLInnoDBLogFileSizeMinimum,
$MySQLInnoDBLogFileSize,
$MySQLInnoDBLogFileSizeRecommended,
'http://dev.mysql.com/doc/refman/5.6/en/innodb-data-log-reconfiguration.html',
);
}
}
# Check character_set_database value.
my $Charset = $Result{DBH}->selectall_arrayref("SHOW variables LIKE 'character_set_database'");
if ( $Charset->[0]->[1] =~ /utf8mb4/i ) {
$Result{Successful} = 0;
$Result{Message} = $LayoutObject->{LanguageObject}->Translate(
"Wrong database collation (%s is %s, but it needs to be utf8).",
'character_set_database',
$Charset->[0]->[1],
);
}
elsif ( $Charset->[0]->[1] !~ /utf8/i ) {
$Result{Successful} = 0;
$Result{Message} = $LayoutObject->{LanguageObject}->Translate(
"Wrong database collation (%s is %s, but it needs to be utf8).",
'character_set_database',
$Charset->[0]->[1],
);
}
}
# Delete not necessary key/value pairs.
delete $Result{DB};
delete $Result{DBH};
return %Result;
}
sub CheckMailConfiguration {
my ( $Self, %Param ) = @_;
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
# First check outbound mail config.
my $OutboundMailType =
$ParamObject->GetParam( Param => 'OutboundMailType' );
my $SMTPHost = $ParamObject->GetParam( Param => 'SMTPHost' );
my $SMTPPort = $ParamObject->GetParam( Param => 'SMTPPort' );
my $SMTPAuthUser =
$ParamObject->GetParam( Param => 'SMTPAuthUser' );
my $SMTPAuthPassword =
$ParamObject->GetParam( Param => 'SMTPAuthPassword' );
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# If chosen config option is SMTP, set some Config params.
if ( $OutboundMailType && $OutboundMailType ne 'sendmail' ) {
$ConfigObject->Set(
Key => 'SendmailModule',
Value => 'Kernel::System::Email::' . uc($OutboundMailType),
);
$ConfigObject->Set(
Key => 'SendmailModule::Host',
Value => $SMTPHost,
);
$ConfigObject->Set(
Key => 'SendmailModule::Port',
Value => $SMTPPort,
);
if ($SMTPAuthUser) {
$ConfigObject->Set(
Key => 'SendmailModule::AuthUser',
Value => $SMTPAuthUser,
);
}
if ($SMTPAuthPassword) {
$ConfigObject->Set(
Key => 'SendmailModule::AuthPassword',
Value => $SMTPAuthPassword,
);
}
}
# If sendmail, set config to sendmail.
else {
$ConfigObject->Set(
Key => 'SendmailModule',
Value => 'Kernel::System::Email::Sendmail',
);
}
# If config option SMTP and no SMTP host given, return with error.
if ( $OutboundMailType ne 'sendmail' && !$SMTPHost ) {
return (
Successful => 0,
Message => 'No SMTP Host given!'
);
}
# Check outbound mail configuration.
my $SendObject = $Kernel::OM->Get('Kernel::System::Email');
my %Result = $SendObject->Check();
my $SysConfigObject = $Kernel::OM->Get('Kernel::System::SysConfig');
my $ExclusiveLockGUID = $SysConfigObject->SettingLock(
LockAll => 1,
Force => 1,
UserID => 1,
);
# If SMTP check was successful, write data into config.
my $SendmailModule = $ConfigObject->Get('SendmailModule');
if (
$Result{Successful}
&& $SendmailModule ne 'Kernel::System::Email::Sendmail'
)
{
my %NewConfigs = (
'SendmailModule' => $SendmailModule,
'SendmailModule::Host' => $SMTPHost,
'SendmailModule::Port' => $SMTPPort,
);
for my $SettingName ( sort keys %NewConfigs ) {
$SysConfigObject->SettingUpdate(
Name => $SettingName,
IsValid => 1,
EffectiveValue => $NewConfigs{$SettingName},
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => 1,
);
}
if ( $SMTPAuthUser && $SMTPAuthPassword ) {
%NewConfigs = (
'SendmailModule::AuthUser' => $SMTPAuthUser,
'SendmailModule::AuthPassword' => $SMTPAuthPassword,
);
for my $SettingName ( sort keys %NewConfigs ) {
$SysConfigObject->SettingUpdate(
Name => $SettingName,
IsValid => 1,
EffectiveValue => $NewConfigs{$SettingName},
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => 1,
);
}
}
}
# If sendmail check was successful, write data into config.
elsif (
$Result{Successful}
&& $SendmailModule eq 'Kernel::System::Email::Sendmail'
)
{
$SysConfigObject->SettingUpdate(
Name => 'SendmailModule',
IsValid => 1,
EffectiveValue => $ConfigObject->Get('SendmailModule'),
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => 1,
);
}
# Now check inbound mail config. return if the outbound config threw an error.
if ( !$Result{Successful} ) {
return %Result;
}
# Check inbound mail config.
my $MailAccount = $Kernel::OM->Get('Kernel::System::MailAccount');
for (qw(InboundUser InboundPassword InboundHost)) {
if ( !$ParamObject->GetParam( Param => $_ ) ) {
return (
Successful => 0,
Message => "Missing parameter: $_!"
);
}
}
my $InboundUser = $ParamObject->GetParam( Param => 'InboundUser' );
my $InboundPassword =
$ParamObject->GetParam( Param => 'InboundPassword' );
my $InboundHost = $ParamObject->GetParam( Param => 'InboundHost' );
my $InboundMailType =
$ParamObject->GetParam( Param => 'InboundMailType' );
%Result = $MailAccount->MailAccountCheck(
Login => $InboundUser,
Password => $InboundPassword,
Host => $InboundHost,
Type => $InboundMailType,
Timeout => '60',
Debug => '0',
);
# If successful, add mail account to DB.
if ( $Result{Successful} ) {
my $ID = $MailAccount->MailAccountAdd(
Login => $InboundUser,
Password => $InboundPassword,
Host => $InboundHost,
Type => $InboundMailType,
ValidID => 1,
Trusted => 0,
DispatchingBy => 'From',
QueueID => 1,
UserID => 1,
);
if ( !$ID ) {
return (
Successful => 0,
Message => 'Error while adding mail account!'
);
}
}
return %Result;
}
sub _CheckConfig {
my ( $Self, %Param ) = @_;
my $SysConfigObject = $Kernel::OM->Get('Kernel::System::SysConfig');
my @Result = $SysConfigObject->ConfigurationSearch(
Search => 'ProductName',
);
return 1 if @Result;
my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home');
return $SysConfigObject->ConfigurationXML2DB(
UserID => 1,
Directory => "$Home/Kernel/Config/Files/XML",
Force => 1,
CleanUp => 1,
);
}
1;