~ubuntu-branches/ubuntu/hardy/bugzilla/hardy-security

« back to all changes in this revision

Viewing changes to checksetup.pl

  • Committer: Bazaar Package Importer
  • Author(s): Rémi Perrot
  • Date: 2004-04-02 01:13:32 UTC
  • Revision ID: james.westby@ubuntu.com-20040402011332-hxrg0n2szimd7d25
Tags: upstream-2.16.5
Import upstream version 2.16.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bonsaitools/bin/perl -w
 
2
# -*- Mode: perl; indent-tabs-mode: nil -*-
 
3
#
 
4
# The contents of this file are subject to the Mozilla Public
 
5
# License Version 1.1 (the "License"); you may not use this file
 
6
# except in compliance with the License. You may obtain a copy of
 
7
# the License at http://www.mozilla.org/MPL/
 
8
#
 
9
# Software distributed under the License is distributed on an "AS
 
10
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 
11
# implied. See the License for the specific language governing
 
12
# rights and limitations under the License.
 
13
#
 
14
# The Original Code is mozilla.org code.
 
15
#
 
16
# The Initial Developer of the Original Code is Holger
 
17
# Schurig. Portions created by Holger Schurig are
 
18
# Copyright (C) 1999 Holger Schurig. All
 
19
# Rights Reserved.
 
20
#
 
21
# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
 
22
#                 Terry Weissman <terry@mozilla.org>
 
23
#                 Dan Mosedale <dmose@mozilla.org>
 
24
#                 Dave Miller <justdave@syndicomm.com>
 
25
#                 Zach Lipton  <zach@zachlipton.com>
 
26
#
 
27
#
 
28
# Direct any questions on this source code to
 
29
#
 
30
# Holger Schurig <holgerschurig@nikocity.de>
 
31
#
 
32
#
 
33
#
 
34
# Hey, what's this?
 
35
#
 
36
# 'checksetup.pl' is a script that is supposed to run during installation
 
37
# time and also after every upgrade.
 
38
#
 
39
# The goal of this script is to make the installation even more easy.
 
40
# It does so by doing things for you as well as testing for problems
 
41
# early.
 
42
#
 
43
# And you can re-run it whenever you want. Especially after Bugzilla
 
44
# gets updated you SHOULD rerun it. Because then it may update your
 
45
# SQL table definitions so that they are again in sync with the code.
 
46
#
 
47
# So, currently this module does:
 
48
#
 
49
#     - check for required perl modules
 
50
#     - set defaults for local configuration variables
 
51
#     - create and populate the data directory after installation
 
52
#     - set the proper rights for the *.cgi, *.html ... etc files
 
53
#     - check if the code can access MySQL
 
54
#     - creates the database 'bugs' if the database does not exist
 
55
#     - creates the tables inside the database if they don't exist
 
56
#     - automatically changes the table definitions of older BugZilla
 
57
#       installations
 
58
#     - populates the groups
 
59
#     - put the first user into all groups so that the system can
 
60
#       be administrated
 
61
#     - changes already existing SQL tables if you change your local
 
62
#       settings, e.g. when you add a new platform
 
63
#
 
64
# People that install this module locally are not supposed to modify
 
65
# this script. This is done by shifting the user settable stuff into
 
66
# a local configuration file 'localconfig'. When this file get's
 
67
# changed and 'checkconfig.pl' will be re-run, then the user changes
 
68
# will be reflected back into the database.
 
69
#
 
70
# Developers however have to modify this file at various places. To
 
71
# make this easier, I have added some special comments that one can
 
72
# search for.
 
73
#
 
74
#     To                                               Search for
 
75
#
 
76
#     add/delete local configuration variables         --LOCAL--
 
77
#     check for more prerequired modules               --MODULES--
 
78
#     change the defaults for local configuration vars --LOCAL--
 
79
#     update the assigned file permissions             --CHMOD--
 
80
#     add more MySQL-related checks                    --MYSQL--
 
81
#     change table definitions                         --TABLE--
 
82
#     add more groups                                  --GROUPS--
 
83
#     create initial administrator account             --ADMIN--
 
84
#
 
85
# Note: sometimes those special comments occur more then once. For
 
86
# example, --LOCAL-- is at least 3 times in this code!  --TABLE--
 
87
# also is used more than once. So search for every occurence!
 
88
#
 
89
 
 
90
 
 
91
###########################################################################
 
92
# Global definitions
 
93
###########################################################################
 
94
 
 
95
use diagnostics;
 
96
use strict;
 
97
 
 
98
# 12/17/00 justdave@syndicomm.com - removed declarations of the localconfig
 
99
# variables from this location.  We don't want these declared here.  They'll
 
100
# automatically get declared in the process of reading in localconfig, and
 
101
# this way we can look in the symbol table to see if they've been declared
 
102
# yet or not.
 
103
 
 
104
 
 
105
###########################################################################
 
106
# Check required module
 
107
###########################################################################
 
108
 
 
109
#
 
110
# Here we check for --MODULES--
 
111
#
 
112
 
 
113
print "\nChecking perl modules ...\n";
 
114
unless (eval "require 5.005") {
 
115
    die "Sorry, you need at least Perl 5.005\n";
 
116
}
 
117
 
 
118
# vers_cmp is adapted from Sort::Versions 1.3 1996/07/11 13:37:00 kjahds,
 
119
# which is not included with Perl by default, hence the need to copy it here.
 
120
# Seems silly to require it when this is the only place we need it...
 
121
sub vers_cmp {
 
122
  if (@_ < 2) { die "not enough parameters for vers_cmp" }
 
123
  if (@_ > 2) { die "too many parameters for vers_cmp" }
 
124
  my ($a, $b) = @_;
 
125
  my (@A) = ($a =~ /(\.|\d+|[^\.\d]+)/g);
 
126
  my (@B) = ($b =~ /(\.|\d+|[^\.\d]+)/g);
 
127
  my ($A,$B);
 
128
  while (@A and @B) {
 
129
    $A = shift @A;
 
130
    $B = shift @B;
 
131
    if ($A eq "." and $B eq ".") {
 
132
      next;
 
133
    } elsif ( $A eq "." ) {
 
134
      return -1;
 
135
    } elsif ( $B eq "." ) {
 
136
      return 1;
 
137
    } elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
 
138
      return $A <=> $B if $A <=> $B;
 
139
    } else {
 
140
      $A = uc $A;
 
141
      $B = uc $B;
 
142
      return $A cmp $B if $A cmp $B;
 
143
    }
 
144
  }
 
145
  @A <=> @B;
 
146
}
 
147
 
 
148
# This was originally clipped from the libnet Makefile.PL, adapted here to
 
149
# use the above vers_cmp routine for accurate version checking.
 
150
sub have_vers {
 
151
  my ($pkg, $wanted) = @_;
 
152
  my ($msg, $vnum, $vstr);
 
153
  no strict 'refs';
 
154
  printf("Checking for %15s %-9s ", $pkg, !$wanted?'(any)':"(v$wanted)");
 
155
 
 
156
  eval { my $p; ($p = $pkg . ".pm") =~ s!::!/!g; require $p; };
 
157
 
 
158
  $vnum = ${"${pkg}::VERSION"} || ${"${pkg}::Version"} || 0;
 
159
  $vnum = -1 if $@;
 
160
 
 
161
  if ($vnum eq "-1") { # string compare just in case it's non-numeric
 
162
    $vstr = "not found";
 
163
  }
 
164
  elsif (vers_cmp($vnum,"0") > -1) {
 
165
    $vstr = "found v$vnum";
 
166
  }
 
167
  else {
 
168
    $vstr = "found unknown version";
 
169
  }
 
170
 
 
171
  my $vok = (vers_cmp($vnum,$wanted) > -1);
 
172
  print ((($vok) ? "ok: " : " "), "$vstr\n");
 
173
  return $vok;
 
174
}
 
175
 
 
176
# Check versions of dependencies.  0 for version = any version acceptible
 
177
my $modules = [ 
 
178
    { 
 
179
        name => 'AppConfig',  
 
180
        version => '1.52' 
 
181
    }, 
 
182
    { 
 
183
        name => 'CGI::Carp', 
 
184
        version => '0' 
 
185
    }, 
 
186
    {
 
187
        name => 'Data::Dumper', 
 
188
        version => '0' 
 
189
    }, 
 
190
    {        
 
191
        name => 'Date::Parse', 
 
192
        version => '0' 
 
193
    }, 
 
194
    { 
 
195
        name => 'DBI', 
 
196
        version => '1.13' 
 
197
    }, 
 
198
    { 
 
199
        name => 'DBD::mysql', 
 
200
        version => '1.2209' 
 
201
    }, 
 
202
    { 
 
203
        name => 'File::Spec', 
 
204
        version => '0.82' 
 
205
    }, 
 
206
    { 
 
207
        name => 'File::Temp',
 
208
        version => '0'
 
209
    },
 
210
    { 
 
211
        name => 'Template', 
 
212
        version => '2.07' 
 
213
    }, 
 
214
    { 
 
215
        name => 'Text::Wrap', 
 
216
        version => '2001.0131' 
 
217
    } 
 
218
];
 
219
 
 
220
my %missing = ();
 
221
foreach my $module (@{$modules}) {
 
222
    unless (have_vers($module->{name}, $module->{version})) { 
 
223
        $missing{$module->{name}} = $module->{version};
 
224
    }
 
225
}
 
226
 
 
227
# If CGI::Carp was loaded successfully for version checking, it changes the
 
228
# die and warn handlers, we don't want them changed, so we need to stash the
 
229
# original ones and set them back afterwards -- justdave@syndicomm.com
 
230
my $saved_die_handler = $::SIG{__DIE__};
 
231
my $saved_warn_handler = $::SIG{__WARN__};
 
232
unless (have_vers("CGI::Carp",0))    { $missing{'CGI::Carp'} = 0 }
 
233
$::SIG{__DIE__} = $saved_die_handler;
 
234
$::SIG{__WARN__} = $saved_warn_handler;
 
235
 
 
236
print "\nThe following Perl modules are optional:\n";
 
237
my $charts = 0;
 
238
$charts++ if have_vers("GD","1.19");
 
239
$charts++ if have_vers("Chart::Base","0.99");
 
240
my $xmlparser = have_vers("XML::Parser",0);
 
241
 
 
242
print "\n";
 
243
if ($charts != 2) {
 
244
    print "If you you want to see graphical bug dependency charts, you may install\n",
 
245
    "the optional libgd and the Perl modules GD-1.19 and Chart::Base-0.99b, e.g. by\n",
 
246
    "running (as root)\n\n",
 
247
    "   perl -MCPAN -e'install \"LDS/GD-1.19.tar.gz\"'\n",
 
248
    "   perl -MCPAN -e'install \"N/NI/NINJAZ/Chart-0.99b.tar.gz\"'\n\n";
 
249
}
 
250
if (!$xmlparser) {
 
251
    print "If you want to use the bug import/export feature to move bugs to or from\n",
 
252
    "other bugzilla installations, you will need to install the XML::Parser module by\n",
 
253
    "running (as root)\n\n",
 
254
    "   perl -MCPAN -e'install \"XML::Parser\"'\n\n";
 
255
}
 
256
if (%missing) {
 
257
    print "\n\n";
 
258
    print "Bugzilla requires some Perl modules which are either missing from your\n",
 
259
    "system, or the version on your system is too old.\n",
 
260
    "They can be installed by running (as root) the following:\n";
 
261
    foreach my $module (keys %missing) {
 
262
        print "   perl -MCPAN -e 'install \"$module\"'\n";
 
263
        if ($missing{$module} > 0) {
 
264
            print "   Minimum version required: $missing{$module}\n";
 
265
        }
 
266
    }
 
267
    print "\n";
 
268
    exit;
 
269
}
 
270
 
 
271
 
 
272
###########################################################################
 
273
# Check and update local configuration
 
274
###########################################################################
 
275
 
 
276
#
 
277
# This is quite tricky. But fun!
 
278
#
 
279
# First we read the file 'localconfig'. Then we check if the variables we
 
280
# need are defined. If not, localconfig will be amended by the new settings
 
281
# and the user informed to check this. The program then stops.
 
282
#
 
283
# Why do it this way around?
 
284
#
 
285
# Assume we will enhance Bugzilla and eventually more local configuration
 
286
# stuff arises on the horizon.
 
287
#
 
288
# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. You
 
289
# know, we never want to overwrite your own version of 'localconfig', so
 
290
# we can't put it into the CVS/tarfile, can we?
 
291
#
 
292
# Now, we need a new variable. We simply add the necessary stuff to checksetup.
 
293
# The user get's the new version of Bugzilla from the CVS, runs checksetup
 
294
# and checksetup finds out "Oh, there is something new". Then it adds some
 
295
# default value to the user's local setup and informs the user to check that
 
296
# to see if that is what the user wants.
 
297
#
 
298
# Cute, ey?
 
299
#
 
300
 
 
301
print "Checking user setup ...\n";
 
302
$@ = undef;
 
303
do 'localconfig';
 
304
if ($@) { # capture errors in localconfig, bug 97290
 
305
   print STDERR <<EOT;
 
306
An error has occurred while reading your 
 
307
'localconfig' file.  The text of the error message is:
 
308
 
 
309
$@
 
310
 
 
311
Please fix the error in your 'localconfig' file.  
 
312
Alternately rename your 'localconfig' file, rerun 
 
313
checksetup.pl, and re-enter your answers.
 
314
 
 
315
  \$ mv -f localconfig localconfig.old
 
316
  \$ ./checksetup.pl
 
317
 
 
318
 
 
319
EOT
 
320
    die "Syntax error in localconfig";
 
321
}
 
322
my $newstuff = "";
 
323
sub LocalVar ($$)
 
324
{
 
325
    my ($name, $definition) = @_;
 
326
    return if ($main::{$name}); # if localconfig declared it, we're done.
 
327
    $newstuff .= " " . $name;
 
328
    open FILE, '>>localconfig';
 
329
    print FILE $definition, "\n\n";
 
330
    close FILE;
 
331
}
 
332
 
 
333
 
 
334
 
 
335
#
 
336
# Set up the defaults for the --LOCAL-- variables below:
 
337
#
 
338
 
 
339
LocalVar('index_html', <<'END');
 
340
#
 
341
# With the introduction of a configurable index page using the
 
342
# template toolkit, Bugzilla's main index page is now index.cgi.
 
343
# Most web servers will allow you to use index.cgi as a directory
 
344
# index and many come preconfigured that way, however if yours
 
345
# doesn't you'll need an index.html file that provides redirection
 
346
# to index.cgi. Setting $index_html to 1 below will allow
 
347
# checksetup.pl to create one for you if it doesn't exist.
 
348
# NOTE: checksetup.pl will not replace an existing file, so if you
 
349
#       wish to have checksetup.pl create one for you, you must
 
350
#       make sure that there isn't already an index.html
 
351
$index_html = 0;
 
352
END
 
353
 
 
354
my $mysql_binaries = `which mysql`;
 
355
if ($mysql_binaries =~ /no mysql/) {
 
356
    # If which didn't find it, just provide a reasonable default
 
357
    $mysql_binaries = "/usr/bin";
 
358
} else {
 
359
    $mysql_binaries =~ s:/mysql\n$::;
 
360
}
 
361
 
 
362
LocalVar('mysqlpath', <<"END");
 
363
#
 
364
# In order to do certain functions in Bugzilla (such as sync the shadow
 
365
# database), we require the MySQL Binaries (mysql, mysqldump, and mysqladmin).
 
366
# Because it's possible that these files aren't in your path, you can specify
 
367
# their location here.
 
368
# Please specify only the directory name, with no trailing slash.
 
369
\$mysqlpath = "$mysql_binaries";
 
370
END
 
371
 
 
372
 
 
373
LocalVar('create_htaccess', <<'END');
 
374
#
 
375
# If you are using Apache for your web server, Bugzilla can create .htaccess
 
376
# files for you that will instruct Apache not to serve files that shouldn't
 
377
# be accessed from the web (like your local configuration data and non-cgi
 
378
# executable files).  For this to work, the directory your Bugzilla
 
379
# installation is in must be within the jurisdiction of a <Directory> block
 
380
# in the httpd.conf file that has 'AllowOverride Limit' in it.  If it has
 
381
# 'AllowOverride All' or other options with Limit, that's fine.
 
382
# (Older Apache installations may use an access.conf file to store these
 
383
# <Directory> blocks.)
 
384
# If this is set to 1, Bugzilla will create these files if they don't exist.
 
385
# If this is set to 0, Bugzilla will not create these files.
 
386
$create_htaccess = 1;
 
387
END
 
