Common.pm 15.5 KB
Newer Older
1 2 3
# 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/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7 8 9

package Bugzilla::Config::Common;

10
use 5.10.1;
11 12
use strict;

13
use Email::Address;
14 15 16 17
use Socket;

use Bugzilla::Util;
use Bugzilla::Constants;
18
use Bugzilla::Field;
19
use Bugzilla::Group;
20
use Bugzilla::Status;
21

22
use parent qw(Exporter);
23
@Bugzilla::Config::Common::EXPORT =
24
    qw(check_multi check_numeric check_regexp check_url check_group
25
       check_sslbase check_priority check_severity check_platform
26
       check_opsys check_shadowdb check_urlbase check_webdotbase
27
       check_user_verify_class check_ip check_font_file
28
       check_mail_delivery_method check_notification check_utf8
29
       check_bug_status check_smtp_auth check_theschwartz_available
30
       check_maxattachmentsize check_email check_smtp_ssl
31
       check_comment_taggers_group
32 33 34 35 36 37 38 39 40 41 42 43 44 45
);

# 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 "";
    }
46 47
    elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
        foreach my $chkParam (split(',', $value)) {
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
            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 $@;
}

75 76 77 78 79 80 81 82
sub check_email {
    my ($value) = @_;
    if ($value !~ $Email::Address::mailbox) {
        return "must be a valid email address.";
    }
    return "";
}

83 84 85 86 87 88 89
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;
90 91 92 93 94
        # Fall back to port 443 if for some reason getservbyname() fails.
        my $port = getservbyname('https', 'tcp') || 443;
        if ($host =~ /^(.+):(\d+)$/) {
            $host = $1;
            $port = $2;
95 96 97 98
        }
        local *SOCK;
        my $proto = getprotobyname('tcp');
        socket(SOCK, PF_INET, SOCK_STREAM, $proto);
99 100
        my $iaddr = inet_aton($host) || return "The host $host cannot be resolved";
        my $sin = sockaddr_in($port, $iaddr);
101
        if (!connect(SOCK, $sin)) {
102
            return "Failed to connect to $host:$port ($!); unable to enable SSL";
103
        }
104
        close(SOCK);
105 106
    }
    return "";
107 108
}

109 110 111 112 113 114 115 116 117
sub check_ip {
    my $inbound_proxies = shift;
    my @proxies = split(/[\s,]+/, $inbound_proxies);
    foreach my $proxy (@proxies) {
        validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
    }
    return "";
}

118 119 120 121 122 123 124 125 126 127
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 "";
128 129 130 131
}

sub check_priority {
    my ($value) = (@_);
132
    my $legal_priorities = get_legal_field_values('priority');
133
    if (!grep($_ eq $value, @$legal_priorities)) {
134
        return "Must be a legal priority value: one of " .
135
            join(", ", @$legal_priorities);
136 137 138 139 140 141
    }
    return "";
}

sub check_severity {
    my ($value) = (@_);
142
    my $legal_severities = get_legal_field_values('bug_severity');
143
    if (!grep($_ eq $value, @$legal_severities)) {
144
        return "Must be a legal severity value: one of " .
145
            join(", ", @$legal_severities);
146 147 148 149 150 151
    }
    return "";
}

sub check_platform {
    my ($value) = (@_);
152
    my $legal_platforms = get_legal_field_values('rep_platform');
153
    if (!grep($_ eq $value, '', @$legal_platforms)) {
154
        return "Must be empty or a legal platform value: one of " .
155
            join(", ", @$legal_platforms);
156 157 158 159 160 161
    }
    return "";
}

sub check_opsys {
    my ($value) = (@_);
162
    my $legal_OS = get_legal_field_values('op_sys');
163
    if (!grep($_ eq $value, '', @$legal_OS)) {
164
        return "Must be empty or a legal operating system value: one of " .
165
            join(", ", @$legal_OS);
166 167 168 169
    }
    return "";
}

170 171
sub check_bug_status {
    my $bug_status = shift;
172
    my @closed_bug_statuses = map {$_->name} closed_bug_statuses();
173
    if (!grep($_ eq $bug_status, @closed_bug_statuses)) {
174 175 176 177 178
        return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
    }
    return "";
}

179 180
sub check_group {
    my $group_name = shift;
181
    return "" unless $group_name;
182 183 184 185 186 187 188
    my $group = new Bugzilla::Group({'name' => $group_name});
    unless (defined $group) {
        return "Must be an existing group name";
    }
    return "";
}

189 190 191 192 193 194 195
sub check_shadowdb {
    my ($value) = (@_);
    $value = trim($value);
    if ($value eq "") {
        return "";
    }

196
    if (!Bugzilla->params->{'shadowdbhost'}) {
197 198 199 200 201 202 203 204 205 206 207
        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) = (@_);
208
    if ($url && $url !~ m:^http.*/$:) {
209 210 211 212 213
        return "must be a legal URL, that starts with http and ends with a slash.";
    }
    return "";
}

214 215 216 217 218 219 220 221 222
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 '';
}

223 224 225 226 227 228 229 230 231 232 233
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
234
        my $webdotdir = bz_locations()->{'webdotdir'};
