# -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Bug Tracking System. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): Terry Weissman # Dawn Endico # Dan Mosedale # Joe Robins # Jacob Steenhagen # J. Paul Reed # Bradley Baetz # Joseph Heenan # Erik Stambaugh # Frédéric Buclin # Marc Schumann # package Bugzilla::Config::Common; use strict; use Socket; use Bugzilla::Util; use Bugzilla::Constants; use Bugzilla::Field; use Bugzilla::Group; use Bugzilla::Status; use base qw(Exporter); @Bugzilla::Config::Common::EXPORT = qw(check_multi check_numeric check_regexp check_url check_group check_sslbase check_priority check_severity check_platform check_opsys check_shadowdb check_urlbase check_webdotbase check_netmask check_user_verify_class check_mail_delivery_method check_notification check_utf8 check_bug_status check_smtp_auth check_theschwartz_available check_maxattachmentsize ); # Checking functions for the various values sub check_multi { my ($value, $param) = (@_); if ($param->{'type'} eq "s") { unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) { return "Invalid choice '$value' for single-select list param '$param->{'name'}'"; } return ""; } elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') { foreach my $chkParam (split(',', $value)) { unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) { return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'"; } } return ""; } else { return "Invalid param type '$param->{'type'}' for check_multi(); " . "contact your Bugzilla administrator"; } } sub check_numeric { my ($value) = (@_); if ($value !~ /^[0-9]+$/) { return "must be a numeric value"; } return ""; } sub check_regexp { my ($value) = (@_); eval { qr/$value/ }; return $@; } sub check_sslbase { my $url = shift; if ($url ne '') { if ($url !~ m#^https://([^/]+).*/$#) { return "must be a legal URL, that starts with https and ends with a slash."; } my $host = $1; # Fall back to port 443 if for some reason getservbyname() fails. my $port = getservbyname('https', 'tcp') || 443; if ($host =~ /^(.+):(\d+)$/) { $host = $1; $port = $2; } local *SOCK; my $proto = getprotobyname('tcp'); socket(SOCK, PF_INET, SOCK_STREAM, $proto); my $iaddr = inet_aton($host) || return "The host $host cannot be resolved"; my $sin = sockaddr_in($port, $iaddr); if (!connect(SOCK, $sin)) { return "Failed to connect to $host:$port; unable to enable SSL"; } close(SOCK); } return ""; } sub check_utf8 { my $utf8 = shift; # You cannot turn off the UTF-8 parameter if you've already converted # your tables to utf-8. my $dbh = Bugzilla->dbh; if ($dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8) { return "You cannot disable UTF-8 support, because your MySQL database" . " is encoded in UTF-8"; } return ""; } sub check_priority { my ($value) = (@_); my $legal_priorities = get_legal_field_values('priority'); if (lsearch($legal_priorities, $value) < 0) { return "Must be a legal priority value: one of " . join(", ", @$legal_priorities); } return ""; } sub check_severity { my ($value) = (@_); my $legal_severities = get_legal_field_values('bug_severity'); if (lsearch($legal_severities, $value) < 0) { return "Must be a legal severity value: one of " . join(", ", @$legal_severities); } return ""; } sub check_platform { my ($value) = (@_); my $legal_platforms = get_legal_field_values('rep_platform'); if (lsearch(['', @$legal_platforms], $value) < 0) { return "Must be empty or a legal platform value: one of " . join(", ", @$legal_platforms); } return ""; } sub check_opsys { my ($value) = (@_); my $legal_OS = get_legal_field_values('op_sys'); if (lsearch(['', @$legal_OS], $value) < 0) { return "Must be empty or a legal operating system value: one of " . join(", ", @$legal_OS); } return ""; } sub check_bug_status { my $bug_status = shift; my @closed_bug_statuses = map {$_->name} closed_bug_statuses(); if (lsearch(\@closed_bug_statuses, $bug_status) < 0) { return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses); } return ""; } sub check_group { my $group_name = shift; return "" unless $group_name; my $group = new Bugzilla::Group({'name' => $group_name}); unless (defined $group) { return "Must be an existing group name"; } return ""; } sub check_shadowdb { my ($value) = (@_); $value = trim($value); if ($value eq "") { return ""; } if (!Bugzilla->params->{'shadowdbhost'}) { return "You need to specify a host when using a shadow database"; } # Can't test existence of this because ConnectToDatabase uses the param, # but we can't set this before testing.... # This can really only be fixed after we can use the DBI more openly return ""; } sub check_urlbase { my ($url) = (@_); if ($url && $url !~ m:^http.*/$:) { return "must be a legal URL, that starts with http and ends with a slash."; } return ""; } sub check_url { my ($url) = (@_); return '' if $url eq ''; # Allow empty URLs if ($url !~ m:/$:) { return 'must be a legal URL, absolute or relative, ending with a slash.'; } return ''; } sub check_webdotbase { my ($value) = (@_); $value = trim($value); if ($value eq "") { return ""; } if($value !~ /^https?:/) { if(! -x $value) { return "The file path \"$value\" is not a valid executable. Please specify the complete file path to 'dot' if you intend to generate graphs locally."; } # Check .htaccess allows access to generated images my $webdotdir = bz_locations()->{'webdotdir'}; if(-e "$webdotdir/.htaccess") { open HTACCESS, "$webdotdir/.htaccess"; if(! grep(/ \\\.png\$/,)) { return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n"; } close HTACCESS; } } return ""; } sub check_netmask { my ($mask) = @_; my $res = check_numeric($mask); return $res if $res; if ($mask < 0 || $mask > 32) { return "an IPv4 netmask must be between 0 and 32 bits"; } # Note that if we changed the netmask from anything apart from 32, then # existing logincookies which aren't for a single IP won't work # any more. We can't know which ones they are, though, so they'll just # take space until they're periodically cleared, later. return ""; } sub check_user_verify_class { # doeditparams traverses the list of params, and for each one it checks, # then updates. This means that if one param checker wants to look at # other params, it must be below that other one. So you can't have two # params mutually dependent on each other. # This means that if someone clears the LDAP config params after setting # the login method as LDAP, we won't notice, but all logins will fail. # So don't do that. my ($list, $entry) = @_; $list || return 'You need to specify at least one authentication mechanism'; for my $class (split /,\s*/, $list) { my $res = check_multi($class, $entry); return $res if $res; if ($class eq 'RADIUS') { eval "require Authen::Radius"; return "Error requiring Authen::Radius: '$@'" if $@; return "RADIUS servername (RADIUS_server) is missing" unless Bugzilla->params->{"RADIUS_server"}; return "RADIUS_secret is empty" unless Bugzilla->params->{"RADIUS_secret"}; } elsif ($class eq 'LDAP') { eval "require Net::LDAP"; return "Error requiring Net::LDAP: '$@'" if $@; return "LDAP servername (LDAPserver) is missing" unless Bugzilla->params->{"LDAPserver"}; return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"}; } } return ""; } sub check_mail_delivery_method { my $check = check_multi(@_); return $check if $check; my $mailer = shift; if ($mailer eq 'sendmail' && $^O =~ /MSWin32/i) { # look for sendmail.exe return "Failed to locate " . SENDMAIL_EXE unless -e SENDMAIL_EXE; } return ""; } sub check_maxattachmentsize { my $check = check_numeric(@_); return $check if $check; my $size = shift; my $dbh = Bugzilla->dbh; if ($dbh->isa('Bugzilla::DB::Mysql')) { my (undef, $max_packet) = $dbh->selectrow_array( q{SHOW VARIABLES LIKE 'max\_allowed\_packet'}); my $byte_size = $size * 1024; if ($max_packet < $byte_size) { return "You asked for a maxattachmentsize of $byte_size bytes," . " but the max_allowed_packet setting in MySQL currently" . " only allows packets up to $max_packet bytes"; } } return ""; } sub check_notification { my $option = shift; my @current_version = (BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/); if ($current_version[1] % 2 && $option eq 'stable_branch_release') { return "You are currently running a development snapshot, and so your " . "installation is not based on a branch. If you want to be notified " . "about the next stable release, you should select " . "'latest_stable_release' instead"; } return ""; } sub check_smtp_auth { my $username = shift; if ($username) { eval "require Authen::SASL"; return "Error requiring Authen::SASL: '$@'" if $@; } return ""; } sub check_theschwartz_available { if (!eval { require TheSchwartz; require Daemon::Generic; }) { return "Using the job queue requires that you have certain Perl" . " modules installed. See the output of checksetup.pl" . " for more information"; } return ""; } # OK, here are the parameter definitions themselves. # # Each definition is a hash with keys: # # name - name of the param # desc - description of the param (for editparams.cgi) # type - see below # choices - (optional) see below # default - default value for the param # checker - (optional) checking function for validating parameter entry # It is called with the value of the param as the first arg and a # reference to the param's hash as the second argument # # The type value can be one of the following: # # t -- A short text entry field (suitable for a single line) # p -- A short text entry field (as with type = 't'), but the string is # replaced by asterisks (appropriate for passwords) # l -- A long text field (suitable for many lines) # b -- A boolean value (either 1 or 0) # m -- A list of values, with many selectable (shows up as a select box) # To specify the list of values, make the 'choices' key be an array # reference of the valid choices. The 'default' key should be a string # with a list of selected values (as a comma-separated list), i.e.: # { # name => 'multiselect', # desc => 'A list of options, choose many', # type => 'm', # choices => [ 'a', 'b', 'c', 'd' ], # default => [ 'a', 'd' ], # checker => \&check_multi # } # # Here, 'a' and 'd' are the default options, and the user may pick any # combination of a, b, c, and d as valid options. # # &check_multi should always be used as the param verification function # for list (single and multiple) parameter types. # # o -- A list of values, orderable, and with many selectable (shows up as a # JavaScript-enhanced select box if JavaScript is enabled, and a text # entry field if not) # Set up in the same way as type m. # # s -- A list of values, with one selectable (shows up as a select box) # To specify the list of values, make the 'choices' key be an array # reference of the valid choices. The 'default' key should be one of # those values, i.e.: # { # name => 'singleselect', # desc => 'A list of options, choose one', # type => 's', # choices => [ 'a', 'b', 'c' ], # default => 'b', # checker => \&check_multi # } # # Here, 'b' is the default option, and 'a' and 'c' are other possible # options, but only one at a time! # # &check_multi should always be used as the param verification function # for list (single and multiple) parameter types. sub get_param_list { return; } 1; __END__ =head1 NAME Bugzilla::Config::Common - Parameter checking functions =head1 DESCRIPTION All parameter checking functions are called with two parameters: =head2 Functions =over =item C Checks that a multi-valued parameter (ie types C, C or C) satisfies its contraints. =item C Checks that the value is a valid number =item C Checks that the value is a valid regexp =back