388
 
 
389
    
 
390
LocalVar('webservergroup', '
 
391
#
 
392
# This is the group your web server runs on.
 
393
# If you have a windows box, ignore this setting.
 
394
# If you do not have access to the group your web server runs under,
 
395
# set this to "". If you do set this to "", then your Bugzilla installation
 
396
# will be _VERY_ insecure, because some files will be world readable/writable,
 
397
# and so anyone who can get local access to your machine can do whatever they
 
398
# want. You should only have this set to "" if this is a testing installation
 
399
# and you cannot set this up any other way. YOU HAVE BEEN WARNED.
 
400
# If you set this to anything besides "", you will need to run checksetup.pl
 
401
# as root, or as a user who is a member of the specified group.
 
402
$webservergroup = "nobody";
 
403
');
 
404
 
 
405
 
 
406
 
 
407
LocalVar('db_host', '
 
408
#
 
409
# How to access the SQL database:
 
410
#
 
411
$db_host = "localhost";         # where is the database?
 
412
$db_port = 3306;                # which port to use
 
413
$db_name = "bugs";              # name of the MySQL database
 
414
$db_user = "bugs";              # user to attach to the MySQL database
 
415
');
 
416
LocalVar('db_pass', '
 
417
#
 
418
# Enter your database password here. It\'s normally advisable to specify
 
419
# a password for your bugzilla database user.
 
420
# If you use apostrophe (\') or a backslash (\\) in your password, you\'ll
 
421
# need to escape it by preceding it with a \\ character. (\\\') or (\\\\)
 
422
#
 
423
$db_pass = \'\';
 
424
');
 
425
 
 
426
 
 
427
 
 
428
LocalVar('db_check', '
 
429
#
 
430
# Should checksetup.pl try to check if your MySQL setup is correct?
 
431
# (with some combinations of MySQL/Msql-mysql/Perl/moonphase this doesn\'t work)
 
432
#
 
433
$db_check = 1;
 
434
');
 
435
 
 
436
 
 
437
LocalVar('severities', '
 
438
#
 
439
# Which bug and feature-request severities do you want?
 
440
#
 
441
@severities = (
 
442
        "blocker",
 
443
        "critical",
 
444
        "major",
 
445
        "normal",
 
446
        "minor",
 
447
        "trivial",
 
448
        "enhancement"
 
449
);
 
450
');
 
451
 
 
452
 
 
453
 
 
454
LocalVar('priorities', '
 
455
#
 
456
# Which priorities do you want to assign to bugs and feature-request?
 
457
#
 
458
@priorities = (
 
459
        "P1",
 
460
        "P2",
 
461
        "P3",
 
462
        "P4",
 
463
        "P5"
 
464
);
 
465
');
 
466
 
 
467
 
 
468
 
 
469
LocalVar('opsys', '
 
470
#
 
471
# What operatings systems may your products run on?
 
472
#
 
473
@opsys = (
 
474
        "All",
 
475
        "Windows 3.1",
 
476
        "Windows 95",
 
477
        "Windows 98",
 
478
        "Windows ME",  # Millenium Edition (upgrade of 98)
 
479
        "Windows 2000",
 
480
        "Windows NT",
 
481
        "Windows XP",
 
482
        "Mac System 7",
 
483
        "Mac System 7.5",
 
484
        "Mac System 7.6.1",
 
485
        "Mac System 8.0",
 
486
        "Mac System 8.5",
 
487
        "Mac System 8.6",
 
488
        "Mac System 9.x",
 
489
        "MacOS X",
 
490
        "Linux",
 
491
        "BSDI",
 
492
        "FreeBSD",
 
493
        "NetBSD",
 
494
        "OpenBSD",
 
495
        "AIX",
 
496
        "BeOS",
 
497
        "HP-UX",
 
498
        "IRIX",
 
499
        "Neutrino",
 
500
        "OpenVMS",
 
501
        "OS/2",
 
502
        "OSF/1",
 
503
        "Solaris",
 
504
        "SunOS",
 
505
        "other"
 
506
);
 
507
');
 
508
 
 
509
 
 
510
 
 
511
LocalVar('platforms', '
 
512
#
 
513
# What hardware platforms may your products run on?
 
514
#
 
515
@platforms = (
 
516
        "All",
 
517
        "DEC",
 
518
        "HP",
 
519
        "Macintosh",
 
520
        "PC",
 
521
        "SGI",
 
522
        "Sun",
 
523
        "Other"
 
524
);
 
525
');
 
526
 
 
527
 
 
528
 
 
529
 
 
530
LocalVar('contenttypes', '
 
531
#
 
532
# The types of content that template files can generate, indexed by file extension.
 
533
#
 
534
$contenttypes = {
 
535
  "html" => "text/html" , 
 
536
   "rdf" => "application/xml" , 
 
537
   "xml" => "text/xml" , 
 
538
    "js" => "application/x-javascript" , 
 
539
};
 
540
');
 
541
 
 
542
 
 
543
 
 
544
 
 
545
if ($newstuff ne "") {
 
546
    print "\nThis version of Bugzilla contains some variables that you may want\n",
 
547
          "to change and adapt to your local settings. Please edit the file\n",
 
548
          "'localconfig' and rerun checksetup.pl\n\n",
 
549
          "The following variables are new to localconfig since you last ran\n",
 
550
          "checksetup.pl:  $newstuff\n\n";
 
551
    exit;
 
552
}
 
553
 
 
554
# 2000-Dec-18 - justdave@syndicomm.com - see Bug 52921
 
555
# This is a hack to read in the values defined in localconfig without getting
 
556
# them predeclared at compile time if they're missing from localconfig.
 
557
# Ideas swiped from pp. 281-282, O'Reilly's "Programming Perl 2nd Edition"
 
558
# Note that we won't need to do this in globals.pl because globals.pl couldn't
 
559
# care less whether they were defined ahead of time or not. 
 
560
my $my_db_check = ${*{$main::{'db_check'}}{SCALAR}};
 
561
my $my_db_host = ${*{$main::{'db_host'}}{SCALAR}};
 
562
my $my_db_port = ${*{$main::{'db_port'}}{SCALAR}};
 
563
my $my_db_name = ${*{$main::{'db_name'}}{SCALAR}};
 
564
my $my_db_user = ${*{$main::{'db_user'}}{SCALAR}};
 
565
my $my_db_pass = ${*{$main::{'db_pass'}}{SCALAR}};
 
566
my $my_index_html = ${*{$main::{'index_html'}}{SCALAR}};
 
567
my $my_create_htaccess = ${*{$main::{'create_htaccess'}}{SCALAR}};
 
568
my $my_webservergroup = ${*{$main::{'webservergroup'}}{SCALAR}};
 
569
my @my_severities = @{*{$main::{'severities'}}{ARRAY}};
 
570
my @my_priorities = @{*{$main::{'priorities'}}{ARRAY}};
 
571
my @my_platforms = @{*{$main::{'platforms'}}{ARRAY}};
 
572
my @my_opsys = @{*{$main::{'opsys'}}{ARRAY}};
 
573
 
 
574
if ($my_webservergroup) {
 
575
    if ($< != 0) { # zach: if not root, yell at them, bug 87398 
 
576
        print <<EOF;
 
577
 
 
578
Warning: you have entered a value for the "webservergroup" parameter
 
579
in localconfig, but you are not running this script as root.
 
580
This can cause permissions problems and decreased security.  If you
 
581
experience problems running Bugzilla scripts, log in as root and re-run
 
582
this script, or remove the value of the "webservergroup" parameter.
 
583
Note that any warnings about "uninitialized values" that you may
 
584
see below are caused by this.
 
585
 
 
586
EOF
 
587
    }
 
588
} else {
 
589
    # Theres no webservergroup, this is very very very very bad.
 
590
    # However, if we're being run on windows, then this option doesn't
 
591
    # really make sense. Doesn't make it any more secure either, though,
 
592
    # but don't print the message, since they can't do anything about it.
 
593
    if ($^O !~ /MSWin32/i) {
 
594
        print <<EOF;
 
595
 
 
596
********************************************************************************
 
597
WARNING! You have not entered a value for the "webservergroup" parameter
 
598
in localconfig. This means that certain files and directories which need
 
599
to be editable by both you and the webserver must be world writable, and
 
600
other files (including the localconfig file which stores your database
 
601
password) must be world readable. This means that _anyone_ who can obtain
 
602
local access to this machine can do whatever they want to your Bugzilla
 
603
installation, and is probably also able to run arbitrary Perl code as the
 
604
user that the webserver runs as.
 
605
 
 
606
You really, really, really need to change this setting.
 
607
********************************************************************************
 
608
 
 
609
EOF
 
610
    }
 
611
}
 
612
 
 
613
###########################################################################
 
614
# Global Utility Library
 
615
###########################################################################
 
616
 
 
617
# globals.pl clears the PATH, but File::Find uses Cwd::cwd() instead of
 
618
# Cwd::getcwd(), which we need to do because `pwd` isn't in the path - see
 
619
# http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2001-09/msg00115.html
 
620
# As a workaround, since we only use File::Find in checksetup, which doesn't
 
621
# run in taint mode anyway, preserve the path...
 
622
my $origPath = $::ENV{'PATH'};
 
623
 
 
624
# Use the Bugzilla utility library for various functions.  We do this
 
625
# here rather than at the top of the file so globals.pl doesn't define
 
626
# localconfig variables for us before we get a chance to check for
 
627
# their existence and create them if they don't exist.  Also, globals.pl
 
628
# removes $ENV{'path'}, which we need in order to run `which mysql` above.
 
629
require "globals.pl";
 
630
 
 
631
# ...and restore it. This doesn't change tainting, so this will still cause
 
632
# errors if this script ever does run with -T.
 
633
$::ENV{'PATH'} = $origPath;
 
634
 
 
635
###########################################################################
 
636
# Check data directory
 
637
###########################################################################
 
638
 
 
639
#
 
640
# Create initial --DATA-- directory and make the initial empty files there:
 
641
#
 
642
 
 
643
# The |require "globals.pl"| above ends up creating a template object with
 
644
# a COMPILE_DIR of 'data'. This means that TT creates the directory for us,
 
645
# so this code wouldn't run if we just checked for the existance of the
 
646
# directory. Instead, check for the existance of 'data/nomail', which is
 
647
# created in this block
 
648
unless (-d 'data' && -e 'data/nomail') {
 
649
    print "Creating data directory ...\n";
 
650
    # permissions for non-webservergroup are fixed later on
 
651
    mkdir 'data', 0770;
 
652
    mkdir 'data/mimedump-tmp', 01777;
 
653
    open FILE, '>>data/comments'; close FILE;
 
654
    open FILE, '>>data/nomail'; close FILE;
 
655
    open FILE, '>>data/mail'; close FILE;
 
656
}
 
657
 
 
658
# 2000-12-14 New graphing system requires a directory to put the graphs in
 
659
# This code copied from what happens for the 'data' dir above.
 
660
# If the graphs dir is not present, we assume that they have been using
 
661
# a Bugzilla with the old data format, and so upgrade their data files.
 
662
unless (-d 'graphs') {
 
663
    print "Creating graphs directory...\n";
 
664
    # permissions for non-webservergroup are fixed later on
 
665
    mkdir 'graphs', 0770; 
 
666
    # Upgrade data format
 
667
    foreach my $in_file (glob("data/mining/*"))
 
668
    {
 
669
        # Don't try and upgrade image or db files!
 
670
        if (($in_file =~ /\.gif$/i) || 
 
671
            ($in_file =~ /\.png$/i) ||
 
672
            ($in_file =~ /\.db$/i) ||
 
673
            ($in_file =~ /\.orig$/i)) {
 
674
            next;
 
675
        }
 
676
 
 
677
        rename("$in_file", "$in_file.orig") or next;        
 
678
        open(IN, "$in_file.orig") or next;
 
679
        open(OUT, ">$in_file") or next;
 
680
        
 
681
        # Fields in the header
 
682
        my @declared_fields = ();
 
683
 
 
684
        # Fields we changed to half way through by mistake
 
685
        # This list comes from an old version of collectstats.pl
 
686
        # This part is only for people who ran later versions of 2.11 (devel)
 
687
        my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED 
 
688
                                     RESOLVED VERIFIED CLOSED);
 
689
 
 
690
        # Fields we actually want (matches the current collectstats.pl)                             
 
691
        my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
 
692
                            VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
 
693
                            DUPLICATE WORKSFORME MOVED);
 
694
 
 
695
        while (<IN>) {
 
696
            if (/^# fields?: (.*)\s$/) {
 
697
                @declared_fields = map uc, (split /\||\r/, $1);
 
698
                print OUT "# fields: ", join('|', @out_fields), "\n";
 
699
            }
 
700
            elsif (/^(\d+\|.*)/) {
 
701
                my @data = split /\||\r/, $1;
 
702
                my %data = ();
 
703
                if (@data == @declared_fields) {
 
704
                    # old format
 
705
                    for my $i (0 .. $#declared_fields) {
 
706
                        $data{$declared_fields[$i]} = $data[$i];
 
707
                    }
 
708
                }
 
709
                elsif (@data == @intermediate_fields) {
 
710
                    # Must have changed over at this point 
 
711
                    for my $i (0 .. $#intermediate_fields) {
 
712
                        $data{$intermediate_fields[$i]} = $data[$i];
 
713
                    }
 
714
                }
 
715
                elsif (@data == @out_fields) {
 
716
                    # This line's fine - it has the right number of entries 
 
717
                    for my $i (0 .. $#out_fields) {
 
718
                        $data{$out_fields[$i]} = $data[$i];
 
719
                    }
 
720
                }
 
721
                else {
 
722
                    print "Oh dear, input line $. of $in_file had " . scalar(@data) . " fields\n";
 
723
                    print "This was unexpected. You may want to check your data files.\n";
 
724
                }
 
725
 
 
726
                print OUT join('|', map { 
 
727
                              defined ($data{$_}) ? ($data{$_}) : "" 
 
728
                                                          } @out_fields), "\n";
 
729
            }
 
730
            else {
 
731
                print OUT;
 
732
            }
 
733
        }
 
734
 
 
735
        close(IN);
 
736
        close(OUT);
 
737
    }
 
738
}
 
739
 
 
740
unless (-d 'data/mining') {
 
741
    mkdir 'data/mining', 0700;
 
742
}
 
743
 
 
744
unless (-d 'data/webdot') {
 
745
    # perms/ownership are fixed up later
 
746
    mkdir 'data/webdot', 0700;
 
747
}
 
748
 
 
749
if ($my_create_htaccess) {
 
750
  my $fileperm = 0644;
 
751
  my $dirperm = 01777;
 
752
  if ($my_webservergroup) {
 
753
    $fileperm = 0640;
 
754
    $dirperm = 0770;
 
755
  }
 
756
  if (!-e ".htaccess") {
 
757
    print "Creating .htaccess...\n";
 
758
    open HTACCESS, ">.htaccess";
 
759
    print HTACCESS <<'END';
 
760
# don't allow people to retrieve non-cgi executable files or our private data
 
761
<FilesMatch ^(.*\.pl|.*localconfig.*|processmail|syncshadowdb|runtests.sh)$>
 
762
  deny from all
 
763
</FilesMatch>
 
764
<FilesMatch ^(localconfig.js|localconfig.rdf)$>
 
765
  allow from all
 
766
</FilesMatch>
 
767
END
 
768
    close HTACCESS;
 
769
    chmod $fileperm, ".htaccess";
 
770
  } else {
 
771
    # 2002-12-21 Bug 186383
 
772
    open HTACCESS, ".htaccess";
 
773
    my $oldaccess = "";
 
774
    while (<HTACCESS>) {
 
775
      $oldaccess .= $_;
 
776
    }
 
777
    close HTACCESS;
 
778
    if ($oldaccess =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
 
779
      print "Repairing .htaccess...\n";
 
780
      open HTACCESS, ">.htaccess";
 
781
      print HTACCESS $oldaccess;
 
782
      print HTACCESS <<'END';
 
783
<FilesMatch ^(localconfig.js|localconfig.rdf)$>
 
784
  allow from all
 
785
</FilesMatch>
 
786
END
 
787
      close HTACCESS;
 
788
    }
 
789
 
 
790
  }
 
791
  if (!-e "data/.htaccess") {
 
792
    print "Creating data/.htaccess...\n";
 
793
    open HTACCESS, ">data/.htaccess";
 
794
    print HTACCESS <<'END';
 
795
# nothing in this directory is retrievable unless overriden by an .htaccess
 
796
# in a subdirectory
 
797
deny from all
 
798
END
 
799
    close HTACCESS;
 
800
    chmod $fileperm, "data/.htaccess";
 
801
  }
 
802
  if (!-e "template/.htaccess") {
 
803
    print "Creating template/.htaccess...\n";
 
804
    open HTACCESS, ">template/.htaccess";
 
805
    print HTACCESS <<'END';
 
806
# nothing in this directory is retrievable unless overriden by an .htaccess
 
807
# in a subdirectory
 
808
deny from all
 
809
END
 
810
    close HTACCESS;
 
811
    chmod $fileperm, "template/.htaccess";
 
812
  }
 
813
  if (!-e "data/webdot/.htaccess") {
 
814
    print "Creating data/webdot/.htaccess...\n";
 
815
    open HTACCESS, ">data/webdot/.htaccess";
 
816
    print HTACCESS <<'END';
 
817
# Restrict access to .dot files to the public webdot server at research.att.com 
 
818
# if research.att.com ever changed their IP, or if you use a different
 
819
# webdot server, you'll need to edit this
 
820
<FilesMatch \.dot$>
 
821
  Allow from 192.20.225.10
 
822
  Deny from all
 
823
</FilesMatch>
 
824
 
 
825
# Allow access to .png files created by a local copy of 'dot'
 
826
<FilesMatch \.png$>
 
827
  Allow from all
 
828
</FilesMatch>
 
829
 
 
830
# And no directory listings, either.
 
831
Deny from all
 
832
END
 
833
    close HTACCESS;
 
834
    chmod $fileperm, "data/webdot/.htaccess";
 
835
  }
 
836
 
 
837
}
 
838
 
 
839
if ($my_index_html) {
 
840
    if (!-e "index.html") {
 
841
        print "Creating index.html...\n";
 
842
        open HTML, ">index.html";
 
843
        print HTML <<'END';
 
844
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 
845
<HTML>
 
846
<HEAD>
 
847
<META HTTP-EQUIV="REFRESH" CONTENT="0; URL=index.cgi">
 
848
</HEAD>
 
849
<BODY>
 
850
<H1>I think you are looking for <a href="index.cgi">index.cgi</a></H1>
 
851
</BODY>
 
852
</HTML>
 
853
END
 
854
        close HTML;
 
855
    }
 
856
    else {
 
857
        open HTML, "index.html";
 
858
        if (! grep /index\.cgi/, <HTML>) {
 
859
            print "\n\n";
 
860
            print "*** It appears that you still have an old index.html hanging\n";
 
861
            print "    around.  The contents of this file should be moved into a\n";
 
862
            print "    template and placed in the 'template/en/custom' directory.\n\n";
 
863
        }
 
864
        close HTML;
 
865
    }
 
866
}
 
867
 
 
868
{
 
869
    eval("use Date::Parse");
 
870
    # Templates will be recompiled if the source changes, but not if the
 
871
    # settings in globals.pl change, so we need to be able to force a rebuild
 
872
    # if that happens
 
873
 
 
874
    # The last time the global template params were changed. Keep in UTC,
 
875
    # YYYY-MM-DD
 
876
    my $lastTemplateParamChange = str2time("2002-04-27", "UTC");
 
877
    if (-e 'data/template') {
 
878
        unless (-d 'data/template' && -e 'data/template/.lastRebuild' &&
 
879
                (stat('data/template/.lastRebuild'))[9] >= $lastTemplateParamChange) {
 
880
            print "Removing existing compiled templates ...\n";
 
881
 
 
882
            # If File::Path::rmtree reported errors, then I'd use that
 
883
            use File::Find;
 
884
            sub remove {
 
885
                return if $_ eq ".";
 
886
                if (-d $_) {
 
887
                    rmdir $_ || die "Couldn't rmdir $_: $!\n";
 
888
                } else {
 
889
                    unlink $_ || die "Couldn't unlink $_: $!\n";
 
890
                }
 
891
            }
 
892
            finddepth(\&remove, 'data/template');
 
893
        }
 
894
    }
 
895
 
 
896
    # Precompile stuff. This speeds up initial access (so the template isn't
 
897
    # compiled multiple times simulataneously by different servers), and helps
 
898
    # to get the permissions right.
 
899
    eval("use Template");
 
900
    my $redir = ($^O =~ /MSWin32/i) ? "NUL" : "/dev/null";
 
901
    my $template = Template->new(
 
902
      {
 
903
        # Output to /dev/null here
 
904
        OUTPUT => $redir,
 
905
 
 
906
        # Colon-separated list of directories containing templates.
 
907
        INCLUDE_PATH => "template/en/custom:template/en/default",
 
908
 
 
909
        PRE_CHOMP => 1 ,
 
910
        TRIM => 1 ,
 
911
 
 
912
        COMPILE_DIR => 'data/', # becomes data/template/en/{custom,default}
 
913
 
 
914
        # These don't actually need to do anything here, just exist
 
915
        FILTERS =>
 
916
        {
 
917
         strike => sub { return $_; } ,
 
918
         js => sub { return $_; },
 
919
         html => sub { return $_; },
 
920
         html_linebreak => sub { return $_; },
 
921
         url_quote => sub { return $_; },
 
922
        },
 
923
      }) || die ("Could not create Template: " . Template->error() . "\n");
 
924
 
 
925
    sub compile {
 
926
        # no_chdir doesn't work on perl 5.005
 
927
 
 
928
        my $origDir = $File::Find::dir;
 
929
        my $name = $File::Find::name;
 
930
 
 
931
        return if (-d $name);
 
932
        return if ($name =~ /\/CVS\//);
 
933
        return if ($name !~ /\.tmpl$/);
 
934
        $name =~ s!template/en/default/!!; # trim the bit we don't pass to TT
 
935
 
 
936
        chdir($::baseDir);
 
937
 
 
938
        $template->process($name, {})
 
939
          || die "Could not compile $name:" . $template->error() . "\n";
 
940
 
 
941
        chdir($origDir);
 
942
    }
 
943
 
 
944
    {
 
945
        print "Precompiling templates ...\n";
 
946
 
 
947
        use File::Find;
 
948
 
 
949
        use Cwd;
 
950
 
 
951
        $::baseDir = getcwd();
 
952
 
 
953
        # Don't hang on templates which use the CGI library
 
954
        eval("use CGI qw(-no_debug)");
 
955
 
 
956
        # Disable warnings which come from running the compiled templates
 
957
        # This way is OK, because they're all runtime warnings.
 
958
        # The reason we get these warnings here is that none of the required
 
959
        # vars will be present.
 
960
        local ($^W) = 0;
 
961
 
 
962
        # Traverse the default hierachy. Custom templates will be picked up
 
963
        # via the INCLUDE_PATH, but we know that bugzilla will only be
 
964
        # calling stuff which exists in en/default
 
965
        # FIXME - if we start doing dynamic INCLUDE_PATH we may have to
 
966
        # recurse all of template/, changing the INCLUDE_PATH each time
 
967
 
 
968
        find(\&compile, "template/en/default");
 
969
    }
 
970
 
 
971
    # update the time on the stamp file
 
972
    open FILE, '>data/template/.lastRebuild'; close FILE;
 
973
    utime $lastTemplateParamChange, $lastTemplateParamChange, ('data/template/.lastRebuild');
 
974
}
 
975
 
 
976
# Just to be sure ...
 
977
unlink "data/versioncache";
 
978
 
 
979
# Remove parameters from the data/params file that no longer exist in Bugzilla.
 
980
if (-e "data/params") {
 
981
    require "data/params";
 
982
    require "defparams.pl";
 
983
    use vars @::param_list;
 
984
    my @oldparams;
 
985
    
 
986
    open(PARAMFILE, ">>old-params.txt") 
 
987
      || die "$0: Can't open old-params.txt for writing: $!\n";
 
988
      
 
989
    foreach my $item (keys %::param) {
 
990
        if (!grep($_ eq $item, @::param_list) && $item ne "version") {
 
991
            push (@oldparams, $item);
 
992
            print PARAMFILE "\n\n$item:\n$::param{$item}\n";
 
993
                
 
994
            delete $::param{$item};
 
995
        }
 
996
    }
 
997
    
 
998
    if (@oldparams) {
 
999
        print "The following parameters are no longer used in Bugzilla, " .
 
1000
              "and so have been\nremoved from your parameters file and " .
 
1001
              "appended to old-params.txt:\n";
 
1002
        print join(", ", @oldparams) . "\n\n";               
 
1003
    }
 
1004
    
 
1005
    close PARAMFILE;
 
1006
    WriteParams();
 
1007
}
 
1008
 
 
1009
 
 
1010
###########################################################################
 
1011
# Set proper rights
 
1012
###########################################################################
 
1013
 
 
1014
#
 
1015
# Here we use --CHMOD-- and friends to set the file permissions
 
1016
#
 
1017
# The rationale is that the web server generally runs as nobody and so the cgi
 
1018
# scripts should not be writable for nobody, otherwise someone may be possible
 
1019
# to change the cgi's when exploiting some security flaw somewhere (not
 
1020
# necessarily in Bugzilla!)
 
1021
#
 
1022
# Also, some *.pl files are executable, some are not.
 
1023
#
 
1024
# +++ Can anybody tell me what a Windows Perl would do with this code?
 
1025
#
 
1026
# Changes 03/14/00 by SML
 
1027
#
 
1028
# This abstracts out what files are executable and what ones are not.  It makes
 
1029
# for slightly neater code and lets us do things like determine exactly which
 
1030
# files are executable and which ones are not.
 
1031
#
 
1032
# Not all directories have permissions changed on them.  i.e., changing ./CVS
 
1033
# to be 0640 is bad.
 
1034
#
 
1035
# Fixed bug in chmod invokation.  chmod (at least on my linux box running perl
 
1036
# 5.005 needs a valid first argument, not 0.
 
1037
#
 
1038
# (end changes, 03/14/00 by SML)
 
1039
#
 
1040
# Changes 15/06/01 kiko@async.com.br
 
1041
 
1042
# Fix file permissions for non-webservergroup installations (see
 
1043
# http://bugzilla.mozilla.org/show_bug.cgi?id=71555). I'm setting things
 
1044
# by default to world readable/executable for all files, and
 
1045
# world-writeable (with sticky on) to data and graphs.
 
1046
#
 
1047
 
 
1048
# These are the files which need to be marked executable
 
1049
my @executable_files = ('processmail', 'whineatnews.pl', 'collectstats.pl',
 
1050
   'checksetup.pl', 'syncshadowdb', 'importxml.pl', 'runtests.sh');
 
1051
 
 
1052
# tell me if a file is executable.  All CGI files and those in @executable_files
 
1053
# are executable
 
1054
sub isExecutableFile {
 
1055
  my ($file) = @_;
 
1056
  if ($file =~ /\.cgi/) {
 
1057
    return 1;
 
1058
  }
 
1059
 
 
1060
  my $exec_file;
 
1061
  foreach $exec_file (@executable_files) {
 
1062
    if ($file eq $exec_file) {
 
1063
      return 1;
 
1064
    }
 
1065
  }
 
1066
  return undef;
 
1067
}
 
1068
 
 
1069
# fix file (or files - wildcards ok) permissions 
 
1070
sub fixPerms {
 
1071
    my ($file_pattern, $owner, $group, $umask, $do_dirs) = @_;
 
1072
    my @files = glob($file_pattern);
 
1073
    my $execperm = 0777 & ~ $umask;
 
1074
    my $normperm = 0666 & ~ $umask;
 
1075
    foreach my $file (@files) {
 
1076
        next if (!-e $file);
 
1077
        # do not change permissions on directories here unless $do_dirs is set
 
1078
        if (!(-d $file)) {
 
1079
            chown $owner, $group, $file;
 
1080
            # check if the file is executable.
 
1081
            if (isExecutableFile($file)) {
 
1082
                #printf ("Changing $file to %o\n", $execperm);
 
1083
                chmod $execperm, $file;
 
1084
            } else {
 
1085
                #printf ("Changing $file to %o\n", $normperm);
 
1086
                chmod $normperm, $file;
 
1087
            }
 
1088
        }
 
1089
        elsif ($do_dirs) {
 
1090
            chown $owner, $group, $file;
 
1091
            if ($file =~ /CVS$/) {
 
1092
                chmod 0700, $file;
 
1093
            }
 
1094
            else {
 
1095
                #printf ("Changing $file to %o\n", $execperm);
 
1096
                chmod $execperm, $file;
 
1097
                fixPerms("$file/.htaccess", $owner, $group, $umask, $do_dirs);
 
1098
                fixPerms("$file/*", $owner, $group, $umask, $do_dirs); # do the contents of the directory
 
1099
            }
 
1100
        }
 
1101
    }
 
1102
}
 
1103
 
 
1104
if ($my_webservergroup) {
 
1105
    # Funny! getgrname returns the GID if fed with NAME ...
 
1106
    my $webservergid = getgrnam($my_webservergroup);
 
1107
    # chown needs to be called with a valid uid, not 0.  $< returns the
 
1108
    # caller's uid.  Maybe there should be a $bugzillauid, and call with that
 
1109
    # userid.
 
1110
    fixPerms('.htaccess', $<, $webservergid, 027); # glob('*') doesn't catch dotfiles
 
1111
    fixPerms('data/.htaccess', $<, $webservergid, 027);
 
1112
    fixPerms('data/duplicates', $<, $webservergid, 027, 1);
 
1113
    fixPerms('data/mining', $<, $webservergid, 027, 1);
 
1114
    fixPerms('data/template', $<, $webservergid, 007, 1); # webserver will write to these
 
1115
    fixPerms('data/webdot', $<, $webservergid, 007, 1);
 
1116
    fixPerms('data/webdot/.htaccess', $<, $webservergid, 027);
 
1117
    fixPerms('data/params', $<, $webservergid, 017);
 
1118
    fixPerms('data/comments', $<, $webservergid, 017);
 
1119
    fixPerms('*', $<, $webservergid, 027);
 
1120
    fixPerms('template', $<, $webservergid, 027, 1);
 
1121
    fixPerms('css', $<, $webservergid, 027, 1);
 
1122
    chmod 0644, 'globals.pl';
 
1123
    chmod 0644, 'RelationSet.pm';
 
1124
 
 
1125
    # Don't use fixPerms here, because it won't change perms on the directory
 
1126
    # unless its using recursion
 
1127
    chown $<, $webservergid, 'data';
 
1128
    chmod 0771, 'data';
 
1129
    chown $<, $webservergid, 'graphs';
 
1130
    chmod 0770, 'graphs';
 
1131
} else {
 
1132
    # get current gid from $( list
 
1133
    my $gid = (split " ", $()[0];
 
1134
    fixPerms('.htaccess', $<, $gid, 022); # glob('*') doesn't catch dotfiles
 
1135
    fixPerms('data/.htaccess', $<, $gid, 022);
 
1136
    fixPerms('data/duplicates', $<, $gid, 022, 1);
 
1137
    fixPerms('data/mining', $<, $gid, 022, 1);
 
1138
    fixPerms('data/template', $<, $gid, 000, 1); # webserver will write to these
 
1139
    fixPerms('data/webdot', $<, $gid, 000, 1);
 
1140
    chmod 01777, 'data/webdot';
 
1141
    fixPerms('data/webdot/.htaccess', $<, $gid, 022);
 
1142
    fixPerms('data/params', $<, $gid, 011);
 
1143
    fixPerms('*', $<, $gid, 022);
 
1144
    fixPerms('template', $<, $gid, 022, 1);
 
1145
    fixPerms('css', $<, $gid, 022, 1);
 
1146
 
 
1147
    # Don't use fixPerms here, because it won't change perms on the directory
 
1148
    # unless its using recursion
 
1149
    chown $<, $gid, 'data';
 
1150
    chmod 0777, 'data';
 
1151
    chown $<, $gid, 'graphs';
 
1152
    chmod 01777, 'graphs';
 
1153
}
 
1154
 
 
1155
 
 
1156
###########################################################################
 
1157
# Check MySQL setup
 
1158
###########################################################################
 
1159
 
 
1160
#
 
1161
# Check if we have access to --MYSQL--
 
1162
#
 
1163
 
 
1164
# This settings are not yet changeable, because other code depends on
 
1165
# the fact that we use MySQL and not, say, PostgreSQL.
 
1166
 
 
1167
my $db_base = 'mysql';
 
1168
 
 
1169
# No need to "use" this here.  It should already be loaded from the
 
1170
# version-checking routines above, and this file won't even compile if
 
1171
# DBI isn't installed so the user gets nasty errors instead of our
 
1172
# pretty one saying they need to install it. -- justdave@syndicomm.com
 
1173
#use DBI;
 
1174
 
 
1175
# get a handle to the low-level DBD driver
 
1176
my $drh = DBI->install_driver($db_base)
 
1177
    or die "Can't connect to the $db_base. Is the database installed and up and running?\n";
 
1178
 
 
1179
if ($my_db_check) {
 
1180
    # Do we have the database itself?
 
1181
 
 
1182
    my $sql_want = "3.22.5";  # minimum version of MySQL
 
1183
 
 
1184
# original DSN line was:
 
1185
#    my $dsn = "DBI:$db_base:$my_db_name;$my_db_host;$my_db_port";
 
1186
# removed the $db_name because we don't know it exists yet, and this will fail
 
1187
# if we request it here and it doesn't. - justdave@syndicomm.com 2000/09/16
 
1188
    my $dsn = "DBI:$db_base:;$my_db_host;$my_db_port";
 
1189
    my $dbh = DBI->connect($dsn, $my_db_user, $my_db_pass)
 
1190
      or die "Can't connect to the $db_base database. Is the database " .
 
1191
        "installed and\nup and running?  Do you have the correct username " .
 
1192
        "and password selected in\nlocalconfig?\n\n";
 
1193
    printf("Checking for %15s %-9s ", "MySQL Server", "(v$sql_want)");
 
1194
    my $qh = $dbh->prepare("SELECT VERSION()");
 
1195
    $qh->execute;
 
1196
    my ($sql_vers) = $qh->fetchrow_array;
 
1197
    $qh->finish;
 
1198
 
 
1199
    # Check what version of MySQL is installed and let the user know
 
1200
    # if the version is too old to be used with Bugzilla.
 
1201
    if ( vers_cmp($sql_vers,$sql_want) > -1 ) {
 
1202
        print "ok: found v$sql_vers\n";
 
1203
    } else {
 
1204
        die "Your MySQL server v$sql_vers is too old./n" . 
 
1205
            "   Bugzilla requires version $sql_want or later of MySQL.\n" . 
 
1206
            "   Please visit http://www.mysql.com/ and download a newer version.\n";
 
1207
    }
 
1208
 
 
1209
    my @databases = $dbh->func('_ListDBs');
 
1210
    unless (grep /^$my_db_name$/, @databases) {
 
1211
       print "Creating database $my_db_name ...\n";
 
1212
       $drh->func('createdb', $my_db_name, "$my_db_host:$my_db_port", $my_db_user, $my_db_pass, 'admin')
 
1213
            or die <<"EOF"
 
1214
 
 
1215
The '$my_db_name' database is not accessible. This might have several reasons:
 
1216
 
 
1217
* MySQL is not running.
 
1218
* MySQL is running, but the rights are not set correct. Go and read the
 
1219
  Bugzilla Guide in the doc directory and all parts of the MySQL
 
1220
  documentation.
 
1221
* There is an subtle problem with Perl, DBI, DBD::mysql and MySQL. Make
 
1222
  sure all settings in 'localconfig' are correct. If all else fails, set
 
1223
  '\$db_check' to zero.\n
 
1224
EOF
 
1225
    }
 
1226
    $dbh->disconnect if $dbh;
 
1227
}
 
1228
 
 
1229
# now get a handle to the database:
 
1230
my $connectstring = "dbi:$db_base:$my_db_name:host=$my_db_host:port=$my_db_port";
 
1231
my $dbh = DBI->connect($connectstring, $my_db_user, $my_db_pass)
 
1232
    or die "Can't connect to the table '$connectstring'.\n",
 
1233
           "Have you read the Bugzilla Guide in the doc directory?  Have you read the doc of '$db_base'?\n";
 
1234
 
 
1235
END { $dbh->disconnect if $dbh }
 
1236
 
 
1237
 
 
1238
###########################################################################
 
1239
# Check GraphViz setup
 
1240
###########################################################################
 
1241
 
 
1242
#
 
1243
# If we are using a local 'dot' binary, verify the specified binary exists
 
1244
# and that the generated images are accessible.
 
1245
#
 
1246
 
 
1247
if(-e "data/params") {
 
1248
  require "data/params";
 
1249
  if( $::param{'webdotbase'} && $::param{'webdotbase'} !~ /^https?:/ ) {
 
1250
    printf("Checking for %15s %-9s ", "GraphViz", "(any)");
 
1251
    if(-x $::param{'webdotbase'}) {
 
1252
      print "ok: found\n";
 
1253
    } else {
 
1254
      print "not a valid executable: $::param{'webdotbase'}\n";
 
1255
    }
 
1256
 
 
1257
    # Check .htaccess allows access to generated images
 
1258
    if(-e "data/webdot/.htaccess") {
 
1259
      open HTACCESS, "data/webdot/.htaccess";
 
1260
      if(! grep(/png/,<HTACCESS>)) {
 
1261
        print "Dependency graph images are not accessible.\n";
 
1262
        print "Delete data/webdot/.htaccess and re-run checksetup.pl to rectify.\n";
 
1263
      }
 
1264
      close HTACCESS;
 
1265
    }
 
1266
  }
 
1267
}
 
1268
 
 
1269
print "\n";
 
1270
 
 
1271
 
 
1272
###########################################################################
 
1273
# Table definitions
 
1274
###########################################################################
 
1275
 
 
1276
#
 
1277
# The following hash stores all --TABLE-- definitions. This will be used
 
1278
# to automatically create those tables that don't exist. The code is
 
1279
# safer than the make*.sh shell scripts used to be, because they won't
 
1280
# delete existing tables.
 
1281
#
 
1282
# If you want intentionally do this, yon can always drop a table and re-run
 
1283
# checksetup, e.g. like this:
 
1284
#
 
1285
#    $ mysql bugs
 
1286
#    mysql> drop table votes;
 
1287
#    mysql> exit;
 
1288
#    $ ./checksetup.pl
 
1289
#
 
1290
# If you change one of those field definitions, then also go below to the
 
1291
# next occurence of the string --TABLE-- (near the end of this file) to
 
1292
# add the code that updates older installations automatically.
 
1293
#
 
1294
 
 
1295
 
 
1296
my %table;
 
1297
 
 
1298
$table{bugs_activity} = 
 
1299
   'bug_id mediumint not null,
 
1300
    attach_id mediumint null,
 
1301
    who mediumint not null,
 
1302
    bug_when datetime not null,
 
1303
    fieldid mediumint not null,
 
1304
    added tinytext,
 
1305
    removed tinytext,
 
1306
 
 
1307
    index (bug_id),
 
1308
    index (bug_when),
 
1309
    index (fieldid)';
 
1310
 
 
1311
 
 
1312
$table{attachments} =
 
1313
   'attach_id mediumint not null auto_increment primary key,
 
1314
    bug_id mediumint not null,
 
1315
    creation_ts timestamp,
 
1316
    description mediumtext not null,
 
1317
    mimetype mediumtext not null,
 
1318
    ispatch tinyint,
 
1319
    filename mediumtext not null,
 
1320
    thedata longblob not null,
 
1321
    submitter_id mediumint not null,
 
1322
    isobsolete tinyint not null default 0, 
 
1323
 
 
1324
    index(bug_id),
 
1325
    index(creation_ts)';
 
1326
 
 
1327
# 2001-05-05 myk@mozilla.org: Tables to support attachment statuses.
 
1328
# "attachstatuses" stores one record for each status on each attachment.
 
1329
# "attachstatusdefs" defines the statuses that can be set on attachments.
 
1330
 
 
1331
$table{attachstatuses} =
 
1332
   '
 
1333
     attach_id    MEDIUMINT    NOT NULL , 
 
1334
     statusid     SMALLINT     NOT NULL , 
 
1335
     PRIMARY KEY(attach_id, statusid) 
 
1336
   ';
 
1337
 
 
1338
$table{attachstatusdefs} =
 
1339
   '
 
1340
     id           SMALLINT     NOT NULL  PRIMARY KEY , 
 
1341
     name         VARCHAR(50)  NOT NULL , 
 
1342
     description  MEDIUMTEXT   NULL , 
 
1343
     sortkey      SMALLINT     NOT NULL  DEFAULT 0 , 
 
1344
     product      VARCHAR(64)  NOT NULL 
 
1345
   ';
 
1346
 
 
1347
#
 
1348
# Apostrophe's are not supportied in the enum types.
 
1349
# See http://bugzilla.mozilla.org/show_bug.cgi?id=27309
 
1350
#
 
1351
$table{bugs} =
 
1352
   'bug_id mediumint not null auto_increment primary key,
 
1353
    groupset bigint not null,
 
1354
    assigned_to mediumint not null, # This is a comment.
 
1355
    bug_file_loc text,
 
1356
    bug_severity enum($my_severities) not null,
 
1357
    bug_status enum("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED", "RESOLVED", "VERIFIED", "CLOSED") not null,
 
1358
    creation_ts datetime not null,
 
1359
    delta_ts timestamp,
 
1360
    short_desc mediumtext,
 
1361
    op_sys enum($my_opsys) not null,
 
1362
    priority enum($my_priorities) not null,
 
1363
    product varchar(64) not null,
 
1364
    rep_platform enum($my_platforms),
 
1365
    reporter mediumint not null,
 
1366
    version varchar(64) not null,
 
1367
    component varchar(50) not null,
 
1368
    resolution enum("", "FIXED", "INVALID", "WONTFIX", "LATER", "REMIND", "DUPLICATE", "WORKSFORME", "MOVED") not null,
 
1369
    target_milestone varchar(20) not null default "---",
 
1370
    qa_contact mediumint not null,
 
1371
    status_whiteboard mediumtext not null,
 
1372
    votes mediumint not null,
 
1373
    keywords mediumtext not null, ' # Note: keywords field is only a cache;
 
1374
                                # the real data comes from the keywords table.
 
1375
    . '
 
1376
    lastdiffed datetime not null,
 
1377
    everconfirmed tinyint not null,
 
1378
    reporter_accessible tinyint not null default 1,
 
1379
    cclist_accessible tinyint not null default 1,
 
1380
 
 
1381
    index (assigned_to),
 
1382
    index (creation_ts),
 
1383
    index (delta_ts),
 
1384
    index (bug_severity),
 
1385
    index (bug_status),
 
1386
    index (op_sys),
 
1387
    index (priority),
 
1388
    index (product),
 
1389
    index (reporter),
 
1390
    index (version),
 
1391
    index (component),
 
1392
    index (resolution),
 
1393
    index (target_milestone),
 
1394
    index (qa_contact),
 
1395
    index (votes)';
 
1396
 
 
1397
 
 
1398
$table{cc} =
 
1399
   'bug_id mediumint not null,
 
1400
    who mediumint not null,
 
1401
 
 
1402
    index(who),
 
1403
    unique(bug_id,who)';
 
1404
 
 
1405
$table{watch} =
 
1406
   'watcher mediumint not null,
 
1407
    watched mediumint not null,
 
1408
 
 
1409
    index(watched),
 
1410
    unique(watcher,watched)';
 
1411
 
 
1412
 
 
1413
$table{longdescs} = 
 
1414
   'bug_id mediumint not null,
 
1415
    who mediumint not null,
 
1416
    bug_when datetime not null,
 
1417
    thetext mediumtext,
 
1418
 
 
1419
    index(bug_id),
 
1420
    index(who),
 
1421
    index(bug_when)';
 
1422
 
 
1423
 
 
1424
$table{components} =
 
1425
   'value tinytext,
 
1426
    program varchar(64),
 
1427
    initialowner mediumint not null,
 
1428
    initialqacontact mediumint not null,
 
1429
    description mediumtext not null';
 
1430
 
 
1431
 
 
1432
$table{dependencies} =
 
1433
   'blocked mediumint not null,
 
1434
    dependson mediumint not null,
 
1435
 
 
1436
    index(blocked),
 
1437
    index(dependson)';
 
1438
 
 
1439
 
 
1440
# Group bits must be a power of two. Groups are identified by a bit; sets of
 
1441
# groups are indicated by or-ing these values together.
 
1442
#
 
1443
# isbuggroup is nonzero if this is a group that controls access to a set
 
1444
# of bugs.  In otherword, the groupset field in the bugs table should only
 
1445
# have this group's bit set if isbuggroup is nonzero.
 
1446
#
 
1447
# User regexp is which email addresses are initially put into this group.
 
1448
# This is only used when an email account is created; otherwise, profiles
 
1449
# may be individually tweaked to add them in and out of groups.
 
1450
#
 
1451
# 2001-04-10 myk@mozilla.org:
 
1452
# isactive determines whether or not a group is active.  An inactive group
 
1453
# cannot have bugs added to it.  Deactivation is a much milder form of
 
1454
# deleting a group that allows users to continue to work on bugs in the group
 
1455
# without enabling them to extend the life of the group by adding bugs to it.
 
1456
# http://bugzilla.mozilla.org/show_bug.cgi?id=75482
 
1457
 
 
1458
$table{groups} =
 
1459
   'bit bigint not null,
 
1460
    name varchar(255) not null,
 
1461
    description text not null,
 
1462
    isbuggroup tinyint not null,
 
1463
    userregexp tinytext not null,
 
1464
    isactive tinyint not null default 1,
 
1465
 
 
1466
    unique(bit),
 
1467
    unique(name)';
 
1468
 
 
1469
$table{logincookies} =
 
1470
   'cookie mediumint not null auto_increment primary key,
 
1471
    userid mediumint not null,
 
1472
    ipaddr varchar(40) NOT NULL,
 
1473
    lastused timestamp,
 
1474
 
 
1475
    index(lastused)';
 
1476
 
 
1477
 
 
1478
$table{products} =
 
1479
   'product varchar(64),
 
1480
    description mediumtext,
 
1481
    milestoneurl tinytext not null,
 
1482
    disallownew tinyint not null,
 
1483
    votesperuser smallint not null,
 
1484
    maxvotesperbug smallint not null default 10000,
 
1485
    votestoconfirm smallint not null,
 
1486
    defaultmilestone varchar(20) not null default "---"
 
1487
';
 
1488
 
 
1489
 
 
1490
$table{profiles} =
 
1491
   'userid mediumint not null auto_increment primary key,
 
1492
    login_name varchar(255) not null,
 
1493
    cryptpassword varchar(34),
 
1494
    realname varchar(255),
 
1495
    groupset bigint not null,
 
1496
    disabledtext mediumtext not null,
 
1497
    mybugslink tinyint not null default 1,
 
1498
    blessgroupset bigint not null default 0,
 
1499
    emailflags mediumtext,
 
1500
 
 
1501
 
 
1502
    unique(login_name)';
 
1503
 
 
1504
 
 
1505
$table{profiles_activity} = 
 
1506
   'userid mediumint not null,
 
1507
    who mediumint not null,
 
1508
    profiles_when datetime not null,
 
1509
    fieldid mediumint not null,
 
1510
    oldvalue tinytext,
 
1511
    newvalue tinytext,
 
1512
 
 
1513
    index (userid),
 
1514
    index (profiles_when),
 
1515
    index (fieldid)';
 
1516
 
 
1517
 
 
1518
$table{namedqueries} =
 
1519
    'userid mediumint not null,
 
1520
     name varchar(64) not null,
 
1521
     watchfordiffs tinyint not null,
 
1522
     linkinfooter tinyint not null,
 
1523
     query mediumtext not null,
 
1524
 
 
1525
     unique(userid, name),
 
1526
     index(watchfordiffs)';
 
1527
 
 
1528
# This isn't quite cooked yet...
 
1529
#
 
1530
#  $table{diffprefs} =
 
1531
#     'userid mediumint not null,
 
1532
#      fieldid mediumint not null,
 
1533
#      mailhead tinyint not null,
 
1534
#      maildiffs tinyint not null,
 
1535
#
 
1536
#      index(userid)';
 
1537
 
 
1538
$table{fielddefs} =
 
1539
   'fieldid mediumint not null auto_increment primary key,
 
1540
    name varchar(64) not null,
 
1541
    description mediumtext not null,
 
1542
    mailhead tinyint not null default 0,
 
1543
    sortkey smallint not null,
 
1544
 
 
1545
    unique(name),
 
1546
    index(sortkey)';
 
1547
 
 
1548
$table{versions} =
 
1549
   'value tinytext,
 
1550
    program varchar(64) not null';
 
1551
 
 
1552
 
 
1553
$table{votes} =
 
1554
   'who mediumint not null,
 
1555
    bug_id mediumint not null,
 
1556
    count smallint not null,
 
1557
 
 
1558
    index(who),
 
1559
    index(bug_id)';
 
1560
 
 
1561
$table{keywords} =
 
1562
    'bug_id mediumint not null,
 
1563
     keywordid smallint not null,
 
1564
 
 
1565
     index(keywordid),
 
1566
     unique(bug_id,keywordid)';
 
1567
 
 
1568
$table{keyworddefs} =
 
1569
    'id smallint not null primary key,
 
1570
     name varchar(64) not null,
 
1571
     description mediumtext,
 
1572
 
 
1573
     unique(name)';
 
1574
 
 
1575
 
 
1576
$table{milestones} =
 
1577
    'value varchar(20) not null,
 
1578
     product varchar(64) not null,
 
1579
     sortkey smallint not null,
 
1580
     unique (product, value)';
 
1581
 
 
1582
$table{shadowlog} =
 
1583
    'id int not null auto_increment primary key,
 
1584
     ts timestamp,
 
1585
     reflected tinyint not null,
 
1586
     command mediumtext not null,
 
1587
     index(reflected)';
 
1588
 
 
1589
# GRM
 
1590
$table{duplicates} =
 
1591
    'dupe_of mediumint(9) not null,
 
1592
     dupe mediumint(9) not null primary key';
 
1593
 
 
1594
# 2001-06-21, myk@mozilla.org, bug 77473:
 
1595
# Stores the tokens users receive when they want to change their password 
 
1596
# or email address.  Tokens provide an extra measure of security for these changes.
 
1597
$table{tokens} =
 
1598
    'userid mediumint not null , 
 
1599
     issuedate datetime not null , 
 
1600
     token varchar(16) not null primary key ,  
 
1601
     tokentype varchar(8) not null , 
 
1602
     eventdata tinytext null , 
 
1603
 
 
1604
     index(userid)';
 
1605
 
 
1606
 
 
1607
 
 
1608
###########################################################################
 
1609
# Create tables
 
1610
###########################################################################
 
1611
 
 
1612
# Get a list of the existing tables (if any) in the database
 
1613
my @tables;
 
1614
my $zz = $DBI::VERSION; # mention it to eliminate "used only once" warning on perl 5.00503
 
1615
if ($DBI::VERSION < 1.20) {
 
1616
    @tables = map { $_ =~ s/.*\.//; $_ } $dbh->tables;
 
1617
}
 
1618
else {
 
1619
    my $sth = $dbh->table_info(undef, undef, undef, "TABLE");
 
1620
    @tables = @{$dbh->selectcol_arrayref($sth, { Columns => [3] })};
 
1621
}
 
1622
#print 'Tables: ', join " ", @tables, "\n";
 
1623
 
 
1624
# add lines here if you add more --LOCAL-- config vars that end up in the enums:
 
1625
 
 
1626
my $my_severities = '"' . join('", "', @my_severities) . '"';
 
1627
my $my_priorities = '"' . join('", "', @my_priorities) . '"';
 
1628
my $my_opsys      = '"' . join('", "', @my_opsys)      . '"';
 
1629
my $my_platforms  = '"' . join('", "', @my_platforms)  . '"';
 
1630
 
 
1631
# go throught our %table hash and create missing tables
 
1632
while (my ($tabname, $fielddef) = each %table) {
 
1633
    next if grep /^$tabname$/, @tables;
 
1634
    print "Creating table $tabname ...\n";
 
1635
 
 
1636
    # add lines here if you add more --LOCAL-- config vars that end up in
 
1637
    # the enums:
 
1638
 
 
1639
    $fielddef =~ s/\$my_severities/$my_severities/;
 
1640
    $fielddef =~ s/\$my_priorities/$my_priorities/;
 
1641
    $fielddef =~ s/\$my_opsys/$my_opsys/;
 
1642
    $fielddef =~ s/\$my_platforms/$my_platforms/;
 
1643
 
 
1644
    $dbh->do("CREATE TABLE $tabname (\n$fielddef\n)")
 
1645
        or die "Could not create table '$tabname'. Please check your '$db_base' access.\n";
 
1646
}
 
1647
 
 
1648
 
 
1649
 
 
1650
 
 
1651
 
 
1652
###########################################################################
 
1653
# Populate groups table
 
1654
###########################################################################
 
1655
 
 
1656
sub GroupDoesExist ($)
 
1657
{
 
1658
    my ($name) = @_;
 
1659
    my $sth = $dbh->prepare("SELECT name FROM groups WHERE name='$name'");
 
1660
    $sth->execute;
 
1661
    if ($sth->rows) {
 
1662
        return 1;
 
1663
    }
 
1664
    return 0;
 
1665
}
 
1666
 
 
1667
 
 
1668
#
 
1669
# This subroutine checks if a group exist. If not, it will be automatically
 
1670
# created with the next available bit set
 
1671
#
 
1672
 
 
1673
sub AddGroup {
 
1674
    my ($name, $desc, $userregexp) = @_;
 
1675
    $userregexp ||= "";
 
1676
 
 
1677
    return if GroupDoesExist($name);
 
1678
    
 
1679
    # get highest bit number
 
1680
    my $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC");
 
1681
    $sth->execute;
 
1682
    my @row = $sth->fetchrow_array;
 
1683
 
 
1684
    # normalize bits
 
1685
    my $bit;
 
1686
    if (defined $row[0]) {
 
1687
        $bit = $row[0] << 1;
 
1688
    } else {
 
1689
        $bit = 1;
 
1690
    }
 
1691
 
 
1692
   
 
1693
    print "Adding group $name ...\n";
 
1694
    $sth = $dbh->prepare('INSERT INTO groups
 
1695
                          (bit, name, description, userregexp, isbuggroup)
 
1696
                          VALUES (?, ?, ?, ?, ?)');
 
1697
    $sth->execute($bit, $name, $desc, $userregexp, 0);
 
1698
    return $bit;
 
1699
}
 
1700
 
 
1701
 
 
1702
#
 
1703
# BugZilla uses --GROUPS-- to assign various rights to its users. 
 
1704
#
 
1705
 
 
1706
AddGroup 'tweakparams',      'Can tweak operating parameters';
 
1707
AddGroup 'editusers',      'Can edit or disable users';
 
1708
AddGroup 'creategroups',     'Can create and destroy groups.';
 
1709
AddGroup 'editcomponents',   'Can create, destroy, and edit components.';
 
1710
AddGroup 'editkeywords',   'Can create, destroy, and edit keywords.';
 
1711
 
 
1712
# Add the groupset field here because this code is run before the
 
1713
# code that updates the database structure.
 
1714
&AddField('profiles', 'groupset', 'bigint not null');
 
1715
 
 
1716
if (!GroupDoesExist("editbugs")) {
 
1717
    my $id = AddGroup('editbugs',  'Can edit all aspects of any bug.', ".*");
 
1718
    $dbh->do("UPDATE profiles SET groupset = groupset | $id");
 
1719
}
 
1720
 
 
1721
if (!GroupDoesExist("canconfirm")) {
 
1722
    my $id = AddGroup('canconfirm',  'Can confirm a bug.', ".*");
 
1723
    $dbh->do("UPDATE profiles SET groupset = groupset | $id");
 
1724
}
 
1725
 
 
1726
 
 
1727
 
 
1728
 
 
1729
 
 
1730
###########################################################################
 
1731
# Populate the list of fields.
 
1732
###########################################################################
 
1733
 
 
1734
my $headernum = 1;
 
1735
 
 
1736
sub AddFDef ($$$) {
 
1737
    my ($name, $description, $mailhead) = (@_);
 
1738
 
 
1739
    $name = $dbh->quote($name);
 
1740
    $description = $dbh->quote($description);
 
1741
 
 
1742
    my $sth = $dbh->prepare("SELECT fieldid FROM fielddefs " .
 
1743
                            "WHERE name = $name");
 
1744
    $sth->execute();
 
1745
    my ($fieldid) = ($sth->fetchrow_array());
 
1746
    if (!$fieldid) {
 
1747
        $fieldid = 'NULL';
 
1748
    }
 
1749
 
 
1750
    $dbh->do("REPLACE INTO fielddefs " .
 
1751
             "(fieldid, name, description, mailhead, sortkey) VALUES " .
 
1752
             "($fieldid, $name, $description, $mailhead, $headernum)");
 
1753
    $headernum++;
 
1754
}
 
1755
 
 
1756
 
 
1757
AddFDef("bug_id", "Bug \#", 1);
 
1758
AddFDef("short_desc", "Summary", 1);
 
1759
AddFDef("product", "Product", 1);
 
1760
AddFDef("version", "Version", 1);
 
1761
AddFDef("rep_platform", "Platform", 1);
 
1762
AddFDef("bug_file_loc", "URL", 1);
 
1763
AddFDef("op_sys", "OS/Version", 1);
 
1764
AddFDef("bug_status", "Status", 1);
 
1765
AddFDef("status_whiteboard", "Status Whiteboard", 0);
 
1766
AddFDef("keywords", "Keywords", 0);
 
1767
AddFDef("resolution", "Resolution", 0);
 
1768
AddFDef("bug_severity", "Severity", 1);
 
1769
AddFDef("priority", "Priority", 1);
 
1770
AddFDef("component", "Component", 1);
 
1771
AddFDef("assigned_to", "AssignedTo", 1);
 
1772
AddFDef("reporter", "ReportedBy", 1);
 
1773
AddFDef("votes", "Votes", 0);
 
1774
AddFDef("qa_contact", "QAContact", 1);
 
1775
AddFDef("cc", "CC", 1);
 
1776
AddFDef("dependson", "BugsThisDependsOn", 0);
 
1777
AddFDef("blocked", "OtherBugsDependingOnThis", 0);
 
1778
AddFDef("attachments.description", "Attachment description", 0);
 
1779
AddFDef("attachments.thedata", "Attachment data", 0);
 
1780
AddFDef("attachments.mimetype", "Attachment mime type", 0);
 
1781
AddFDef("attachments.ispatch", "Attachment is patch", 0);
 
1782
AddFDef("attachments.isobsolete", "Attachment is obsolete", 0);
 
1783
AddFDef("attachstatusdefs.name", "Attachment Status", 0);
 
1784
AddFDef("target_milestone", "Target Milestone", 0);
 
1785
AddFDef("delta_ts", "Last changed date", 0);
 
1786
AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed",
 
1787
        0);
 
1788
AddFDef("longdesc", "Comment", 0);
 
1789
    
 
1790
    
 
1791
 
 
1792
 
 
1793
###########################################################################
 
1794
# Detect changed local settings
 
1795
###########################################################################
 
1796
 
 
1797
sub GetFieldDef ($$)
 
1798
{
 
1799
    my ($table, $field) = @_;
 
1800
    my $sth = $dbh->prepare("SHOW COLUMNS FROM $table");
 
1801
    $sth->execute;
 
1802
 
 
1803
    while (my $ref = $sth->fetchrow_arrayref) {
 
1804
        next if $$ref[0] ne $field;
 
1805
        return $ref;
 
1806
   }
 
1807
}
 
1808
 
 
1809
sub GetIndexDef ($$)
 
1810
{
 
1811
    my ($table, $field) = @_;
 
1812
    my $sth = $dbh->prepare("SHOW INDEX FROM $table");
 
1813
    $sth->execute;
 
1814
 
 
1815
    while (my $ref = $sth->fetchrow_arrayref) {
 
1816
        next if $$ref[2] ne $field;
 
1817
        return $ref;
 
1818
   }
 
1819
}
 
1820
 
 
1821
sub CountIndexes ($)
 
1822
{
 
1823
    my ($table) = @_;
 
1824
    
 
1825
    my $sth = $dbh->prepare("SHOW INDEX FROM $table");
 
1826
    $sth->execute;
 
1827
 
 
1828
    if ( $sth->rows == -1 ) {
 
1829
      die ("Unexpected response while counting indexes in $table:" .
 
1830
           " \$sth->rows == -1");
 
1831
    }
 
1832
    
 
1833
    return ($sth->rows);
 
1834
}
 
1835
 
 
1836
sub DropIndexes ($)
 
1837
{
 
1838
    my ($table) = @_;
 
1839
    my %SEEN;
 
1840
 
 
1841
    # get the list of indexes
 
1842
    #
 
1843
    my $sth = $dbh->prepare("SHOW INDEX FROM $table");
 
1844
    $sth->execute;
 
1845
 
 
1846
    # drop each index
 
1847
    #
 
1848
    while ( my $ref = $sth->fetchrow_arrayref) {
 
1849
      
 
1850
      # note that some indexes are described by multiple rows in the
 
1851
      # index table, so we may have already dropped the index described
 
1852
      # in the current row.
 
1853
      # 
 
1854
      next if exists $SEEN{$$ref[2]};
 
1855
 
 
1856
      my $dropSth = $dbh->prepare("ALTER TABLE $table DROP INDEX $$ref[2]");
 
1857
      $dropSth->execute;
 
1858
      $dropSth->finish;
 
1859
      $SEEN{$$ref[2]} = 1;
 
1860
 
 
1861
    }
 
1862
 
 
1863
}
 
1864
#
 
1865
# Check if the enums in the bugs table return the same values that are defined
 
1866
# in the various locally changeable variables. If this is true, then alter the
 
1867
# table definition.
 
1868
#
 
1869
 
 
1870
sub CheckEnumField ($$@)
 
1871
{
 
1872
    my ($table, $field, @against) = @_;
 
1873
 
 
1874
    my $ref = GetFieldDef($table, $field);
 
1875
    #print "0: $$ref[0]   1: $$ref[1]   2: $$ref[2]   3: $$ref[3]  4: $$ref[4]\n";
 
1876
    
 
1877
    $_ = "enum('" . join("','", @against) . "')";
 
1878
    if ($$ref[1] ne $_) {
 
1879
        print "Updating field $field in table $table ...\n";
 
1880
        $_ .= " NOT NULL" if $$ref[3];
 
1881
        $dbh->do("ALTER TABLE $table
 
1882
                  CHANGE $field
 
1883
                  $field $_");
 
1884
    }
 
1885
}
 
1886
 
 
1887
 
 
1888
 
 
1889
#
 
1890
# This code changes the enum types of some SQL tables whenever you change
 
1891
# some --LOCAL-- variables. Once you have a running system, to add new 
 
1892
# severities, priorities, operating systems and platforms, add them to 
 
1893
# the localconfig file and then re-run checksetup.pl which will make the 
 
1894
# necessary changes to your database. Additions to these fields in
 
1895
# checksetup.pl after the initial installation of bugzilla on a system
 
1896
# are ignored.
 
1897
#
 
1898
 
 
1899
CheckEnumField('bugs', 'bug_severity', @my_severities);
 
1900
CheckEnumField('bugs', 'priority',     @my_priorities);
 
1901
CheckEnumField('bugs', 'op_sys',       @my_opsys);
 
1902
CheckEnumField('bugs', 'rep_platform', @my_platforms);
 
1903
 
 
1904
 
 
1905
###########################################################################
 
1906
# Create Administrator  --ADMIN--
 
1907
###########################################################################
 
1908
 
 
1909
#  Prompt the user for the email address and name of an administrator.  Create
 
1910
#  that login, if it doesn't exist already, and make it a member of all groups.
 
1911
 
 
1912
sub bailout {   # this is just in case we get interrupted while getting passwd
 
1913
    system("stty","echo"); # re-enable input echoing
 
1914
    exit 1;
 
1915
}
 
1916
 
 
1917
my $sth = $dbh->prepare(<<_End_Of_SQL_);
 
1918
  SELECT login_name
 
1919
  FROM profiles
 
1920
  WHERE groupset=9223372036854775807
 
1921
_End_Of_SQL_
 
1922
$sth->execute;
 
1923
# when we have no admin users, prompt for admin email address and password ...
 
1924
if ($sth->rows == 0) {
 
1925
  my $login = "";
 
1926
  my $realname = "";
 
1927
  my $pass1 = "";
 
1928
  my $pass2 = "*";
 
1929
  my $admin_ok = 0;
 
1930
  my $admin_create = 1;
 
1931
  my $mailcheckexp = "";
 
1932
  my $mailcheck    = ""; 
 
1933
 
 
1934
  # Here we look to see what the emailregexp is set to so we can 
 
1935
  # check the email addy they enter. Bug 96675. If they have no 
 
1936
  # params (likely but not always the case), we use the default.
 
1937
  if (-e "data/params") { 
 
1938
    require "data/params"; # if they have a params file, use that
 
1939
  }
 
1940
  if ($::param{emailregexp}) {
 
1941
    $mailcheckexp = $::param{emailregexp};
 
1942
    $mailcheck    = $::param{emailregexpdesc};
 
1943
  } else {
 
1944
    $mailcheckexp = '^[^@]+@[^@]+\\.[^@]+$';
 
1945
    $mailcheck    = 'A legal address must contain exactly one \'@\', 
 
1946
      and at least one \'.\' after the @.';
 
1947
  }
 
1948
 
 
1949
  print "\nLooks like we don't have an administrator set up yet.  Either this is your\n";
 
1950
  print "first time using Bugzilla, or your administrator's privs might have accidently\n";
 
1951
  print "gotten deleted at some point.\n";
 
1952
  while(! $admin_ok ) {
 
1953
    while( $login eq "" ) {
 
1954
      print "Enter the e-mail address of the administrator: ";
 
1955
      $login = <STDIN>;
 
1956
      chomp $login;
 
1957
      if(! $login ) {
 
1958
        print "\nYou DO want an administrator, don't you?\n";
 
1959
      }
 
1960
      unless ($login =~ /$mailcheckexp/) {
 
1961
        print "\nThe login address is invalid:\n";
 
1962
        print "$mailcheck\n";
 
1963
        print "You can change this test on the params page once checksetup has successfully\n";
 
1964
        print "completed.\n\n";
 
1965
        # Go round, and ask them again
 
1966
        $login = "";
 
1967
      }
 
1968
    }
 
1969
    $login = $dbh->quote($login);
 
1970
    $sth = $dbh->prepare(<<_End_Of_SQL_);
 
1971
      SELECT login_name
 
1972
      FROM profiles
 
1973
      WHERE login_name=$login
 
1974
_End_Of_SQL_
 
1975
    $sth->execute;
 
1976
    if ($sth->rows > 0) {
 
1977
      print "$login already has an account.\n";
 
1978
      print "Make this user the administrator? [Y/n] ";
 
1979
      my $ok = <STDIN>;
 
1980
      chomp $ok;
 
1981
      if ($ok !~ /^n/i) {
 
1982
        $admin_ok = 1;
 
1983
        $admin_create = 0;
 
1984
      } else {
 
1985
        print "OK, well, someone has to be the administrator.  Try someone else.\n";
 
1986
        $login = "";
 
1987
      }
 
1988
    } else {
 
1989
      print "You entered $login.  Is this correct? [Y/n] ";
 
1990
      my $ok = <STDIN>;
 
1991
      chomp $ok;
 
1992
      if ($ok !~ /^n/i) {
 
1993
        $admin_ok = 1;
 
1994
      } else {
 
1995
        print "That's okay, typos happen.  Give it another shot.\n";
 
1996
        $login = "";
 
1997
      }
 
1998
    }
 
1999
  }
 
2000
 
 
2001
  if ($admin_create) {
 
2002
 
 
2003
    while( $realname eq "" ) {
 
2004
      print "Enter the real name of the administrator: ";
 
2005
      $realname = <STDIN>;
 
2006
      chomp $realname;
 
2007
      if(! $realname ) {
 
2008
        print "\nReally.  We need a full name.\n";
 
2009
      }
 
2010
    }
 
2011
 
 
2012
    # trap a few interrupts so we can fix the echo if we get aborted.
 
2013
    $SIG{HUP}  = \&bailout;
 
2014
    $SIG{INT}  = \&bailout;
 
2015
    $SIG{QUIT} = \&bailout;
 
2016
    $SIG{TERM} = \&bailout;
 
2017
 
 
2018
    system("stty","-echo");  # disable input echoing
 
2019
 
 
2020
    while( $pass1 ne $pass2 ) {
 
2021
      while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
 
2022
        print "Enter a password for the administrator account: ";
 
2023
        $pass1 = <STDIN>;
 
2024
        chomp $pass1;
 
2025
        if(! $pass1 ) {
 
2026
          print "\n\nIt's just plain stupid to not have a password.  Try again!\n";
 
2027
        } elsif ( $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
 
2028
          print "\n\nThe password must be 3-16 characters in length, " .
 
2029
                "and it may\ncontain only letters, digits, " .
 
2030
                "underscores (_), and hyphens (-).\n";
 
2031
        }
 
2032
      }
 
2033
      print "\nPlease retype the password to verify: ";
 
2034
      $pass2 = <STDIN>;
 
2035
      chomp $pass2;
 
2036
      if ($pass1 ne $pass2) {
 
2037
        print "\n\nPasswords don't match.  Try again!\n";
 
2038
        $pass1 = "";
 
2039
        $pass2 = "*";
 
2040
      }
 
2041
    }
 
2042
 
 
2043
    # Crypt the administrator's password
 
2044
    my $cryptedpassword = Crypt($pass1);
 
2045
 
 
2046
    system("stty","echo"); # re-enable input echoing
 
2047
    $SIG{HUP}  = 'DEFAULT'; # and remove our interrupt hooks
 
2048
    $SIG{INT}  = 'DEFAULT';
 
2049
    $SIG{QUIT} = 'DEFAULT';
 
2050
    $SIG{TERM} = 'DEFAULT';
 
2051
 
 
2052
    $realname = $dbh->quote($realname);
 
2053
    $cryptedpassword = $dbh->quote($cryptedpassword);
 
2054
 
 
2055
    $dbh->do(<<_End_Of_SQL_);
 
2056
      INSERT INTO profiles
 
2057
      (login_name, realname, cryptpassword, groupset)
 
2058
      VALUES ($login, $realname, $cryptedpassword, 0x7fffffffffffffff)
 
2059
_End_Of_SQL_
 
2060
  } else {
 
2061
    $dbh->do(<<_End_Of_SQL_);
 
2062
      UPDATE profiles
 
2063
      SET groupset=0x7fffffffffffffff
 
2064
      WHERE login_name=$login
 
2065
_End_Of_SQL_
 
2066
  }
 
2067
  print "\n$login is now set up as the administrator account.\n";
 
2068
}
 
2069
 
 
2070
 
 
2071
 
 
2072
 
 
2073
###########################################################################
 
2074
# Create initial test product if there are no products present.
 
2075
###########################################################################
 
2076
 
 
2077
$sth = $dbh->prepare(<<_End_Of_SQL_);
 
2078
  SELECT userid
 
2079
  FROM profiles
 
2080
  WHERE groupset=9223372036854775807
 
2081
_End_Of_SQL_
 
2082
$sth->execute;
 
2083
my ($adminuid) = $sth->fetchrow_array;
 
2084
if (!$adminuid) { die "No administator!" } # should never get here
 
2085
$sth = $dbh->prepare("SELECT product FROM products");
 
2086
$sth->execute;
 
2087
unless ($sth->rows) {
 
2088
    print "Creating initial dummy product 'TestProduct' ...\n";
 
2089
    $dbh->do('INSERT INTO products(product, description, milestoneurl, disallownew, votesperuser, votestoconfirm) VALUES ("TestProduct",
 
2090
              "This is a test product.  This ought to be blown away and ' .
 
2091
             'replaced with real stuff in a finished installation of ' .
 
2092
             'bugzilla.", "", 0, 0, 0)');
 
2093
    $dbh->do('INSERT INTO versions (value, program) VALUES ("other", "TestProduct")');
 
2094
    $dbh->do("INSERT INTO components (value, program, description, initialowner, initialqacontact)
 
2095
             VALUES (" .
 
2096
             "'TestComponent', 'TestProduct', " .
 
2097
             "'This is a test component in the test product database.  " .
 
2098
             "This ought to be blown away and replaced with real stuff in " .
 
2099
             "a finished installation of bugzilla.', $adminuid, 0)");
 
2100
    $dbh->do('INSERT INTO milestones (product, value) VALUES ("TestProduct","---")');
 
2101
}
 
2102
 
 
2103
 
 
2104
 
 
2105
 
 
2106
 
 
2107
###########################################################################
 
2108
# Update the tables to the current definition
 
2109
###########################################################################
 
2110
 
 
2111
#
 
2112
# As time passes, fields in tables get deleted, added, changed and so on.
 
2113
# So we need some helper subroutines to make this possible:
 
2114
#
 
2115
 
 
2116
sub ChangeFieldType ($$$)
 
2117
{
 
2118
    my ($table, $field, $newtype) = @_;
 
2119
 
 
2120
    my $ref = GetFieldDef($table, $field);
 
2121
    #print "0: $$ref[0]   1: $$ref[1]   2: $$ref[2]   3: $$ref[3]  4: $$ref[4]\n";
 
2122
 
 
2123
    my $oldtype = $ref->[1];
 
2124
    if (! $ref->[2]) {
 
2125
        $oldtype .= qq{ not null};
 
2126
    }
 
2127
    if ($ref->[4]) {
 
2128
        $oldtype .= qq{ default "$ref->[4]"};
 
2129
    }
 
2130
 
 
2131
    if ($oldtype ne $newtype) {
 
2132
        print "Updating field type $field in table $table ...\n";
 
2133
        print "old: $oldtype\n";
 
2134
        print "new: $newtype\n";
 
2135
#        'not null' should be passed as part of the call to ChangeFieldType()
 
2136
#        $newtype .= " NOT NULL" if $$ref[3];
 
2137
        $dbh->do("ALTER TABLE $table
 
2138
                  CHANGE $field
 
2139
                  $field $newtype");
 
2140
    }
 
2141
}
 
2142
 
 
2143
sub RenameField ($$$)
 
2144
{
 
2145
    my ($table, $field, $newname) = @_;
 
2146
 
 
2147
    my $ref = GetFieldDef($table, $field);
 
2148
    return unless $ref; # already fixed?
 
2149
    #print "0: $$ref[0]   1: $$ref[1]   2: $$ref[2]   3: $$ref[3]  4: $$ref[4]\n";
 
2150
 
 
2151
    if ($$ref[1] ne $newname) {
 
2152
        print "Updating field $field in table $table ...\n";
 
2153
        my $type = $$ref[1];
 
2154
        $type .= " NOT NULL" if $$ref[3];
 
2155
        $dbh->do("ALTER TABLE $table
 
2156
                  CHANGE $field
 
2157
                  $newname $type");
 
2158
    }
 
2159
}
 
2160
 
 
2161
sub AddField ($$$)
 
2162
{
 
2163
    my ($table, $field, $definition) = @_;
 
2164
 
 
2165
    my $ref = GetFieldDef($table, $field);
 
2166
    return if $ref; # already added?
 
2167
 
 
2168
    print "Adding new field $field to table $table ...\n";
 
2169
    $dbh->do("ALTER TABLE $table
 
2170
              ADD COLUMN $field $definition");
 
2171
}
 
2172
 
 
2173
sub DropField ($$)
 
2174
{
 
2175
    my ($table, $field) = @_;
 
2176
 
 
2177
    my $ref = GetFieldDef($table, $field);
 
2178
    return unless $ref; # already dropped?
 
2179
 
 
2180
    print "Deleting unused field $field from table $table ...\n";
 
2181
    $dbh->do("ALTER TABLE $table
 
2182
              DROP COLUMN $field");
 
2183
}
 
2184
 
 
2185
# this uses a mysql specific command. 
 
2186
sub TableExists ($)
 
2187
{
 
2188
   my ($table) = @_;
 
2189
   my @tables;
 
2190
   my $dbtable;
 
2191
   my $exists = 0;
 
2192
   my $sth = $dbh->prepare("SHOW TABLES");
 
2193
   $sth->execute;
 
2194
   while ( ($dbtable) = $sth->fetchrow_array ) {
 
2195
      if ($dbtable eq $table) {
 
2196
         $exists = 1;
 
2197
      } 
 
2198
   } 
 
2199
   return $exists;
 
2200
}   
 
2201
 
 
2202
 
 
2203
# really old fields that were added before checksetup.pl existed
 
2204
# but aren't in very old bugzilla's (like 2.1)
 
2205
# Steve Stock (sstock@iconnect-inc.com)
 
2206
AddField('bugs', 'target_milestone', 'varchar(20) not null default "---"');
 
2207
AddField('bugs', 'groupset', 'bigint not null');
 
2208
AddField('bugs', 'qa_contact', 'mediumint not null');
 
2209
AddField('bugs', 'status_whiteboard', 'mediumtext not null');
 
2210
AddField('products', 'disallownew', 'tinyint not null');
 
2211
AddField('products', 'milestoneurl', 'tinytext not null');
 
2212
AddField('components', 'initialqacontact', 'tinytext not null');
 
2213
AddField('components', 'description', 'mediumtext not null');
 
2214
ChangeFieldType('components', 'program', 'varchar(64)');
 
2215
 
 
2216
 
 
2217
# 1999-06-22 Added an entry to the attachments table to record who the
 
2218
# submitter was.  Nothing uses this yet, but it still should be recorded.
 
2219
 
 
2220
AddField('attachments', 'submitter_id', 'mediumint not null');
 
2221
 
 
2222
#
 
2223
# One could even populate this field automatically, e.g. with
 
2224
#
 
2225
# unless (GetField('attachments', 'submitter_id') {
 
2226
#    AddField ...
 
2227
#    populate
 
2228
# }
 
2229
#
 
2230
# For now I was too lazy, so you should read the documentation :-)
 
2231
 
 
2232
 
 
2233
 
 
2234
# 1999-9-15 Apparently, newer alphas of MySQL won't allow you to have "when"
 
2235
# as a column name.  So, I have had to rename a column in the bugs_activity
 
2236
# table.
 
2237
 
 
2238
RenameField ('bugs_activity', 'when', 'bug_when');
 
2239
 
 
2240
 
 
2241
 
 
2242
# 1999-10-11 Restructured voting database to add a cached value in each bug
 
2243
# recording how many total votes that bug has.  While I'm at it, I removed
 
2244
# the unused "area" field from the bugs database.  It is distressing to
 
2245
# realize that the bugs table has reached the maximum number of indices
 
2246
# allowed by MySQL (16), which may make future enhancements awkward.
 
2247
# (P.S. All is not lost; it appears that the latest betas of MySQL support
 
2248
# a new table format which will allow 32 indices.)
 
2249
 
 
2250
DropField('bugs', 'area');
 
2251
AddField('bugs',     'votes',        'mediumint not null, add index (votes)');
 
2252
AddField('products', 'votesperuser', 'mediumint not null');
 
2253
 
 
2254
 
 
2255
 
 
2256
# The product name used to be very different in various tables.
 
2257
#
 
2258
# It was   varchar(16)   in bugs
 
2259
#          tinytext      in components
 
2260
#          tinytext      in products
 
2261
#          tinytext      in versions
 
2262
#
 
2263
# tinytext is equivalent to varchar(255), which is quite huge, so I change
 
2264
# them all to varchar(64).
 
2265
 
 
2266
ChangeFieldType ('bugs',       'product', 'varchar(64) not null');
 
2267
ChangeFieldType ('components', 'program', 'varchar(64)');
 
2268
ChangeFieldType ('products',   'product', 'varchar(64)');
 
2269
ChangeFieldType ('versions',   'program', 'varchar(64) not null');
 
2270
 
 
2271
# 2000-01-16 Added a "keywords" field to the bugs table, which
 
2272
# contains a string copy of the entries of the keywords table for this
 
2273
# bug.  This is so that I can easily sort and display a keywords
 
2274
# column in bug lists.
 
2275
 
 
2276
if (!GetFieldDef('bugs', 'keywords')) {
 
2277
    AddField('bugs', 'keywords', 'mediumtext not null');
 
2278
 
 
2279
    my @kwords;
 
2280
    print "Making sure 'keywords' field of table 'bugs' is empty ...\n";
 
2281
    $dbh->do("UPDATE bugs SET delta_ts = delta_ts, keywords = '' " .
 
2282
             "WHERE keywords != ''");
 
2283
    print "Repopulating 'keywords' field of table 'bugs' ...\n";
 
2284
    my $sth = $dbh->prepare("SELECT keywords.bug_id, keyworddefs.name " .
 
2285
                            "FROM keywords, keyworddefs " .
 
2286
                            "WHERE keyworddefs.id = keywords.keywordid " .
 
2287
                            "ORDER BY keywords.bug_id, keyworddefs.name");
 
2288
    $sth->execute;
 
2289
    my @list;
 
2290
    my $bugid = 0;
 
2291
    my @row;
 
2292
    while (1) {
 
2293
        my ($b, $k) = ($sth->fetchrow_array());
 
2294
        if (!defined $b || $b ne $bugid) {
 
2295
            if (@list) {
 
2296
                $dbh->do("UPDATE bugs SET delta_ts = delta_ts, keywords = " .
 
2297
                         $dbh->quote(join(', ', @list)) .
 
2298
                         " WHERE bug_id = $bugid");
 
2299
            }
 
2300
            if (!$b) {
 
2301
                last;
 
2302
            }
 
2303
            $bugid = $b;
 
2304
            @list = ();
 
2305
        }
 
2306
        push(@list, $k);
 
2307
    }
 
2308
}
 
2309
 
 
2310
 
 
2311
# 2000-01-18 Added a "disabledtext" field to the profiles table.  If not
 
2312
# empty, then this account has been disabled, and this field is to contain
 
2313
# text describing why.
 
2314
 
 
2315
AddField('profiles', 'disabledtext',  'mediumtext not null');
 
2316
 
 
2317
 
 
2318
 
 
2319
# 2000-01-20 Added a new "longdescs" table, which is supposed to have all the
 
2320
# long descriptions in it, replacing the old long_desc field in the bugs 
 
2321
# table.  The below hideous code populates this new table with things from
 
2322
# the old field, with ugly parsing and heuristics.
 
2323
 
 
2324
sub WriteOneDesc {
 
2325
    my ($id, $who, $when, $buffer) = (@_);
 
2326
    $buffer = trim($buffer);
 
2327
    if ($buffer eq '') {
 
2328
        return;
 
2329
    }
 
2330
    $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, thetext) VALUES " .
 
2331
             "($id, $who, " .  time2str("'%Y/%m/%d %H:%M:%S'", $when) .
 
2332
             ", " . $dbh->quote($buffer) . ")");
 
2333
}
 
2334
 
 
2335
 
 
2336
if (GetFieldDef('bugs', 'long_desc')) {
 
2337
    eval("use Date::Parse");
 
2338
    eval("use Date::Format");
 
2339
    my $sth = $dbh->prepare("SELECT count(*) FROM bugs");
 
2340
    $sth->execute();
 
2341
    my ($total) = ($sth->fetchrow_array);
 
2342
 
 
2343
    print "Populating new long_desc table.  This is slow.  There are $total\n";
 
2344
    print "bugs to process; a line of dots will be printed for each 50.\n\n";
 
2345
    $| = 1;
 
2346
 
 
2347
    $dbh->do("LOCK TABLES bugs write, longdescs write, profiles write");
 
2348
 
 
2349
    $dbh->do('DELETE FROM longdescs');
 
2350
 
 
2351
    $sth = $dbh->prepare("SELECT bug_id, creation_ts, reporter, long_desc " .
 
2352
                         "FROM bugs ORDER BY bug_id");
 
2353
    $sth->execute();
 
2354
    my $count = 0;
 
2355
    while (1) {
 
2356
        my ($id, $createtime, $reporterid, $desc) = ($sth->fetchrow_array());
 
2357
        if (!$id) {
 
2358
            last;
 
2359
        }
 
2360
        print ".";
 
2361
        $count++;
 
2362
        if ($count % 10 == 0) {
 
2363
            print " ";
 
2364
            if ($count % 50 == 0) {
 
2365
                print "$count/$total (" . int($count * 100 / $total) . "%)\n";
 
2366
            }
 
2367
        }
 
2368
        $desc =~ s/\r//g;
 
2369
        my $who = $reporterid;
 
2370
        my $when = str2time($createtime);
 
2371
        my $buffer = "";
 
2372
        foreach my $line (split(/\n/, $desc)) {
 
2373
            $line =~ s/\s+$//g;       # Trim trailing whitespace.
 
2374
            if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/) {
 
2375
                my $name = $1;
 
2376
                my $date = str2time($2);
 
2377
                $date += 59;    # Oy, what a hack.  The creation time is
 
2378
                                # accurate to the second.  But we the long
 
2379
                                # text only contains things accurate to the
 
2380
                                # minute.  And so, if someone makes a comment
 
2381
                                # within a minute of the original bug creation,
 
2382
                                # then the comment can come *before* the
 
2383
                                # bug creation.  So, we add 59 seconds to
 
2384
                                # the time of all comments, so that they
 
2385
                                # are always considered to have happened at
 
2386
                                # the *end* of the given minute, not the
 
2387
                                # beginning.
 
2388
                if ($date >= $when) {
 
2389
                    WriteOneDesc($id, $who, $when, $buffer);
 
2390
                    $buffer = "";
 
2391
                    $when = $date;
 
2392
                    my $s2 = $dbh->prepare("SELECT userid FROM profiles " .
 
2393
                                           "WHERE login_name = " .
 
2394
                                           $dbh->quote($name));
 
2395
                    $s2->execute();
 
2396
                    ($who) = ($s2->fetchrow_array());
 
2397
                    if (!$who) {
 
2398
                        # This username doesn't exist.  Try a special
 
2399
                        # netscape-only hack (sorry about that, but I don't
 
2400
                        # think it will hurt any other installations).  We
 
2401
                        # have many entries in the bugsystem from an ancient
 
2402
                        # world where the "@netscape.com" part of the loginname
 
2403
                        # was omitted.  So, look up the user again with that
 
2404
                        # appended, and use it if it's there.
 
2405
                        if ($name !~ /\@/) {
 
2406
                            my $nsname = $name . "\@netscape.com";
 
2407
                            $s2 =
 
2408
                                $dbh->prepare("SELECT userid FROM profiles " .
 
2409
                                              "WHERE login_name = " .
 
2410
                                              $dbh->quote($nsname));
 
2411
                            $s2->execute();
 
2412
                            ($who) = ($s2->fetchrow_array());
 
2413
                        }
 
2414
                    }
 
2415
                            
 
2416
                    if (!$who) {
 
2417
                        # This username doesn't exist.  Maybe someone renamed
 
2418
                        # him or something.  Invent a new profile entry,
 
2419
                        # disabled, just to represent him.
 
2420
                        $dbh->do("INSERT INTO profiles " .
 
2421
                                 "(login_name, cryptpassword," .
 
2422
                                 " disabledtext) VALUES (" .
 
2423
                                 $dbh->quote($name) .
 
2424
                                 ", " . $dbh->quote(Crypt('okthen')) . ", " . 
 
2425
                                 "'Account created only to maintain database integrity')");
 
2426
                        $s2 = $dbh->prepare("SELECT LAST_INSERT_ID()");
 
2427
                        $s2->execute();
 
2428
                        ($who) = ($s2->fetchrow_array());
 
2429
                    }
 
2430
                    next;
 
2431
                } else {
 
2432
#                    print "\nDecided this line of bug $id has a date of " .
 
2433
#                        time2str("'%Y/%m/%d %H:%M:%S'", $date) .
 
2434
#                            "\nwhich is less than previous line:\n$line\n\n";
 
2435
                }
 
2436
 
 
2437
            }
 
2438
            $buffer .= $line . "\n";
 
2439
        }
 
2440
        WriteOneDesc($id, $who, $when, $buffer);
 
2441
    }
 
2442
                
 
2443
 
 
2444
    print "\n\n";
 
2445
 
 
2446
    DropField('bugs', 'long_desc');
 
2447
 
 
2448
    $dbh->do("UNLOCK TABLES");
 
2449
}
 
2450
 
 
2451
 
 
2452
# 2000-01-18 Added a new table fielddefs that records information about the
 
2453
# different fields we keep an activity log on.  The bugs_activity table
 
2454
# now has a pointer into that table instead of recording the name directly.
 
2455
 
 
2456
if (GetFieldDef('bugs_activity', 'field')) {
 
2457
    AddField('bugs_activity', 'fieldid',
 
2458
             'mediumint not null, ADD INDEX (fieldid)');
 
2459
    print "Populating new fieldid field ...\n";
 
2460
 
 
2461
    $dbh->do("LOCK TABLES bugs_activity WRITE, fielddefs WRITE");
 
2462
 
 
2463
    my $sth = $dbh->prepare('SELECT DISTINCT field FROM bugs_activity');
 
2464
    $sth->execute();
 
2465
    my %ids;
 
2466
    while (my ($f) = ($sth->fetchrow_array())) {
 
2467
        my $q = $dbh->quote($f);
 
2468
        my $s2 =
 
2469
            $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = $q");
 
2470
        $s2->execute();
 
2471
        my ($id) = ($s2->fetchrow_array());
 
2472
        if (!$id) {
 
2473
            $dbh->do("INSERT INTO fielddefs (name, description) VALUES " .
 
2474
                     "($q, $q)");
 
2475
            $s2 = $dbh->prepare("SELECT LAST_INSERT_ID()");
 
2476
            $s2->execute();
 
2477
            ($id) = ($s2->fetchrow_array());
 
2478
        }
 
2479
        $dbh->do("UPDATE bugs_activity SET fieldid = $id WHERE field = $q");
 
2480
    }
 
2481
    $dbh->do("UNLOCK TABLES");
 
2482
 
 
2483
    DropField('bugs_activity', 'field');
 
2484
}
 
2485
        
 
2486
 
 
2487
# 2000-01-18 New email-notification scheme uses a new field in the bug to 
 
2488
# record when email notifications were last sent about this bug.  Also,
 
2489
# added a user pref whether a user wants to use the brand new experimental
 
2490
# stuff.
 
2491
# 2001-04-29 jake@acutex.net - The newemailtech field is no longer needed
 
2492
#   http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
 
2493
 
 
2494
if (!GetFieldDef('bugs', 'lastdiffed')) {
 
2495
    AddField('bugs', 'lastdiffed', 'datetime not null');
 
2496
    $dbh->do('UPDATE bugs SET lastdiffed = now(), delta_ts = delta_ts');
 
2497
}
 
2498
 
 
2499
 
 
2500
# 2000-01-22 The "login_name" field in the "profiles" table was not
 
2501
# declared to be unique.  Sure enough, somehow, I got 22 duplicated entries
 
2502
# in my database.  This code detects that, cleans up the duplicates, and
 
2503
# then tweaks the table to declare the field to be unique.  What a pain.
 
2504
 
 
2505
if (GetIndexDef('profiles', 'login_name')->[1]) {
 
2506
    print "Searching for duplicate entries in the profiles table ...\n";
 
2507
    while (1) {
 
2508
        # This code is weird in that it loops around and keeps doing this
 
2509
        # select again.  That's because I'm paranoid about deleting entries
 
2510
        # out from under us in the profiles table.  Things get weird if
 
2511
        # there are *three* or more entries for the same user...
 
2512
        $sth = $dbh->prepare("SELECT p1.userid, p2.userid, p1.login_name " .
 
2513
                             "FROM profiles AS p1, profiles AS p2 " .
 
2514
                             "WHERE p1.userid < p2.userid " .
 
2515
                             "AND p1.login_name = p2.login_name " .
 
2516
                             "ORDER BY p1.login_name");
 
2517
        $sth->execute();
 
2518
        my ($u1, $u2, $n) = ($sth->fetchrow_array);
 
2519
        if (!$u1) {
 
2520
            last;
 
2521
        }
 
2522
        print "Both $u1 & $u2 are ids for $n!  Merging $u2 into $u1 ...\n";
 
2523
        foreach my $i (["bugs", "reporter"],
 
2524
                       ["bugs", "assigned_to"],
 
2525
                       ["bugs", "qa_contact"],
 
2526
                       ["attachments", "submitter_id"],
 
2527
                       ["bugs_activity", "who"],
 
2528
                       ["cc", "who"],
 
2529
                       ["votes", "who"],
 
2530
                       ["longdescs", "who"]) {
 
2531
            my ($table, $field) = (@$i);
 
2532
            print "   Updating $table.$field ...\n";
 
2533
            my $extra = "";
 
2534
            if ($table eq "bugs") {
 
2535
                $extra = ", delta_ts = delta_ts";
 
2536
            }
 
2537
            $dbh->do("UPDATE $table SET $field = $u1 $extra " .
 
2538
                     "WHERE $field = $u2");
 
2539
        }
 
2540
        $dbh->do("DELETE FROM profiles WHERE userid = $u2");
 
2541
    }
 
2542
    print "OK, changing index type to prevent duplicates in the future ...\n";
 
2543
    
 
2544
    $dbh->do("ALTER TABLE profiles DROP INDEX login_name");
 
2545
    $dbh->do("ALTER TABLE profiles ADD UNIQUE (login_name)");
 
2546
 
 
2547
}    
 
2548
 
 
2549
 
 
2550
# 2000-01-24 Added a new field to let people control whether the "My
 
2551
# bugs" link appears at the bottom of each page.  Also can control
 
2552
# whether each named query should show up there.
 
2553
 
 
2554
AddField('profiles', 'mybugslink', 'tinyint not null default 1');
 
2555
AddField('namedqueries', 'linkinfooter', 'tinyint not null');
 
2556
 
 
2557
 
 
2558
# 2000-02-12 Added a new state to bugs, UNCONFIRMED.  Added ability to confirm
 
2559
# a vote via bugs.  Added user bits to control which users can confirm bugs
 
2560
# by themselves, and which users can edit bugs without their names on them.
 
2561
# Added a user field which controls which groups a user can put other users 
 
2562
# into.
 
2563
 
 
2564
my @resolutions = ("", "FIXED", "INVALID", "WONTFIX", "LATER", "REMIND",
 
2565
                  "DUPLICATE", "WORKSFORME", "MOVED");
 
2566
CheckEnumField('bugs', 'resolution', @resolutions);
 
2567
 
 
2568
if (($_ = GetFieldDef('components', 'initialowner')) and ($_->[1] eq 'tinytext')) {
 
2569
    $sth = $dbh->prepare("SELECT program, value, initialowner, initialqacontact FROM components");
 
2570
    $sth->execute();
 
2571
    while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) {
 
2572
        $initialowner =~ s/([\\\'])/\\$1/g; $initialowner =~ s/\0/\\0/g;
 
2573
        $program =~ s/([\\\'])/\\$1/g; $program =~ s/\0/\\0/g;
 
2574
        $value =~ s/([\\\'])/\\$1/g; $value =~ s/\0/\\0/g;
 
2575
 
 
2576
        my $s2 = $dbh->prepare("SELECT userid FROM profiles WHERE login_name = '$initialowner'");
 
2577
        $s2->execute();
 
2578
 
 
2579
        my $initialownerid = $s2->fetchrow_array();
 
2580
 
 
2581
        unless (defined $initialownerid) {
 
2582
            print "Warning: You have an invalid initial owner '$initialowner' in program '$program', component '$value'!\n";
 
2583
            $initialownerid = 0;
 
2584
        }
 
2585
 
 
2586
        my $update = "UPDATE components SET initialowner = $initialownerid ".
 
2587
            "WHERE program = '$program' AND value = '$value'";
 
2588
        my $s3 = $dbh->prepare("UPDATE components SET initialowner = $initialownerid ".
 
2589
                               "WHERE program = '$program' AND value = '$value';");
 
2590
        $s3->execute();
 
2591
    }
 
2592
 
 
2593
    ChangeFieldType('components','initialowner','mediumint');
 
2594
}
 
2595
 
 
2596
if (($_ = GetFieldDef('components', 'initialqacontact')) and ($_->[1] eq 'tinytext')) {
 
2597
    $sth = $dbh->prepare("SELECT program, value, initialqacontact, initialqacontact FROM components");
 
2598
    $sth->execute();
 
2599
    while (my ($program, $value, $initialqacontact) = $sth->fetchrow_array()) {
 
2600
        $initialqacontact =~ s/([\\\'])/\\$1/g; $initialqacontact =~ s/\0/\\0/g;
 
2601
        $program =~ s/([\\\'])/\\$1/g; $program =~ s/\0/\\0/g;
 
2602
        $value =~ s/([\\\'])/\\$1/g; $value =~ s/\0/\\0/g;
 
2603
 
 
2604
        my $s2 = $dbh->prepare("SELECT userid FROM profiles WHERE login_name = '$initialqacontact'");
 
2605
        $s2->execute();
 
2606
 
 
2607
        my $initialqacontactid = $s2->fetchrow_array();
 
2608
 
 
2609
        unless (defined $initialqacontactid) {
 
2610
            if ($initialqacontact ne '') {
 
2611
                print "Warning: You have an invalid initial QA contact '$initialqacontact' in program '$program', component '$value'!\n";
 
2612
            }
 
2613
            $initialqacontactid = 0;
 
2614
        }
 
2615
 
 
2616
        my $update = "UPDATE components SET initialqacontact = $initialqacontactid ".
 
2617
            "WHERE program = '$program' AND value = '$value'";
 
2618
        my $s3 = $dbh->prepare("UPDATE components SET initialqacontact = $initialqacontactid ".
 
2619
                               "WHERE program = '$program' AND value = '$value';");
 
2620
        $s3->execute();
 
2621
    }
 
2622
 
 
2623
    ChangeFieldType('components','initialqacontact','mediumint');
 
2624
}
 
2625
 
 
2626
 
 
2627
 
 
2628
my @states = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED", "RESOLVED",
 
2629
              "VERIFIED", "CLOSED");
 
2630
CheckEnumField('bugs', 'bug_status', @states);
 
2631
 
 
2632
if (!GetFieldDef('bugs', 'everconfirmed')) {
 
2633
    AddField('bugs', 'everconfirmed',  'tinyint not null');
 
2634
    $dbh->do("UPDATE bugs SET everconfirmed = 1, delta_ts = delta_ts");
 
2635
}
 
2636
AddField('products', 'maxvotesperbug', 'smallint not null default 10000');
 
2637
AddField('products', 'votestoconfirm', 'smallint not null');
 
2638
AddField('profiles', 'blessgroupset', 'bigint not null');
 
2639
 
 
2640
# 2000-03-21 Adding a table for target milestones to 
 
2641
# database - matthew@zeroknowledge.com
 
2642
 
 
2643
$sth = $dbh->prepare("SELECT count(*) from milestones");
 
2644
$sth->execute();
 
2645
if (!($sth->fetchrow_arrayref()->[0])) {
 
2646
    print "Replacing blank milestones...\n";
 
2647
    $dbh->do("UPDATE bugs SET target_milestone = '---', delta_ts=delta_ts WHERE target_milestone = ' '");
 
2648
    
 
2649
# Populate milestone table with all exisiting values in database
 
2650
    $sth = $dbh->prepare("SELECT DISTINCT target_milestone, product FROM bugs");
 
2651
    $sth->execute();
 
2652
    
 
2653
    print "Populating milestones table...\n";
 
2654
    
 
2655
    my $value;
 
2656
    my $product;
 
2657
    while(($value, $product) = $sth->fetchrow_array())
 
2658
    {
 
2659
        # check if the value already exists
 
2660
        my $sortkey = substr($value, 1);
 
2661
        if ($sortkey !~ /^\d+$/) {
 
2662
            $sortkey = 0;
 
2663
        } else {
 
2664
            $sortkey *= 10;
 
2665
        }
 
2666
        $value = $dbh->quote($value);
 
2667
        $product = $dbh->quote($product);
 
2668
        my $s2 = $dbh->prepare("SELECT value FROM milestones WHERE value = $value AND product = $product");
 
2669
        $s2->execute();
 
2670
        
 
2671
        if(!$s2->fetchrow_array())
 
2672
        {
 
2673
            $dbh->do("INSERT INTO milestones(value, product, sortkey) VALUES($value, $product, $sortkey)");
 
2674
        }
 
2675
    }
 
2676
}
 
2677
 
 
2678
# 2000-03-22 Changed the default value for target_milestone to be "---"
 
2679
# (which is still not quite correct, but much better than what it was 
 
2680
# doing), and made the size of the value field in the milestones table match
 
2681
# the size of the target_milestone field in the bugs table.
 
2682
 
 
2683
ChangeFieldType('bugs', 'target_milestone',
 
2684
                'varchar(20) not null default "---"');
 
2685
ChangeFieldType('milestones', 'value', 'varchar(20) not null');
 
2686
 
 
2687
 
 
2688
# 2000-03-23 Added a defaultmilestone field to the products table, so that
 
2689
# we know which milestone to initially assign bugs to.
 
2690
 
 
2691
if (!GetFieldDef('products', 'defaultmilestone')) {
 
2692
    AddField('products', 'defaultmilestone',
 
2693
             'varchar(20) not null default "---"');
 
2694
    $sth = $dbh->prepare("SELECT product, defaultmilestone FROM products");
 
2695
    $sth->execute();
 
2696
    while (my ($product, $defaultmilestone) = $sth->fetchrow_array()) {
 
2697
        $product = $dbh->quote($product);
 
2698
        $defaultmilestone = $dbh->quote($defaultmilestone);
 
2699
        my $s2 = $dbh->prepare("SELECT value FROM milestones " .
 
2700
                               "WHERE value = $defaultmilestone " .
 
2701
                               "AND product = $product");
 
2702
        $s2->execute();
 
2703
        if (!$s2->fetchrow_array()) {
 
2704
            $dbh->do("INSERT INTO milestones(value, product) " .
 
2705
                     "VALUES ($defaultmilestone, $product)");
 
2706
        }
 
2707
    }
 
2708
}
 
2709
 
 
2710
# 2000-03-24 Added unique indexes into the cc and keyword tables.  This
 
2711
# prevents certain database inconsistencies, and, moreover, is required for
 
2712
# new generalized list code to work.
 
2713
 
 
2714
if ( CountIndexes('cc') != 3 ) {
 
2715
 
 
2716
    # XXX should eliminate duplicate entries before altering
 
2717
    #
 
2718
    print "Recreating indexes on cc table.\n";
 
2719
    DropIndexes('cc');
 
2720
    $dbh->do("ALTER TABLE cc ADD UNIQUE (bug_id,who)");
 
2721
    $dbh->do("ALTER TABLE cc ADD INDEX (who)");
 
2722
}    
 
2723
 
 
2724
if ( CountIndexes('keywords') != 3 ) {
 
2725
 
 
2726
    # XXX should eliminate duplicate entries before altering
 
2727
    #
 
2728
    print "Recreating indexes on keywords table.\n";
 
2729
    DropIndexes('keywords');
 
2730
    $dbh->do("ALTER TABLE keywords ADD INDEX (keywordid)");
 
2731
    $dbh->do("ALTER TABLE keywords ADD UNIQUE (bug_id,keywordid)");
 
2732
 
 
2733
}    
 
2734
 
 
2735
# 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a better 
 
2736
# way than it used to. This code searches the comments to populate the table
 
2737
# initially. It's executed if the table is empty; if it's empty because there
 
2738
# are no dupes (as opposed to having just created the table) it won't have
 
2739
# any effect anyway, so it doesn't matter.
 
2740
$sth = $dbh->prepare("SELECT count(*) from duplicates");
 
2741
$sth->execute();
 
2742
if (!($sth->fetchrow_arrayref()->[0])) {
 
2743
        # populate table
 
2744
        print("Populating duplicates table...\n");
 
2745
 
 
2746
        $sth = $dbh->prepare("SELECT longdescs.bug_id, thetext FROM longdescs left JOIN bugs using(bug_id) WHERE (thetext " . 
 
2747
                "regexp '[.*.]{3,3} This bug has been marked as a duplicate of [[:digit:]]{1,5} [.*.]{3,3}') AND (resolution = 'DUPLICATE') ORDER" .
 
2748
                        " BY longdescs.bug_when");
 
2749
        $sth->execute();
 
2750
 
 
2751
        my %dupes;
 
2752
        my $key;
 
2753
 
 
2754
        # Because of the way hashes work, this loop removes all but the last dupe
 
2755
        # resolution found for a given bug.
 
2756
        while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) {
 
2757
                $dupes{$dupe} = $dupe_of;
 
2758
        }
 
2759
 
 
2760
        foreach $key (keys(%dupes))
 
2761
        {
 
2762
                $dupes{$key} =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms;
 
2763
                $dupes{$key} = $1;
 
2764
                $dbh->do("INSERT INTO duplicates VALUES('$dupes{$key}', '$key')");
 
2765
                #                                        BugItsADupeOf   Dupe
 
2766
        }
 
2767
}
 
2768
 
 
2769
# 2000-12-18.  Added an 'emailflags' field for storing preferences about
 
2770
# when email gets sent on a per-user basis.
 
2771
if (!GetFieldDef('profiles', 'emailflags')) {
 
2772
    AddField('profiles', 'emailflags', 'mediumtext');
 
2773
}
 
2774
 
 
2775
# 2000-11-27 For Bugzilla 2.5 and later. Change table 'comments' to 
 
2776
# 'longdescs' - the new name of the comments table.
 
2777
if (&TableExists('comments')) {
 
2778
    RenameField ('comments', 'when', 'bug_when');
 
2779
    ChangeFieldType('comments', 'bug_id', 'mediumint not null');
 
2780
    ChangeFieldType('comments', 'who', 'mediumint not null');
 
2781
    ChangeFieldType('comments', 'bug_when', 'datetime not null');
 
2782
    RenameField('comments','comment','thetext');
 
2783
    # Here we rename comments to longdescs
 
2784
    $dbh->do("DROP TABLE longdescs");
 
2785
    $dbh->do("ALTER TABLE comments RENAME longdescs");
 
2786
}
 
2787
 
 
2788
# 2001-04-08 Added a special directory for the duplicates stats.
 
2789
unless (-d 'data/duplicates') {
 
2790
    print "Creating duplicates directory...\n";
 
2791
    mkdir 'data/duplicates', 0770; 
 
2792
    if ($my_webservergroup eq "") {
 
2793
        chmod 01777, 'data/duplicates';
 
2794
    } 
 
2795
}
 
2796
 
 
2797
#
 
2798
# 2001-04-10 myk@mozilla.org:
 
2799
# isactive determines whether or not a group is active.  An inactive group
 
2800
# cannot have bugs added to it.  Deactivation is a much milder form of
 
2801
# deleting a group that allows users to continue to work on bugs in the group
 
2802
# without enabling them to extend the life of the group by adding bugs to it.
 
2803
# http://bugzilla.mozilla.org/show_bug.cgi?id=75482
 
2804
#
 
2805
AddField('groups', 'isactive', 'tinyint not null default 1');
 
2806
 
 
2807
#
 
2808
# 2001-06-15 myk@mozilla.org:
 
2809
# isobsolete determines whether or not an attachment is pertinent/relevant/valid.
 
2810
#
 
2811
AddField('attachments', 'isobsolete', 'tinyint not null default 0');
 
2812
 
 
2813
# 2001-04-29 jake@acutex.net - Remove oldemailtech
 
2814
#   http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
 
2815
if (-d 'shadow') {
 
2816
    print "Removing shadow directory...\n";
 
2817
    unlink glob("shadow/*");
 
2818
    unlink glob("shadow/.*");
 
2819
    rmdir "shadow";
 
2820
}
 
2821
DropField("profiles", "emailnotification");
 
2822
DropField("profiles", "newemailtech");
 
2823
 
 
2824
# 2001-06-12; myk@mozilla.org; bugs 74032, 77473:
 
2825
# Recrypt passwords using Perl &crypt instead of the mysql equivalent
 
2826
# and delete plaintext passwords from the database.
 
2827
if ( GetFieldDef('profiles', 'password') ) {
 
2828
    
 
2829
    print <<ENDTEXT;
 
2830
Your current installation of Bugzilla stores passwords in plaintext 
 
2831
in the database and uses mysql's encrypt function instead of Perl's 
 
2832
crypt function to crypt passwords.  Passwords are now going to be 
 
2833
re-crypted with the Perl function, and plaintext passwords will be 
 
2834
deleted from the database.  This could take a while if your  
 
2835
installation has many users. 
 
2836
ENDTEXT
 
2837
 
 
2838
    # Re-crypt everyone's password.
 
2839
    my $sth = $dbh->prepare("SELECT userid, password FROM profiles");
 
2840
    $sth->execute();
 
2841
 
 
2842
    my $i = 1;
 
2843
 
 
2844
    print "Fixing password #1... ";
 
2845
    while (my ($userid, $password) = $sth->fetchrow_array()) {
 
2846
        my $cryptpassword = $dbh->quote(Crypt($password));
 
2847
        $dbh->do("UPDATE profiles SET cryptpassword = $cryptpassword WHERE userid = $userid");
 
2848
        ++$i;
 
2849
        # Let the user know where we are at every 500 records.
 
2850
        print "$i... " if !($i%500);
 
2851
    }
 
2852
    print "$i... Done.\n";
 
2853
 
 
2854
    # Drop the plaintext password field and resize the cryptpassword field.
 
2855
    DropField('profiles', 'password');
 
2856
    ChangeFieldType('profiles', 'cryptpassword', 'varchar(34)');
 
2857
 
 
2858
}
 
2859
 
 
2860
#
 
2861
# 2001-06-06 justdave@syndicomm.com:
 
2862
# There was no index on the 'who' column in the long descriptions table.
 
2863
# This caused queries by who posted comments to take a LONG time.
 
2864
#   http://bugzilla.mozilla.org/show_bug.cgi?id=57350
 
2865
if (!defined GetIndexDef('longdescs','who')) {
 
2866
    print "Adding index for who column in longdescs table...\n";
 
2867
    $dbh->do('ALTER TABLE longdescs ADD INDEX (who)');
 
2868
}
 
2869
 
 
2870
# 2001-06-15 kiko@async.com.br - Change bug:version size to avoid
 
2871
# truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352
 
2872
ChangeFieldType('bugs', 'version','varchar(64) not null');
 
2873
 
 
2874
# 2001-07-20 jake@acutex.net - Change bugs_activity to only record changes
 
2875
#  http://bugzilla.mozilla.org/show_bug.cgi?id=55161
 
2876
if (GetFieldDef('bugs_activity', 'oldvalue')) {
 
2877
    AddField("bugs_activity", "removed", "tinytext");
 
2878
    AddField("bugs_activity", "added", "tinytext");
 
2879
 
 
2880
    # Need to get fieldid's for the fields that have multipule values
 
2881
    my @multi = ();
 
2882
    foreach my $f ("cc", "dependson", "blocked", "keywords") {
 
2883
        my $sth = $dbh->prepare("SELECT fieldid FROM fielddefs WHERE name = '$f'");
 
2884
        $sth->execute();
 
2885
        my ($fid) = $sth->fetchrow_array();
 
2886
        push (@multi, $fid);
 
2887
    } 
 
2888
 
 
2889
    # Now we need to process the bugs_activity table and reformat the data
 
2890
    my $i = 0;
 
2891
    print "Fixing activity log ";
 
2892
    my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid,
 
2893
                            oldvalue, newvalue FROM bugs_activity");
 
2894
    $sth->execute;
 
2895
    while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) = $sth->fetchrow_array()) {
 
2896
        # print the iteration count every 500 records so the user knows we didn't die
 
2897
        print "$i..." if !($i++ % 500); 
 
2898
        # Make sure (old|new)value isn't null (to suppress warnings)
 
2899
        $oldvalue ||= "";
 
2900
        $newvalue ||= "";
 
2901
        my ($added, $removed) = "";
 
2902
        if (grep ($_ eq $fieldid, @multi)) {
 
2903
            $oldvalue =~ s/[\s,]+/ /g;
 
2904
            $newvalue =~ s/[\s,]+/ /g;
 
2905
            my @old = split(" ", $oldvalue);
 
2906
            my @new = split(" ", $newvalue);
 
2907
            my (@add, @remove) = ();
 
2908
            # Find values that were "added"
 
2909
            foreach my $value(@new) {
 
2910
                if (! grep ($_ eq $value, @old)) {
 
2911
                    push (@add, $value);
 
2912
                }
 
2913
            }
 
2914
            # Find values that were removed
 
2915
            foreach my $value(@old) {
 
2916
                if (! grep ($_ eq $value, @new)) {
 
2917
                    push (@remove, $value);
 
2918
                }
 
2919
            }
 
2920
            $added = join (", ", @add);
 
2921
            $removed = join (", ", @remove);
 
2922
            # If we can't determine what changed, put a ? in both fields
 
2923
            unless ($added || $removed) {
 
2924
                $added = "?";
 
2925
                $removed = "?";
 
2926
            }
 
2927
            # If the origianl field (old|new)value was full, then this
 
2928
            # could be incomplete data.
 
2929
            if (length($oldvalue) == 255 || length($newvalue) == 255) {
 
2930
                $added = "? $added";
 
2931
                $removed = "? $removed";
 
2932
            }
 
2933
        } else {
 
2934
            $removed = $oldvalue;
 
2935
            $added = $newvalue;
 
2936
        }
 
2937
        $added = $dbh->quote($added);
 
2938
        $removed = $dbh->quote($removed);
 
2939
        $dbh->do("UPDATE bugs_activity SET removed = $removed, added = $added
 
2940
                  WHERE bug_id = $bug_id AND who = $who
 
2941
                   AND bug_when = '$bug_when' AND fieldid = $fieldid");
 
2942
    }
 
2943
    print ". Done.\n";
 
2944
    DropField("bugs_activity", "oldvalue");
 
2945
    DropField("bugs_activity", "newvalue");
 
2946
 
2947
 
 
2948
# 2001-07-24 jake@acutex.net - disabledtext was being handled inconsitantly
 
2949
# http://bugzilla.mozilla.org/show_bug.cgi?id=90933
 
2950
ChangeFieldType("profiles", "disabledtext", "mediumtext not null");
 
2951
 
 
2952
# 2001-07-26 myk@mozilla.org bug39816: 
 
2953
# Add fields to the bugs table that record whether or not the reporter,
 
2954
# assignee, QA contact, and users on the cc: list can see bugs even when
 
2955
# they are not members of groups to which the bugs are restricted.
 
2956
# 2002-02-06 bbaetz@student.usyd.edu.au - assignee/qa can always see the bug
 
2957
AddField("bugs", "reporter_accessible", "tinyint not null default 1");
 
2958
#AddField("bugs", "assignee_accessible", "tinyint not null default 1");
 
2959
#AddField("bugs", "qacontact_accessible", "tinyint not null default 1");
 
2960
AddField("bugs", "cclist_accessible", "tinyint not null default 1");
 
2961
 
 
2962
# 2001-08-21 myk@mozilla.org bug84338:
 
2963
# Add a field for the attachment ID to the bugs_activity table, so installations
 
2964
# using the attachment manager can record changes to attachments.
 
2965
AddField("bugs_activity", "attach_id", "mediumint null");
 
2966
 
 
2967
# 2002-02-04 bbaetz@student.usyd.edu.au bug 95732
 
2968
# Remove logincookies.cryptpassword, and delete entries which become
 
2969
# invalid
 
2970
if (GetFieldDef("logincookies", "cryptpassword")) {
 
2971
    # We need to delete any cookies which are invalid, before dropping the
 
2972
    # column
 
2973
 
 
2974
    print "Removing invalid login cookies...\n";
 
2975
 
 
2976
    # mysql doesn't support DELETE with multi-table queries, so we have
 
2977
    # to iterate
 
2978
    my $sth = $dbh->prepare("SELECT cookie FROM logincookies, profiles " .
 
2979
                            "WHERE logincookies.cryptpassword != " .
 
2980
                            "profiles.cryptpassword AND " .
 
2981
                            "logincookies.userid = profiles.userid");
 
2982
    $sth->execute();
 
2983
    while (my ($cookie) = $sth->fetchrow_array()) {
 
2984
        $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie");
 
2985
    }
 
2986
 
 
2987
    DropField("logincookies", "cryptpassword");
 
2988
}
 
2989
 
 
2990
# 2002-02-13 bbaetz@student.usyd.edu.au - bug 97471
 
2991
# qacontact/assignee should always be able to see bugs,
 
2992
# so remove their restriction column
 
2993
if (GetFieldDef("bugs","qacontact_accessible")) {
 
2994
    print "Removing restrictions on bugs for assignee and qacontact...\n";
 
2995
 
 
2996
    DropField("bugs", "qacontact_accessible");
 
2997
    DropField("bugs", "assignee_accessible");
 
2998
}
 
2999
 
 
3000
# 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466
 
3001
# 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the
 
3002
#  BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release
 
3003
# Use the ip, not the hostname, in the logincookies table
 
3004
if (GetFieldDef("logincookies", "hostname")) {
 
3005
    # We've changed what we match against, so all entries are now invalid
 
3006
    $dbh->do("DELETE FROM logincookies");
 
3007
 
 
3008
    # Now update the logincookies schema
 
3009
    DropField("logincookies", "hostname");
 
3010
    AddField("logincookies", "ipaddr", "varchar(40) NOT NULL");
 
3011
}
 
3012
 
 
3013
# If you had to change the --TABLE-- definition in any way, then add your
 
3014
# differential change code *** A B O V E *** this comment.
 
3015
#
 
3016
# That is: if you add a new field, you first search for the first occurence
 
3017
# of --TABLE-- and add your field to into the table hash. This new setting
 
3018
# would be honored for every new installation. Then add your
 
3019
# AddField/DropField/ChangeFieldType/RenameField code above. This would then
 
3020
# be honored by everyone who updates his Bugzilla installation.
 
3021
#
 
3022
#
 
3023
# Final checks...
 
3024
 
 
3025
unlink "data/versioncache";
 
3026
 
 
3027
print "Reminder: Bugzilla now requires version 8.7 or later of sendmail.\n";