235 236 237 238 239 240 241 242 243 244 245
        if(-e "$webdotdir/.htaccess") {
            open HTACCESS, "$webdotdir/.htaccess";
            if(! grep(/ \\\.png\$/,<HTACCESS>)) {
                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 "";
}

246 247 248 249 250 251 252 253 254 255 256 257 258 259
sub check_font_file {
    my ($font) = @_;
    $font = trim($font);
    return '' unless $font;

    if ($font !~ /\.ttf$/) {
        return "The file must point to a TrueType font file (its extension must be .ttf)"
    }
    if (! -f $font) {
        return "The file '$font' cannot be found. Make sure you typed the full path to the file"
    }
    return '';
}

260 261 262 263 264 265 266 267 268
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.

269
    my $params = Bugzilla->params;
270
    my ($list, $entry) = @_;
271
    $list || return 'You need to specify at least one authentication mechanism';
272 273 274
    for my $class (split /,\s*/, $list) {
        my $res = check_multi($class, $entry);
        return $res if $res;
275
        if ($class eq 'RADIUS') {
276 277 278 279 280 281 282
            if (!Bugzilla->feature('auth_radius')) {
                return "RADIUS support is not available. Run checksetup.pl"
                       . " for more details";
            }
            return "RADIUS servername (RADIUS_server) is missing"
                if !$params->{"RADIUS_server"};
            return "RADIUS_secret is empty" if !$params->{"RADIUS_secret"};
283 284
        }
        elsif ($class eq 'LDAP') {
285 286 287 288 289 290 291
            if (!Bugzilla->feature('auth_ldap')) {
                return "LDAP support is not available. Run checksetup.pl"
                       . " for more details";
            }
            return "LDAP servername (LDAPserver) is missing" 
                if !$params->{"LDAPserver"};
            return "LDAPBaseDN is empty" if !$params->{"LDAPBaseDN"};
292
        }
293 294 295 296 297 298 299 300
    }
    return "";
}

sub check_mail_delivery_method {
    my $check = check_multi(@_);
    return $check if $check;
    my $mailer = shift;
301
    if ($mailer eq 'sendmail' and ON_WINDOWS) {
302 303 304 305 306
        # look for sendmail.exe 
        return "Failed to locate " . SENDMAIL_EXE
            unless -e SENDMAIL_EXE;
    }
    return "";
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
}

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 "";
325 326
}

327 328 329
sub check_notification {
    my $option = shift;
    my @current_version =
330
        (BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
331 332 333 334 335 336
    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";
    }
337 338 339 340
    if ($option ne 'disabled' && !Bugzilla->feature('updates')) {
        return "Some Perl modules are missing to get notifications about " .
               "new releases. See the output of checksetup.pl for more information";
    }
341 342 343
    return "";
}

344 345
sub check_smtp_auth {
    my $username = shift;
346 347 348
    if ($username and !Bugzilla->feature('smtp_auth')) {
        return "SMTP Authentication is not available. Run checksetup.pl for"
               . " more details";
349 350
    }
    return "";
351 352
}

353 354 355 356 357 358 359 360
sub check_smtp_ssl {
    my $use_ssl = shift;
    if ($use_ssl && !Bugzilla->feature('smtp_ssl')) {
        return "SSL support is not available. Run checksetup.pl for more details";
    }
    return "";
}

361
sub check_theschwartz_available {
362 363
    my $use_queue = shift;
    if ($use_queue && !Bugzilla->feature('jobqueue')) {
364 365 366 367 368
        return "Using the job queue requires that you have certain Perl"
               . " modules installed. See the output of checksetup.pl"
               . " for more information";
    }
    return "";
369
}
370

371 372 373 374 375 376 377 378
sub check_comment_taggers_group {
    my $group_name = shift;
    if ($group_name && !Bugzilla->feature('jsonrpc')) {
        return "Comment tagging requires installation of the JSONRPC feature";
    }
    return check_group($group_name);
}

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
# 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)
395 396
# p -- A short text entry field (as with type = 't'), but the string is
#      replaced by asterisks (appropriate for passwords)
397 398 399 400
# 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
401 402
#      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.:
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
#       {
#         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.
#
418 419 420 421 422
# 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.
#
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
# 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

456 457 458
All parameter checking functions are called with two parameters: the value to 
check, and a hash with the details of the param (type, default etc.) as defined
in the relevant F<Bugzilla::Config::*> package.
459 460 461 462 463 464 465

=head2 Functions

=over

=item C<check_multi>

466
Checks that a multi-valued parameter (ie types C<s>, C<o> or C<m>) satisfies
467 468 469 470 471 472 473 474 475 476
its contraints.

=item C<check_numeric>

Checks that the value is a valid number

=item C<check_regexp>

Checks that the value is a valid regexp

477 478 479 480 481
=item C<check_comment_taggers_group>

Checks that the required modules for comment tagging are installed, and that a
valid group is provided.

482
=back
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

=head1 B<Methods in need of POD>

=over

=item check_notification

=item check_priority

=item check_ip

=item check_user_verify_class

=item check_bug_status

=item check_shadowdb

=item check_smtp_auth

=item check_url

=item check_urlbase

=item check_email

=item check_webdotbase

510 511
=item check_font_file

512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
=item get_param_list

=item check_maxattachmentsize

=item check_utf8

=item check_group

=item check_opsys

=item check_platform

=item check_severity

=item check_sslbase

=item check_mail_delivery_method

=item check_theschwartz_available

=item check_smtp_ssl

=back