Requirements.pm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. # -*- Mode: perl; indent-tabs-mode: nil -*-
  2. #
  3. # The contents of this file are subject to the Mozilla Public
  4. # License Version 1.1 (the "License"); you may not use this file
  5. # except in compliance with the License. You may obtain a copy of
  6. # the License at http://www.mozilla.org/MPL/
  7. #
  8. # Software distributed under the License is distributed on an "AS
  9. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10. # implied. See the License for the specific language governing
  11. # rights and limitations under the License.
  12. #
  13. # The Original Code is the Bugzilla Bug Tracking System.
  14. #
  15. # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
  16. # Marc Schumann <wurblzap@gmail.com>
  17. package Bugzilla::Install::Requirements;
  18. # NOTE: This package MUST NOT "use" any Bugzilla modules other than
  19. # Bugzilla::Constants, anywhere. We may "use" standard perl modules.
  20. #
  21. # Subroutines may "require" and "import" from modules, but they
  22. # MUST NOT "use."
  23. use strict;
  24. use Bugzilla::Install::Util qw(vers_cmp install_string);
  25. use List::Util qw(max);
  26. use Safe;
  27. use base qw(Exporter);
  28. our @EXPORT = qw(
  29. REQUIRED_MODULES
  30. OPTIONAL_MODULES
  31. check_requirements
  32. check_graphviz
  33. have_vers
  34. install_command
  35. );
  36. use Bugzilla::Constants;
  37. # The below two constants are subroutines so that they can implement
  38. # a hook. Other than that they are actually constants.
  39. # "package" is the perl package we're checking for. "module" is the name
  40. # of the actual module we load with "require" to see if the package is
  41. # installed or not. "version" is the version we need, or 0 if we'll accept
  42. # any version.
  43. #
  44. # "blacklist" is an arrayref of regular expressions that describe versions that
  45. # are 'blacklisted'--that is, even if the version is high enough, Bugzilla
  46. # will refuse to say that it's OK to run with that version.
  47. sub REQUIRED_MODULES {
  48. my $perl_ver = sprintf('%vd', $^V);
  49. my @modules = (
  50. {
  51. package => 'CGI.pm',
  52. module => 'CGI',
  53. # Perl 5.10 requires CGI 3.33 due to a taint issue when
  54. # uploading attachments, see bug 416382.
  55. # Require CGI 3.21 for -httponly support, see bug 368502.
  56. version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
  57. },
  58. {
  59. package => 'TimeDate',
  60. module => 'Date::Format',
  61. version => '2.21'
  62. },
  63. {
  64. package => 'PathTools',
  65. module => 'File::Spec',
  66. version => '0.84'
  67. },
  68. {
  69. package => 'DBI',
  70. module => 'DBI',
  71. version => '1.41'
  72. },
  73. {
  74. package => 'Template-Toolkit',
  75. module => 'Template',
  76. version => '2.15'
  77. },
  78. {
  79. package => 'Email-Send',
  80. module => 'Email::Send',
  81. version => ON_WINDOWS ? '2.16' : '2.00'
  82. },
  83. {
  84. package => 'Email-MIME',
  85. module => 'Email::MIME',
  86. version => '1.861'
  87. },
  88. {
  89. package => 'Email-MIME-Modifier',
  90. module => 'Email::MIME::Modifier',
  91. version => '1.442'
  92. },
  93. );
  94. my $all_modules = _get_extension_requirements(
  95. 'REQUIRED_MODULES', \@modules);
  96. return $all_modules;
  97. };
  98. sub OPTIONAL_MODULES {
  99. my @modules = (
  100. {
  101. package => 'GD',
  102. module => 'GD',
  103. version => '1.20',
  104. feature => 'Graphical Reports, New Charts, Old Charts'
  105. },
  106. {
  107. package => 'Chart',
  108. module => 'Chart::Base',
  109. version => '1.0',
  110. feature => 'New Charts, Old Charts'
  111. },
  112. {
  113. package => 'Template-GD',
  114. # This module tells us whether or not Template-GD is installed
  115. # on Template-Toolkits after 2.14, and still works with 2.14 and lower.
  116. module => 'Template::Plugin::GD::Image',
  117. version => 0,
  118. feature => 'Graphical Reports'
  119. },
  120. {
  121. package => 'GDTextUtil',
  122. module => 'GD::Text',
  123. version => 0,
  124. feature => 'Graphical Reports'
  125. },
  126. {
  127. package => 'GDGraph',
  128. module => 'GD::Graph',
  129. version => 0,
  130. feature => 'Graphical Reports'
  131. },
  132. {
  133. package => 'XML-Twig',
  134. module => 'XML::Twig',
  135. version => 0,
  136. feature => 'Move Bugs Between Installations'
  137. },
  138. {
  139. package => 'MIME-tools',
  140. # MIME::Parser is packaged as MIME::Tools on ActiveState Perl
  141. module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
  142. version => '5.406',
  143. feature => 'Move Bugs Between Installations'
  144. },
  145. {
  146. package => 'libwww-perl',
  147. module => 'LWP::UserAgent',
  148. version => 0,
  149. feature => 'Automatic Update Notifications'
  150. },
  151. {
  152. package => 'PatchReader',
  153. module => 'PatchReader',
  154. version => '0.9.4',
  155. feature => 'Patch Viewer'
  156. },
  157. {
  158. package => 'PerlMagick',
  159. module => 'Image::Magick',
  160. version => 0,
  161. feature => 'Optionally Convert BMP Attachments to PNGs'
  162. },
  163. {
  164. package => 'perl-ldap',
  165. module => 'Net::LDAP',
  166. version => 0,
  167. feature => 'LDAP Authentication'
  168. },
  169. {
  170. package => 'Authen-SASL',
  171. module => 'Authen::SASL',
  172. version => 0,
  173. feature => 'SMTP Authentication'
  174. },
  175. {
  176. package => 'RadiusPerl',
  177. module => 'Authen::Radius',
  178. version => 0,
  179. feature => 'RADIUS Authentication'
  180. },
  181. {
  182. package => 'SOAP-Lite',
  183. module => 'SOAP::Lite',
  184. version => 0,
  185. # These versions (0.70 -> 0.710.05) are affected by bug 468009
  186. blacklist => ['^0\.70', '^0\.710?\.0[1-5]$'],
  187. feature => 'XML-RPC Interface'
  188. },
  189. {
  190. # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
  191. package => 'HTML-Parser',
  192. module => 'HTML::Parser',
  193. version => '3.40',
  194. feature => 'More HTML in Product/Group Descriptions'
  195. },
  196. {
  197. package => 'HTML-Scrubber',
  198. module => 'HTML::Scrubber',
  199. version => 0,
  200. feature => 'More HTML in Product/Group Descriptions'
  201. },
  202. # Inbound Email
  203. {
  204. package => 'Email-MIME-Attachment-Stripper',
  205. module => 'Email::MIME::Attachment::Stripper',
  206. version => 0,
  207. feature => 'Inbound Email'
  208. },
  209. {
  210. package => 'Email-Reply',
  211. module => 'Email::Reply',
  212. version => 0,
  213. feature => 'Inbound Email'
  214. },
  215. # mod_perl
  216. {
  217. package => 'mod_perl',
  218. module => 'mod_perl2',
  219. version => '1.999022',
  220. feature => 'mod_perl'
  221. },
  222. {
  223. package => 'Math-Random-Secure',
  224. module => 'Math::Random::Secure',
  225. version => '0.05',
  226. feature => 'Improve cookie and token security',
  227. },
  228. );
  229. my $all_modules = _get_extension_requirements(
  230. 'OPTIONAL_MODULES', \@modules);
  231. return $all_modules;
  232. };
  233. # This implements the install-requirements hook described in Bugzilla::Hook.
  234. sub _get_extension_requirements {
  235. my ($function, $base_modules) = @_;
  236. my @all_modules;
  237. # get a list of all extensions
  238. my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
  239. foreach my $extension (@extensions) {
  240. my $file = "$extension/code/install-requirements.pl";
  241. if (-e $file) {
  242. my $safe = new Safe;
  243. # This is a very liberal Safe.
  244. $safe->permit(qw(:browse require entereval caller));
  245. $safe->rdo($file);
  246. if ($@) {
  247. warn $@;
  248. next;
  249. }
  250. my $modules = eval { &{$safe->varglob($function)}($base_modules) };
  251. next unless $modules;
  252. push(@all_modules, @$modules);
  253. }
  254. }
  255. unshift(@all_modules, @$base_modules);
  256. return \@all_modules;
  257. };
  258. sub check_requirements {
  259. my ($output) = @_;
  260. print "\n", install_string('checking_modules'), "\n" if $output;
  261. my $root = ROOT_USER;
  262. my $missing = _check_missing(REQUIRED_MODULES, $output);
  263. print "\n", install_string('checking_dbd'), "\n" if $output;
  264. my $have_one_dbd = 0;
  265. my $db_modules = DB_MODULE;
  266. foreach my $db (keys %$db_modules) {
  267. my $dbd = $db_modules->{$db}->{dbd};
  268. $have_one_dbd = 1 if have_vers($dbd, $output);
  269. }
  270. print "\n", install_string('checking_optional'), "\n" if $output;
  271. my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
  272. # If we're running on Windows, reset the input line terminator so that
  273. # console input works properly - loading CGI tends to mess it up
  274. $/ = "\015\012" if ON_WINDOWS;
  275. my $pass = !scalar(@$missing) && $have_one_dbd;
  276. return {
  277. pass => $pass,
  278. one_dbd => $have_one_dbd,
  279. missing => $missing,
  280. optional => $missing_optional,
  281. any_missing => !$pass || scalar(@$missing_optional),
  282. };
  283. }
  284. # A helper for check_requirements
  285. sub _check_missing {
  286. my ($modules, $output) = @_;
  287. my @missing;
  288. foreach my $module (@$modules) {
  289. unless (have_vers($module, $output)) {
  290. push(@missing, $module);
  291. }
  292. }
  293. return \@missing;
  294. }
  295. # Returns the build ID of ActivePerl. If several versions of
  296. # ActivePerl are installed, it won't be able to know which one
  297. # you are currently running. But that's our best guess.
  298. sub _get_activestate_build_id {
  299. eval 'use Win32::TieRegistry';
  300. return 0 if $@;
  301. my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
  302. or return 0;
  303. return $key->GetValue("CurrentVersion");
  304. }
  305. sub print_module_instructions {
  306. my ($check_results, $output) = @_;
  307. # We only print these notes if we have to.
  308. if ((!$output && @{$check_results->{missing}})
  309. || ($output && $check_results->{any_missing}))
  310. {
  311. if (ON_WINDOWS) {
  312. print "\n* NOTE: You must run any commands listed below as "
  313. . ROOT_USER . ".\n\n";
  314. my $perl_ver = sprintf('%vd', $^V);
  315. # URL when running Perl 5.8.x.
  316. my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
  317. my $repo_up_cmd =
  318. '* *';
  319. # Packages for Perl 5.10 are not compatible with Perl 5.8.
  320. if (vers_cmp($perl_ver, '5.10') > -1) {
  321. $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
  322. }
  323. # ActivePerl older than revision 819 require an additional command.
  324. if (_get_activestate_build_id() < 819) {
  325. $repo_up_cmd = <<EOT;
  326. * *
  327. * Then you have to do (also as an Administrator): *
  328. * *
  329. * ppm repo up theory58S *
  330. * *
  331. * Do that last command over and over until you see "theory58S" at the *
  332. * top of the displayed list. *
  333. EOT
  334. }
  335. print <<EOT;
  336. ***********************************************************************
  337. * Note For Windows Users *
  338. ***********************************************************************
  339. * In order to install the modules listed below, you first have to run *
  340. * the following command as an Administrator: *
  341. * *
  342. * ppm repo add theory58S $url_to_theory58S
  343. $repo_up_cmd
  344. ***********************************************************************
  345. EOT
  346. }
  347. }
  348. # Required Modules
  349. if (my @missing = @{$check_results->{missing}}) {
  350. print <<EOT;
  351. ***********************************************************************
  352. * REQUIRED MODULES *
  353. ***********************************************************************
  354. * Bugzilla requires you to install some Perl modules which are either *
  355. * missing from your system, or the version on your system is too old. *
  356. * *
  357. * The latest versions of each module can be installed by running the *
  358. * commands below. *
  359. ***********************************************************************
  360. EOT
  361. print "COMMANDS:\n\n";
  362. foreach my $package (@missing) {
  363. my $command = install_command($package);
  364. print " $command\n";
  365. }
  366. print "\n";
  367. }
  368. if (!$check_results->{one_dbd}) {
  369. print <<EOT;
  370. ***********************************************************************
  371. * DATABASE ACCESS *
  372. ***********************************************************************
  373. * In order to access your database, Bugzilla requires that the *
  374. * correct "DBD" module be installed for the database that you are *
  375. * running. *
  376. * *
  377. * Pick and run the correct command below for the database that you *
  378. * plan to use with Bugzilla. *
  379. ***********************************************************************
  380. COMMANDS:
  381. EOT
  382. my %db_modules = %{DB_MODULE()};
  383. foreach my $db (keys %db_modules) {
  384. my $command = install_command($db_modules{$db}->{dbd});
  385. printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
  386. print ' ' x 12 . "Minimum version required: "
  387. . $db_modules{$db}->{dbd}->{version} . "\n";
  388. }
  389. print "\n";
  390. }
  391. return unless $output;
  392. if (my @missing = @{$check_results->{optional}}) {
  393. print <<EOT;
  394. **********************************************************************
  395. * OPTIONAL MODULES *
  396. **********************************************************************
  397. * Certain Perl modules are not required by Bugzilla, but by *
  398. * installing the latest version you gain access to additional *
  399. * features. *
  400. * *
  401. * The optional modules you do not have installed are listed below, *
  402. * with the name of the feature they enable. If you want to install *
  403. * one of these modules, just run the appropriate command in the *
  404. * "COMMANDS TO INSTALL" section. *
  405. **********************************************************************
  406. EOT
  407. # Now we have to determine how large the table cols will be.
  408. my $longest_name = max(map(length($_->{package}), @missing));
  409. # The first column header is at least 11 characters long.
  410. $longest_name = 11 if $longest_name < 11;
  411. # The table is 71 characters long. There are seven mandatory
  412. # characters (* and space) in the string. So, we have a total
  413. # of 64 characters to work with.
  414. my $remaining_space = 64 - $longest_name;
  415. print '*' x 71 . "\n";
  416. printf "* \%${longest_name}s * %-${remaining_space}s *\n",
  417. 'MODULE NAME', 'ENABLES FEATURE(S)';
  418. print '*' x 71 . "\n";
  419. foreach my $package (@missing) {
  420. printf "* \%${longest_name}s * %-${remaining_space}s *\n",
  421. $package->{package}, $package->{feature};
  422. }
  423. print '*' x 71 . "\n";
  424. print "COMMANDS TO INSTALL:\n\n";
  425. foreach my $module (@missing) {
  426. my $command = install_command($module);
  427. printf "%15s: $command\n", $module->{package};
  428. }
  429. }
  430. if ($output && $check_results->{any_missing} && !ON_WINDOWS) {
  431. print install_string('install_all', { perl => $^X });
  432. }
  433. }
  434. sub check_graphviz {
  435. my ($output) = @_;
  436. return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
  437. printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;
  438. my $return = 0;
  439. if(-x Bugzilla->params->{'webdotbase'}) {
  440. print "ok: found\n" if $output;
  441. $return = 1;
  442. } else {
  443. print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
  444. }
  445. my $webdotdir = bz_locations()->{'webdotdir'};
  446. # Check .htaccess allows access to generated images
  447. if (-e "$webdotdir/.htaccess") {
  448. my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
  449. || die "$webdotdir/.htaccess: " . $!;
  450. if (!grep(/png/, $htaccess->getlines)) {
  451. print "Dependency graph images are not accessible.\n";
  452. print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
  453. }
  454. $htaccess->close;
  455. }
  456. return $return;
  457. }
  458. # This was originally clipped from the libnet Makefile.PL, adapted here to
  459. # use the below vers_cmp routine for accurate version checking.
  460. sub have_vers {
  461. my ($params, $output) = @_;
  462. my $module = $params->{module};
  463. my $package = $params->{package};
  464. if (!$package) {
  465. $package = $module;
  466. $package =~ s/::/-/g;
  467. }
  468. my $wanted = $params->{version};
  469. eval "require $module;";
  470. # VERSION is provided by UNIVERSAL::
  471. my $vnum = eval { $module->VERSION } || -1;
  472. # CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
  473. # That breaks the standard version tests, so we need to manually correct
  474. # the version
  475. if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
  476. $vnum = $1 . "." . $2;
  477. }
  478. my $vstr;
  479. if ($vnum eq "-1") { # string compare just in case it's non-numeric
  480. $vstr = install_string('module_not_found');
  481. }
  482. elsif (vers_cmp($vnum,"0") > -1) {
  483. $vstr = install_string('module_found', { ver => $vnum });
  484. }
  485. else {
  486. $vstr = install_string('module_unknown_version');
  487. }
  488. my $vok = (vers_cmp($vnum,$wanted) > -1);
  489. my $blacklisted;
  490. if ($vok && $params->{blacklist}) {
  491. $blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
  492. $vok = 0 if $blacklisted;
  493. }
  494. if ($output) {
  495. my $ok = $vok ? install_string('module_ok') : '';
  496. my $black_string = $blacklisted ? install_string('blacklisted') : '';
  497. my $want_string = $wanted ? "v$wanted" : install_string('any');
  498. $ok = "$ok:" if $ok;
  499. printf "%s %19s %-9s $ok $vstr $black_string\n",
  500. install_string('checking_for'), $package, "($want_string)";
  501. }
  502. return $vok ? 1 : 0;
  503. }
  504. sub install_command {
  505. my $module = shift;
  506. my ($command, $package);
  507. if (ON_WINDOWS) {
  508. $command = 'ppm install %s';
  509. $package = $module->{package};
  510. }
  511. else {
  512. $command = "$^X install-module.pl \%s";
  513. # Non-Windows installations need to use module names, because
  514. # CPAN doesn't understand package names.
  515. $package = $module->{module};
  516. }
  517. return sprintf $command, $package;
  518. }
  519. 1;
  520. __END__
  521. =head1 NAME
  522. Bugzilla::Install::Requirements - Functions and variables dealing
  523. with Bugzilla's perl-module requirements.
  524. =head1 DESCRIPTION
  525. This module is used primarily by C<checksetup.pl> to determine whether
  526. or not all of Bugzilla's prerequisites are installed. (That is, all the
  527. perl modules it requires.)
  528. =head1 CONSTANTS
  529. =over 4
  530. =item C<REQUIRED_MODULES>
  531. An arrayref of hashrefs that describes the perl modules required by
  532. Bugzilla. The hashes have two keys, C<name> and C<version>, which
  533. represent the name of the module and the version that we require.
  534. =back
  535. =head1 SUBROUTINES
  536. =over 4
  537. =item C<check_requirements>
  538. =over
  539. =item B<Description>
  540. This checks what optional or required perl modules are installed, like
  541. C<checksetup.pl> does.
  542. =item B<Params>
  543. =over
  544. =item C<$output> - C<true> if you want the function to print out information
  545. about what it's doing, and the versions of everything installed.
  546. =back
  547. =item B<Returns>
  548. A hashref containing these values:
  549. =over
  550. =item C<pass> - Whether or not we have all the mandatory requirements.
  551. =item C<missing> - An arrayref containing any required modules that
  552. are not installed or that are not up-to-date. Each item in the array is
  553. a hashref in the format of items from L</REQUIRED_MODULES>.
  554. =item C<optional> - The same as C<missing>, but for optional modules.
  555. =item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
  556. =item C<any_missing> - True if there are any missing modules, even optional
  557. modules.
  558. =back
  559. =back
  560. =item C<check_graphviz($output)>
  561. Description: Checks if the graphviz binary specified in the
  562. C<webdotbase> parameter is a valid binary, or a valid URL.
  563. Params: C<$output> - C<$true> if you want the function to
  564. print out information about what it's doing.
  565. Returns: C<1> if the check was successful, C<0> otherwise.
  566. =item C<have_vers($module, $output)>
  567. Description: Tells you whether or not you have the appropriate
  568. version of the module requested. It also prints
  569. out a message to the user explaining the check
  570. and the result.
  571. Params: C<$module> - A hashref, in the format of an item from
  572. L</REQUIRED_MODULES>.
  573. C<$output> - Set to true if you want this function to
  574. print information to STDOUT about what it's
  575. doing.
  576. Returns: C<1> if you have the module installed and you have the
  577. appropriate version. C<0> otherwise.
  578. =item C<install_command($module)>
  579. Description: Prints out the appropriate command to install the
  580. module specified, depending on whether you're
  581. on Windows or Linux.
  582. Params: C<$module> - A hashref, in the format of an item from
  583. L</REQUIRED_MODULES>.
  584. Returns: nothing
  585. =back