# 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 => 'cvsbin', default => sub { bin_loc('cvs') }, }, { 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 file. =head1 SYNOPSIS use Bugzilla::Install::Requirements qw(update_localconfig); update_localconfig({ output => 1 }); =head1 DESCRIPTION This module is used primarily by L to create and modify the localconfig file. Most scripts should use L to access localconfig variables. =head1 CONSTANTS =over =item C 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 An array of names of variables. If C finds these variables defined in localconfig, it will print out a warning. =back =head1 SUBROUTINES =over =item C =over =item B Reads the localconfig file and returns all valid values in a hashref. =item B =over =item C<$include_deprecated> C if you want the returned hashref to include *any* variable currently defined in localconfig, even if it doesn't exist in C. Generally this is is only for use by L. =back =item B 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, unless C<$include_deprecated> is true. =back =item C 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 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 being an array of names of variables that were removed, and C being an array of names of variables that were added to localconfig. =back