blob: 7f473cc77f2b5035f0f0d1622b47f45c34468761 [file] [log] [blame]
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Install::Localconfig;
# NOTE: This package may "use" any modules that it likes. However,
# all functions in this package should assume that:
#
# * The data/ directory does not exist.
# * Templates are not available.
# * Files do not have the correct permissions
# * The database is not up to date
use 5.10.1;
use strict;
use warnings;
use Bugzilla::Constants;
use Bugzilla::Install::Util qw(bin_loc install_string);
use Bugzilla::Util qw(generate_random_password wrap_hard);
use Data::Dumper;
use File::Basename qw(dirname);
use Safe;
use Term::ANSIColor;
use parent qw(Exporter);
our @EXPORT_OK = qw(
read_localconfig
update_localconfig
);
use constant LOCALCONFIG_VARS => (
{
name => 'create_htaccess',
default => 1,
},
{
name => 'webservergroup',
default => ON_WINDOWS ? '' : 'apache',
},
{
name => 'use_suexec',
default => 0,
},
{
name => 'db_driver',
default => 'mysql',
},
{
name => 'db_host',
default => 'localhost',
},
{
name => 'db_name',
default => 'bugs',
},
{
name => 'db_user',
default => 'bugs',
},
{
name => 'db_pass',
default => '',
},
{
name => 'db_port',
default => 0,
},
{
name => 'db_sock',
default => '',
},
{
name => 'db_check',
default => 1,
},
{
name => 'db_mysql_ssl_ca_file',
default => '',
},
{
name => 'db_mysql_ssl_ca_path',
default => '',
},
{
name => 'db_mysql_ssl_client_cert',
default => '',
},
{
name => 'db_mysql_ssl_client_key',
default => '',
},
{
name => 'index_html',
default => 0,
},
{
name => 'interdiffbin',
default => sub { bin_loc('interdiff') },
},
{
name => 'diffpath',
default => sub { dirname(bin_loc('diff')) },
},
{
name => 'site_wide_secret',
# 64 characters is roughly the equivalent of a 384-bit key, which
# is larger than anybody would ever be able to brute-force.
default => sub { generate_random_password(64) },
},
);
sub read_localconfig {
my ($include_deprecated) = @_;
my $filename = bz_locations()->{'localconfig'};
my %localconfig;
if (-e $filename) {
my $s = new Safe;
# Some people like to store their database password in another file.
$s->permit('dofile');
$s->rdo($filename);
if ($@ || $!) {
my $err_msg = $@ ? $@ : $!;
die install_string('error_localconfig_read',
{ error => $err_msg, localconfig => $filename }), "\n";
}
my @read_symbols;
if ($include_deprecated) {
# First we have to get the whole symbol table
my $safe_root = $s->root;
my %safe_package;
{ no strict 'refs'; %safe_package = %{$safe_root . "::"}; }
# And now we read the contents of every var in the symbol table.
# However:
# * We only include symbols that start with an alphanumeric
# character. This excludes symbols like "_<./localconfig"
# that show up in some perls.
# * We ignore the INC symbol, which exists in every package.
# * Perl 5.10 imports a lot of random symbols that all
# contain "::", and we want to ignore those.
@read_symbols = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ }
(keys %safe_package);
}
else {
@read_symbols = map($_->{name}, LOCALCONFIG_VARS);
}
foreach my $var (@read_symbols) {
my $glob = $s->varglob($var);
# We can't get the type of a variable out of a Safe automatically.
# We can only get the glob itself. So we figure out its type this
# way, by trying first a scalar, then an array, then a hash.
#
# The interesting thing is that this converts all deprecated
# array or hash vars into hashrefs or arrayrefs, but that's
# fine since as I write this all modern localconfig vars are
# actually scalars.
if (defined $$glob) {
$localconfig{$var} = $$glob;
}
elsif (@$glob) {
$localconfig{$var} = \@$glob;
}
elsif (%$glob) {
$localconfig{$var} = \%$glob;
}
}
}
return \%localconfig;
}
#
# This is quite tricky. But fun!
#
# First we read the file 'localconfig'. Then we check if the variables we
# need are defined. If not, we will append the new settings to
# localconfig, instruct the user to check them, and stop.
#
# Why do it this way?
#
# Assume we will enhance Bugzilla and eventually more local configuration
# stuff arises on the horizon.
#
# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. You
# know, we never want to overwrite your own version of 'localconfig', so
# we can't put it into the CVS/tarfile, can we?
#
# Now, when we need a new variable, we simply add the necessary stuff to
# LOCALCONFIG_VARS. When the user gets the new version of Bugzilla from CVS and
# runs checksetup, it finds out "Oh, there is something new". Then it adds
# some default value to the user's local setup and informs the user to
# check that to see if it is what the user wants.
#
# Cute, ey?
#
sub update_localconfig {
my ($params) = @_;
my $output = $params->{output} || 0;
my $answer = Bugzilla->installation_answers;
my $localconfig = read_localconfig('include deprecated');
my @new_vars;
foreach my $var (LOCALCONFIG_VARS) {
my $name = $var->{name};
my $value = $localconfig->{$name};
# Regenerate site_wide_secret if it was made by our old, weak
# generate_random_password. Previously we used to generate
# a 256-character string for site_wide_secret.
$value = undef if ($name eq 'site_wide_secret' and defined $value
and length($value) == 256);
if (!defined $value) {
$var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
if (exists $answer->{$name}) {
$localconfig->{$name} = $answer->{$name};
}
else {
# If the user did not supply an answers file, then they get
# notified about every variable that gets added. If there was
# an answer file, then we don't notify about site_wide_secret
# because we assume the intent was to auto-generate it anyway.
if (!scalar(keys %$answer) || $name ne 'site_wide_secret') {
push(@new_vars, $name);
}
$localconfig->{$name} = $var->{default};
}
}
}
if (!$localconfig->{'interdiffbin'} && $output) {
print "\n", install_string('patchutils_missing'), "\n";
}
my @old_vars;
foreach my $var (keys %$localconfig) {
push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS);
}
my $filename = bz_locations->{'localconfig'};
# Move any custom or old variables into a separate file.
if (scalar @old_vars) {
my $filename_old = "$filename.old";
open(my $old_file, ">>:utf8", $filename_old)
or die "$filename_old: $!";
local $Data::Dumper::Purity = 1;
foreach my $var (@old_vars) {
print $old_file Data::Dumper->Dump([$localconfig->{$var}],
["*$var"]) . "\n\n";
}
close $old_file;
my $oldstuff = join(', ', @old_vars);
print install_string('lc_old_vars',
{ localconfig => $filename, old_file => $filename_old,
vars => $oldstuff }), "\n";
}
# Re-write localconfig
open(my $fh, ">:utf8", $filename) or die "$filename: $!";
foreach my $var (LOCALCONFIG_VARS) {
my $name = $var->{name};
my $desc = install_string("localconfig_$name", { root => ROOT_USER });
chomp($desc);
# Make the description into a comment.
$desc =~ s/^/# /mg;
print $fh $desc, "\n",
Data::Dumper->Dump([$localconfig->{$name}],
["*$name"]), "\n";
}
if (@new_vars) {
my $newstuff = join(', ', @new_vars);
print "\n";
print colored(install_string('lc_new_vars', { localconfig => $filename,
new_vars => wrap_hard($newstuff, 70) }),
COLOR_ERROR), "\n";
exit;
}
# Reset the cache for Bugzilla->localconfig so that it will be re-read
delete Bugzilla->request_cache->{localconfig};
return { old_vars => \@old_vars, new_vars => \@new_vars };
}
1;
__END__
=head1 NAME
Bugzilla::Install::Localconfig - Functions and variables dealing
with the manipulation and creation of the F<localconfig> file.
=head1 SYNOPSIS
use Bugzilla::Install::Requirements qw(update_localconfig);
update_localconfig({ output => 1 });
=head1 DESCRIPTION
This module is used primarily by L<checksetup.pl> to create and
modify the localconfig file. Most scripts should use L<Bugzilla/localconfig>
to access localconfig variables.
=head1 CONSTANTS
=over
=item C<LOCALCONFIG_VARS>
An array of hashrefs. These hashrefs contain three keys:
name - The name of the variable.
default - The default value for the variable. Should always be
something that can fit in a scalar.
desc - Additional text to put in localconfig before the variable
definition. Must end in a newline. Each line should start
with "#" unless you have some REALLY good reason not
to do that.
=item C<OLD_LOCALCONFIG_VARS>
An array of names of variables. If C<update_localconfig> finds these
variables defined in localconfig, it will print out a warning.
=back
=head1 SUBROUTINES
=over
=item C<read_localconfig>
=over
=item B<Description>
Reads the localconfig file and returns all valid values in a hashref.
=item B<Params>
=over
=item C<$include_deprecated>
C<true> if you want the returned hashref to include *any* variable
currently defined in localconfig, even if it doesn't exist in
C<LOCALCONFIG_VARS>. Generally this is is only for use
by L</update_localconfig>.
=back
=item B<Returns>
A hashref of the localconfig variables. If an array is defined in
localconfig, it will be an arrayref in the returned hash. If a
hash is defined, it will be a hashref in the returned hash.
Only includes variables specified in C<LOCALCONFIG_VARS>, unless
C<$include_deprecated> is true.
=back
=item C<update_localconfig>
Description: Adds any new variables to localconfig that aren't
currently defined there. Also optionally prints out
a message about vars that *should* be there and aren't.
Exits the program if it adds any new vars.
Params: C<$output> - C<true> if the function should display informational
output and warnings. It will always display errors or
any message which would cause program execution to halt.
Returns: A hashref, with C<old_vals> being an array of names of variables
that were removed, and C<new_vals> being an array of names
of variables that were added to localconfig.
=back