Common.pm 12.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
# -*- 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 <terry@mozilla.org>
#                 Dawn Endico <endico@mozilla.org>
#                 Dan Mosedale <dmose@mozilla.org>
#                 Joe Robins <jmrobins@tgix.com>
#                 Jacob Steenhagen <jake@bugzilla.org>
#                 J. Paul Reed <preed@sigkill.com>
#                 Bradley Baetz <bbaetz@student.usyd.edu.au>
#                 Joseph Heenan <joseph@heenan.me.uk>
#                 Erik Stambaugh <erik@dasbistro.com>
#                 Frédéric Buclin <LpSolit@gmail.com>
#

package Bugzilla::Config::Common;

use strict;

use Socket;

use Bugzilla::Util;
use Bugzilla::Constants;
40
use Bugzilla::Field;
41
use Bugzilla::Group;
42 43 44

use base qw(Exporter);
@Bugzilla::Config::Common::EXPORT =
45
    qw(check_multi check_numeric check_regexp check_url check_group
46
       check_sslbase check_priority check_severity check_platform
47 48
       check_opsys check_shadowdb check_urlbase check_webdotbase
       check_netmask check_user_verify_class check_image_converter
49
       check_languages check_mail_delivery_method check_notification
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
);

# 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") {
        foreach my $chkParam (@$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;
        if ($host =~ /:\d+$/) {
            return "must not contain a port.";
        }
        local *SOCK;
        my $proto = getprotobyname('tcp');
        socket(SOCK, PF_INET, SOCK_STREAM, $proto);
        my $sin = sockaddr_in(443, inet_aton($host));
        if (!connect(SOCK, $sin)) {
            return "Failed to connect to " . html_quote($host) . 
                   ":443, unable to enable SSL.";
        }
    }
    return "";
}

sub check_priority {
    my ($value) = (@_);
117 118
    my $legal_priorities = get_legal_field_values('priority');
    if (lsearch($legal_priorities, $value) < 0) {
119
        return "Must be a legal priority value: one of " .
120
            join(", ", @$legal_priorities);
121 122 123 124 125 126
    }
    return "";
}

sub check_severity {
    my ($value) = (@_);
127 128
    my $legal_severities = get_legal_field_values('bug_severity');
    if (lsearch($legal_severities, $value) < 0) {
129
        return "Must be a legal severity value: one of " .
130
            join(", ", @$legal_severities);
131 132 133 134 135 136
    }
    return "";
}

sub check_platform {
    my ($value) = (@_);
137 138
    my $legal_platforms = get_legal_field_values('rep_platform');
    if (lsearch(['', @$legal_platforms], $value) < 0) {
139
        return "Must be empty or a legal platform value: one of " .
140
            join(", ", @$legal_platforms);
141 142 143 144 145 146
    }
    return "";
}

sub check_opsys {
    my ($value) = (@_);
147 148
    my $legal_OS = get_legal_field_values('op_sys');
    if (lsearch(['', @$legal_OS], $value) < 0) {
149
        return "Must be empty or a legal operating system value: one of " .
150
            join(", ", @$legal_OS);
151 152 153 154
    }
    return "";
}

155 156
sub check_group {
    my $group_name = shift;
157
    return "" unless $group_name;
158 159 160 161 162 163 164
    my $group = new Bugzilla::Group({'name' => $group_name});
    unless (defined $group) {
        return "Must be an existing group name";
    }
    return "";
}

165 166 167 168 169 170 171
sub check_shadowdb {
    my ($value) = (@_);
    $value = trim($value);
    if ($value eq "") {
        return "";
    }

172
    if (!Bugzilla->params->{'shadowdbhost'}) {
173 174 175 176 177 178 179 180 181 182 183
        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) = (@_);
184
    if ($url && $url !~ m:^http.*/$:) {
185 186 187 188 189
        return "must be a legal URL, that starts with http and ends with a slash.";
    }
    return "";
}

190 191 192 193 194 195 196 197 198
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 '';
}

199 200 201 202 203 204 205 206 207 208 209
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
210
        my $webdotdir = bz_locations()->{'webdotdir'};
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
        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 "";
}

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
232
    # take space until they're periodically cleared, later.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

    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) = @_;
    for my $class (split /,\s*/, $list) {
        my $res = check_multi($class, $entry);
        return $res if $res;
        if ($class eq 'DB') {
            # No params
        } elsif ($class eq 'LDAP') {
            eval "require Net::LDAP";
            return "Error requiring Net::LDAP: '$@'" if $@;
255 256
            return "LDAP servername is missing" unless Bugzilla->params->{"LDAPserver"};
            return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"};
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
        } else {
                return "Unknown user_verify_class '$class' in check_user_verify_class";
        }
    }
    return "";
}

sub check_image_converter {
    my ($value, $hash) = @_;
    if ($value == 1){
       eval "require Image::Magick";
       return "Error requiring Image::Magick: '$@'" if $@;
    } 
    return "";
}

sub check_languages {
    my @languages = split /[,\s]+/, trim($_[0]);
    if(!scalar(@languages)) {
       return "You need to specify a language tag."
    }
278
    my $templatedir = bz_locations()->{'templatedir'};
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    foreach my $language (@languages) {
       if(   ! -d "$templatedir/$language/custom" 
          && ! -d "$templatedir/$language/default") {
          return "The template directory for $language does not exist";
       }
    }
    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 "";
}

300 301 302
sub check_notification {
    my $option = shift;
    my @current_version =
303
        (BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
304 305 306 307 308 309 310 311 312 313
    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 "";
}


314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
# 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)
# 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 an array
#      reference for the list of selected values (which must appear in the
#      first anonymous array), 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.
#
# 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<check_multi>

Checks that a multi-valued parameter (ie type C<s> or type C<m>) satisfies
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

=back