use DateTime::Duration to correctly convert partial dates in to fields
[BackupPC.git] / configure.pl
1 #!/usr/bin/env perl
2 #============================================================= -*-perl-*-
3 #
4 # configure.pl: Configuration and installation program for BackupPC
5 #
6 # DESCRIPTION
7 #
8 #   This script should be run as root:
9 #
10 #        perl configure.pl
11 #
12 #   To read about the command-line options for this configure script:
13 #
14 #        perldoc configure.pl
15 #
16 #   The installation steps are described as the script runs.
17 #
18 # AUTHOR
19 #   Craig Barratt <cbarratt@users.sourceforge.net>
20 #
21 # COPYRIGHT
22 #   Copyright (C) 2001-2010  Craig Barratt
23 #
24 #   This program is free software; you can redistribute it and/or modify
25 #   it under the terms of the GNU General Public License as published by
26 #   the Free Software Foundation; either version 2 of the License, or
27 #   (at your option) any later version.
28 #
29 #   This program is distributed in the hope that it will be useful,
30 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
31 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32 #   GNU General Public License for more details.
33 #
34 #   You should have received a copy of the GNU General Public License
35 #   along with this program; if not, write to the Free Software
36 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
37 #
38 #========================================================================
39 #
40 # Version 3.1.0beta0, released 3 Sep 2007.
41 #
42 # See http://backuppc.sourceforge.net.
43 #
44 #========================================================================
45
46 use strict;
47 no  utf8;
48 use vars qw(%Conf %OrigConf);
49 use lib "./lib";
50 use Encode;
51
52 my $EncodeVersion = eval($Encode::VERSION);
53 if ( $EncodeVersion < 1.99 ) {
54     print("Error: you need to upgrade perl's Encode package.\n"
55         . "I found $EncodeVersion and BackupPC needs >= 1.99\n"
56         . "Please go to www.cpan.org or use the cpan command.\n");
57     exit(1);
58 }
59
60 my @Packages = qw(File::Path File::Spec File::Copy DirHandle Digest::MD5
61                   Data::Dumper Getopt::Std Getopt::Long Pod::Usage
62                   BackupPC::Lib BackupPC::FileZIO);
63
64 foreach my $pkg ( @Packages ) {
65     eval "use $pkg";
66     next if ( !$@ );
67     if ( $pkg =~ /BackupPC/ ) {
68         die <<EOF;
69
70 Error loading $pkg: $@
71 BackupPC cannot load the package $pkg, which is included in the
72 BackupPC distribution.  This probably means you did not cd to the
73 unpacked BackupPC distribution before running configure.pl, eg:
74
75     cd BackupPC-__VERSION__
76     ./configure.pl
77
78 Please try again.
79
80 EOF
81     }
82     die <<EOF;
83
84 BackupPC needs the package $pkg.  Please install $pkg
85 before installing BackupPC.
86
87 EOF
88 }
89
90 my %opts;
91 $opts{"set-perms"} = 1;
92 if ( !GetOptions(
93             \%opts,
94             "batch",
95             "backuppc-user=s",
96             "bin-path=s%",
97             "cgi-dir=s",
98             "compress-level=i",
99             "config-path=s",
100             "config-override=s%",
101             "config-dir=s",
102             "data-dir=s",
103             "dest-dir=s",
104             "fhs!",
105             "help|?",
106             "hostname=s",
107             "html-dir=s",
108             "html-dir-url=s",
109             "install-dir=s",
110             "log-dir=s",
111             "man",
112             "set-perms!",
113             "uid-ignore!",
114         ) || @ARGV ) {
115     pod2usage(2);
116 }
117 pod2usage(1) if ( $opts{help} );
118 pod2usage(-exitstatus => 0, -verbose => 2) if $opts{man};
119
120 my $DestDir = $opts{"dest-dir"};
121 $DestDir = "" if ( $DestDir eq "/" );
122
123 if ( !$opts{"uid-ignore"} && $< != 0 ) {
124     print <<EOF;
125
126 This configure script should be run as root, rather than uid $<.
127 Provided uid $< has sufficient permissions to create the data and
128 install directories, then it should be ok to proceed.  Otherwise,
129 please quit and restart as root.
130
131 EOF
132     exit(1) if ( prompt("--> Do you want to continue?",
133                        "y") !~ /y/i );
134     exit(1) if ( $opts{batch} && !$opts{"uid-ignore"} );
135 }
136
137 #
138 # Whether we use the file system hierarchy conventions or not.
139 # Older versions did not.  BackupPC used to be installed in
140 # two main directories (in addition to CGI and html pages)
141 #
142 #    TopDir       which includes subdirs conf, log, pc, pool, cpool
143 #                
144 #    InstallDir   which includes subdirs bin, lib, doc
145 #
146 # With FHS enabled (which is the default for new installations)
147 # the config files move to /etc/BackupPC and log files to /var/log:
148 #
149 #    /etc/BackupPC/config.pl  main config file (was $TopDir/conf/config.pl)
150 #    /etc/BackupPC/hosts      hosts file (was $TopDir/conf/hosts)
151 #    /etc/BackupPC/pc/HOST.pl per-pc config file (was $TopDir/pc/HOST/config.pl)
152 #    /var/log/BackupPC        log files (was $TopDir/log)
153 #    /var/log/BackupPC        Pid, status and email info (was $TopDir/log)
154 #
155
156 #
157 # Check if this is an upgrade, in which case read the existing
158 # config file to get all the defaults.
159 #
160 my $ConfigPath = "";
161 my $ConfigFileOK = 1;
162 while ( 1 ) {
163     if ( $ConfigFileOK && -f "/etc/BackupPC/config.pl" ) {
164         $ConfigPath = "/etc/BackupPC/config.pl";
165         $opts{fhs} = 1 if ( !defined($opts{fhs}) );
166         print <<EOF;
167
168 Found /etc/BackupPC/config.pl, so this is an upgrade of an
169 existing BackupPC installation.  We will verify some existing
170 information, but you will probably not need to make any
171 changes - just hit ENTER to each question.
172 EOF
173     } else {
174         print <<EOF;
175
176 Is this a new installation or upgrade for BackupPC?  If this is
177 an upgrade please tell me the full path of the existing BackupPC
178 configuration file (eg: /etc/BackupPC/config.pl).  Otherwise, just
179 hit return.
180
181 EOF
182         $ConfigPath = prompt("--> Full path to existing main config.pl",
183                              $ConfigPath,
184                              "config-path");
185     }
186     last if ( $ConfigPath eq ""
187             || ($ConfigPath =~ /^\// && -f $ConfigPath && -w $ConfigPath) );
188     my $problem = "is not an absolute path";
189     $problem = "is not writable"        if ( !-w $ConfigPath );
190     $problem = "is not readable"        if ( !-r $ConfigPath );
191     $problem = "is not a regular file"  if ( !-f $ConfigPath );
192     $problem = "doesn't exist"          if ( !-e $ConfigPath );
193     print("The file '$ConfigPath' $problem.\n");
194     if ( $opts{batch} ) {
195         print("Need to specify a valid --config-path for upgrade\n");
196         exit(1);
197     }
198     $ConfigFileOK = 0;
199 }
200 $opts{fhs} = 1 if ( !defined($opts{fhs}) && $ConfigPath eq "" );
201 $opts{fhs} = 0 if ( !defined($opts{fhs}) );
202
203 my $bpc;
204 if ( $ConfigPath ne "" && -r $ConfigPath ) {
205     (my $confDir = $ConfigPath) =~ s{/[^/]+$}{};
206     die("BackupPC::Lib->new failed\n")
207             if ( !($bpc = BackupPC::Lib->new(".", ".", $confDir, 1)) );
208     %Conf = $bpc->Conf();
209     %OrigConf = %Conf;
210     if ( !$opts{fhs} ) {
211         ($Conf{TopDir} = $ConfigPath) =~ s{/[^/]+/[^/]+$}{}
212                     if ( $Conf{TopDir} eq '' );
213         $bpc->{LogDir} = $Conf{LogDir}  = "$Conf{TopDir}/log"
214                     if ( $Conf{LogDir} eq '' );
215     }
216     $bpc->{ConfDir} = $Conf{ConfDir} = $confDir;
217     my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}, 1);
218     if ( $err eq "" ) {
219         print <<EOF;
220
221 BackupPC is running on $Conf{ServerHost}.  You need to stop BackupPC
222 before you can upgrade the code.  Depending upon your installation,
223 you could run "/etc/init.d/backuppc stop".
224
225 EOF
226         exit(1);
227     }
228 }
229
230 #
231 # Create defaults for FHS setup
232 #
233 if ( $opts{fhs} ) {
234     $Conf{TopDir}       ||= $opts{"data-dir"}    || "/data/BackupPC";
235     $Conf{ConfDir}      ||= $opts{"config-dir"}  || "/etc/BackupPC";
236     $Conf{InstallDir}   ||= $opts{"install-dir"} || "/usr/local/BackupPC";
237     $Conf{LogDir}       ||= $opts{"log-dir"}     || "/var/log/BackupPC";
238 } else {
239     $Conf{TopDir}       ||= $opts{"data-dir"}    || "/data/BackupPC";
240     $Conf{ConfDir}      ||= $opts{"config-dir"}  || "$Conf{TopDir}/conf";
241     $Conf{InstallDir}   ||= $opts{"install-dir"} || "/usr/local/BackupPC";
242     $Conf{LogDir}       ||= $opts{"log-dir"}     || "$Conf{TopDir}/log";
243 }
244
245 #
246 # These are the programs whose paths we need to find
247 #
248 my %Programs = (
249     perl           => "PerlPath",
250     'gtar/tar'     => "TarClientPath",
251     smbclient      => "SmbClientPath",
252     nmblookup      => "NmbLookupPath",
253     rsync          => "RsyncClientPath",
254     ping           => "PingPath",
255     df             => "DfPath",
256     'ssh/ssh2'     => "SshPath",
257     sendmail       => "SendmailPath",
258     hostname       => "HostnamePath",
259     split          => "SplitPath",
260     par2           => "ParPath",
261     cat            => "CatPath",
262     gzip           => "GzipPath",
263     bzip2          => "Bzip2Path",
264 );
265
266 foreach my $prog ( sort(keys(%Programs)) ) {
267     my $path;
268     foreach my $subProg ( split(/\//, $prog) ) {
269         $path = FindProgram("$ENV{PATH}:/usr/bin:/bin:/sbin:/usr/sbin",
270                             $subProg) if ( !length($path) );
271     }
272     $Conf{$Programs{$prog}} = $path if ( !length($Conf{$Programs{$prog}}) );
273 }
274
275 while ( 1 ) {
276     print <<EOF;
277
278 I found the following locations for these programs:
279
280 EOF
281     foreach my $prog ( sort(keys(%Programs)) ) {
282         printf("    %-12s => %s\n", $prog, $Conf{$Programs{$prog}});
283     }
284     print "\n";
285     last if (prompt('--> Are these paths correct?', 'y') =~ /^y/i);
286     foreach my $prog ( sort(keys(%Programs)) ) {
287         $Conf{$Programs{$prog}} = prompt("--> $prog path",
288                                          $Conf{$Programs{$prog}});
289     }
290 }
291
292 my $Perl58 = system($Conf{PerlPath}
293                         . q{ -e 'exit($^V && $^V ge v5.8.0 ? 1 : 0);'});
294
295 if ( !$Perl58 ) {
296     print <<EOF;
297
298 BackupPC needs perl version 5.8.0 or later.  $Conf{PerlPath} appears
299 to be an older version.  Please upgrade to a newer version of perl
300 and re-run this configure script.
301
302 EOF
303     exit(1);
304 }
305
306 print <<EOF;
307
308 Please tell me the hostname of the machine that BackupPC will run on.
309
310 EOF
311 chomp($Conf{ServerHost} = `$Conf{HostnamePath}`)
312         if ( defined($Conf{HostnamePath}) && !defined($Conf{ServerHost}) );
313 $Conf{ServerHost} = prompt("--> BackupPC will run on host",
314                            $Conf{ServerHost},
315                            "hostname");
316
317 print <<EOF;
318
319 BackupPC should run as a dedicated user with limited privileges.  You
320 need to create a user.  This user will need read/write permission on
321 the main data directory and read/execute permission on the install
322 directory (these directories will be setup shortly).
323
324 The primary group for this user should also be chosen carefully.
325 The data directories and files will have group read permission,
326 so group members can access backup files.
327
328 EOF
329 my($name, $passwd, $Uid, $Gid);
330 while ( 1 ) {
331     $Conf{BackupPCUser} = prompt("--> BackupPC should run as user",
332                                  $Conf{BackupPCUser} || "backuppc",
333                                  "backuppc-user");
334     if ( $opts{"set-perms"} ) {
335         ($name, $passwd, $Uid, $Gid) = getpwnam($Conf{BackupPCUser});
336         last if ( $name ne "" );
337         print <<EOF;
338
339 getpwnam() says that user $Conf{BackupPCUser} doesn't exist.  Please
340 check the name and verify that this user is in the passwd file.
341
342 EOF
343         exit(1) if ( $opts{batch} );
344     } else {
345         last;
346     }
347 }
348
349 print <<EOF;
350
351 Please specify an install directory for BackupPC.  This is where the
352 BackupPC scripts, library and documentation will be installed.
353
354 EOF
355
356 while ( 1 ) {
357     $Conf{InstallDir} = prompt("--> Install directory (full path)",
358                                $Conf{InstallDir},
359                                "install-dir");
360     last if ( $Conf{InstallDir} =~ /^\// );
361     if ( $opts{batch} ) {
362         print("Need to specify --install-dir for new installation\n");
363         exit(1);
364     }
365 }
366
367 print <<EOF;
368
369 Please specify a data directory for BackupPC.  This is where all the
370 PC backup data is stored.  This file system needs to be big enough to
371 accommodate all the PCs you expect to backup (eg: at least several GB
372 per machine).
373
374 EOF
375
376 while ( 1 ) {
377     $Conf{TopDir} = prompt("--> Data directory (full path)",
378                            $Conf{TopDir},
379                            "data-dir");
380     last if ( $Conf{TopDir} =~ /^\// );
381     if ( $opts{batch} ) {
382         print("Need to specify --data-dir for new installation\n");
383         exit(1);
384     }
385 }
386
387 $Conf{CompressLevel} = $opts{"compress-level"}
388                             if ( defined($opts{"compress-level"}) );
389
390 if ( !defined($Conf{CompressLevel}) ) {
391     $Conf{CompressLevel} = BackupPC::FileZIO->compOk ? 3 : 0;
392     if ( $ConfigPath eq "" && $Conf{CompressLevel} ) {
393         print <<EOF;
394
395 BackupPC can compress pool files, providing around a 40% reduction in pool
396 size (your mileage may vary). Specify the compression level (0 turns
397 off compression, and 1 to 9 represent good/fastest to best/slowest).
398 The recommended values are 0 (off) or 3 (reasonable compression and speed).
399 Increasing the compression level to 5 will use around 20% more cpu time
400 and give perhaps 2-3% more compression.
401
402 EOF
403     } elsif ( $ConfigPath eq "" ) {
404         print <<EOF;
405
406 BackupPC can compress pool files, but it needs the Compress::Zlib
407 package installed (see www.cpan.org). Compression will provide around a
408 40% reduction in pool size, at the expense of cpu time.  You can leave
409 compression off and run BackupPC without compression, in which case you
410 should leave the compression level at 0 (which means off).  Or the better
411 choice is to quit, install Compress::Zlib, and re-run configure.pl.
412
413 EOF
414     } elsif ( $Conf{CompressLevel} ) {
415         $Conf{CompressLevel} = 0;
416         print <<EOF;
417
418 BackupPC now supports pool file compression.  Since you are upgrading
419 BackupPC you probably have existing uncompressed backups.  You could
420 turn on compression, so that new backups will be compressed.  This
421 will increase the pool storage requirement, since both uncompressed
422 and compressed copies of files will be stored. But eventually the old
423 uncompressed backups will expire, recovering the pool storage.  Please
424 see the documentation for more details.
425
426 If you are not sure what to do, leave the Compression Level at 0,
427 which disables compression.  You can always read the documentation
428 and turn it on later.
429
430 EOF
431     } else {
432         $Conf{CompressLevel} = 0;
433         print <<EOF;
434
435 BackupPC now supports pool file compression, but it needs the
436 Compress::Zlib module (see www.cpan.org).  For now, leave
437 the compression level set at 0 to disable compression.  If you
438 want you can install Compress::Zlib and turn compression on.
439
440 EOF
441     }
442     while ( 1 ) {
443         $Conf{CompressLevel}
444                     = prompt("--> Compression level", $Conf{CompressLevel});
445         last if ( $Conf{CompressLevel} =~ /^\d+$/ );
446     }
447 }
448
449 print <<EOF;
450
451 BackupPC has a powerful CGI perl interface that runs under Apache.
452 A single executable needs to be installed in a cgi-bin directory.
453 This executable needs to run as set-uid $Conf{BackupPCUser}, or
454 it can be run under mod_perl with Apache running as user $Conf{BackupPCUser}.
455
456 Leave this path empty if you don't want to install the CGI interface.
457
458 EOF
459
460 while ( 1 ) {
461     $Conf{CgiDir} = prompt("--> CGI bin directory (full path)",
462                            $Conf{CgiDir},
463                            "cgi-dir");
464     last if ( $Conf{CgiDir} =~ /^\// || $Conf{CgiDir} eq "" );
465     if ( $opts{batch} ) {
466         print("Need to specify --cgi-dir for new installation\n");
467         exit(1);
468     }
469 }
470
471 if ( $Conf{CgiDir} ne "" ) {
472
473     print <<EOF;
474
475 BackupPC's CGI script needs to display various PNG/GIF images that
476 should be stored where Apache can serve them.  They should be placed
477 somewhere under Apache's DocumentRoot.  BackupPC also needs to know
478 the URL to access these images.  Example:
479
480     Apache image directory:  /var/www/htdocs/BackupPC
481     URL for image directory: /BackupPC
482
483 The URL for the image directory should start with a slash.
484
485 EOF
486     while ( 1 ) {
487         $Conf{CgiImageDir} = prompt("--> Apache image directory (full path)",
488                                     $Conf{CgiImageDir},
489                                     "html-dir");
490         last if ( $Conf{CgiImageDir} =~ /^\// );
491         if ( $opts{batch} ) {
492             print("Need to specify --html-dir for new installation\n");
493             exit(1);
494         }
495     }
496     while ( 1 ) {
497         $Conf{CgiImageDirURL} = prompt("--> URL for image directory (omit http://host; starts with '/')",
498                                         $Conf{CgiImageDirURL},
499                                         "html-dir-url");
500         last if ( $Conf{CgiImageDirURL} =~ /^\// );
501         if ( $opts{batch} ) {
502             print("Need to specify --html-dir-url for new installation\n");
503             exit(1);
504         }
505     }
506 }
507
508 print <<EOF;
509
510 Ok, we're about to:
511
512   - install the binaries, lib and docs in $Conf{InstallDir},
513   - create the data directory $Conf{TopDir},
514   - create/update the config.pl file $Conf{ConfDir}/config.pl,
515   - optionally install the cgi-bin interface.
516
517 EOF
518
519 exit unless prompt("--> Do you want to continue?", "y") =~ /y/i;
520
521 #
522 # Create install directories
523 #
524 foreach my $dir ( qw(bin doc
525                      lib/BackupPC/CGI
526                      lib/BackupPC/Config
527                      lib/BackupPC/Lang
528                      lib/BackupPC/Storage
529                      lib/BackupPC/Xfer
530                      lib/BackupPC/Zip
531                      lib/Net/FTP
532                  ) ) {
533     next if ( -d "$DestDir$Conf{InstallDir}/$dir" );
534     mkpath("$DestDir$Conf{InstallDir}/$dir", 0, 0755);
535     if ( !-d "$DestDir$Conf{InstallDir}/$dir"
536             || !my_chown($Uid, $Gid, "$DestDir$Conf{InstallDir}/$dir") ) {
537         die("Failed to create or chown $DestDir$Conf{InstallDir}/$dir\n");
538     } else {
539         print("Created $DestDir$Conf{InstallDir}/$dir\n");
540     }
541 }
542
543 #
544 # Create CGI image directory
545 #
546 foreach my $dir ( ($Conf{CgiImageDir}) ) {
547     next if ( $dir eq "" || -d "$DestDir$dir" );
548     mkpath("$DestDir$dir", 0, 0755);
549     if ( !-d "$DestDir$dir" || !my_chown($Uid, $Gid, "$DestDir$dir") ) {
550         die("Failed to create or chown $DestDir$dir");
551     } else {
552         print("Created $DestDir$dir\n");
553     }
554 }
555
556 #
557 # Create other directories
558 #
559 foreach my $dir ( (
560             "$Conf{TopDir}",
561             "$Conf{TopDir}/pool",
562             "$Conf{TopDir}/cpool",
563             "$Conf{TopDir}/pc",
564             "$Conf{TopDir}/trash",
565             "$Conf{ConfDir}",
566             "$Conf{LogDir}",
567         ) ) {
568     mkpath("$DestDir$dir", 0, 0750) if ( !-d "$DestDir$dir" );
569     if ( !-d "$DestDir$dir"
570             || !my_chown($Uid, $Gid, "$DestDir$dir") ) {
571         die("Failed to create or chown $DestDir$dir\n");
572     } else {
573         print("Created $DestDir$dir\n");
574     }
575 }
576
577 printf("Installing binaries in $DestDir$Conf{InstallDir}/bin\n");
578 foreach my $prog ( qw(
579         __CONFIGURE_BIN_LIST__
580     ) ) {
581     InstallFile($prog, "$DestDir$Conf{InstallDir}/$prog", 0555);
582 }
583
584 printf("Installing library in $DestDir$Conf{InstallDir}/lib\n");
585 foreach my $lib ( qw(
586         __CONFIGURE_LIB_LIST__
587     ) ) {
588     InstallFile($lib, "$DestDir$Conf{InstallDir}/$lib", 0444);
589 }
590
591 if ( $Conf{CgiImageDir} ne "" ) {
592     printf("Installing images in $DestDir$Conf{CgiImageDir}\n");
593     foreach my $img ( <images/*> ) {
594         (my $destImg = $img) =~ s{^images/}{};
595         InstallFile($img, "$DestDir$Conf{CgiImageDir}/$destImg", 0444, 1);
596     }
597
598     #
599     # Install new CSS file, making a backup copy if necessary
600     #
601     my $cssBackup = "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css.pre-__VERSION__";
602     if ( -f "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css" && !-f $cssBackup ) {
603         rename("$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css", $cssBackup);
604     }
605     InstallFile("conf/BackupPC_stnd.css",
606                 "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css", 0444, 0);
607     InstallFile("conf/BackupPC_stnd_orig.css",
608                 "$DestDir$Conf{CgiImageDir}/BackupPC_stnd_orig.css", 0444, 0);
609     InstallFile("conf/sorttable.js",
610                 "$DestDir$Conf{CgiImageDir}/sorttable.js", 0444, 0);
611 }
612
613 printf("Making init.d scripts\n");
614 foreach my $init ( qw(gentoo-backuppc gentoo-backuppc.conf linux-backuppc
615                       solaris-backuppc debian-backuppc freebsd-backuppc
616                       freebsd-backuppc2 suse-backuppc slackware-backuppc ) ) {
617     InstallFile("init.d/src/$init", "init.d/$init", 0444);
618 }
619
620 printf("Making Apache configuration file for suid-perl\n");
621 InstallFile("httpd/src/BackupPC.conf", "httpd/BackupPC.conf", 0644);
622
623 printf("Installing docs in $DestDir$Conf{InstallDir}/doc\n");
624 foreach my $doc ( qw(BackupPC.pod BackupPC.html) ) {
625     InstallFile("doc/$doc", "$DestDir$Conf{InstallDir}/doc/$doc", 0444);
626 }
627
628 printf("Installing config.pl and hosts in $DestDir$Conf{ConfDir}\n");
629 InstallFile("conf/hosts", "$DestDir$Conf{ConfDir}/hosts", 0644)
630                     if ( !-f "$DestDir$Conf{ConfDir}/hosts" );
631
632 #
633 # Now do the config file.  If there is an existing config file we
634 # merge in the new config file, adding any new configuration
635 # parameters and deleting ones that are no longer needed.
636 #
637 my $dest = "$DestDir$Conf{ConfDir}/config.pl";
638 my ($distConf, $distVars) = ConfigParse("conf/config.pl");
639 my ($oldConf, $oldVars);
640 my ($newConf, $newVars) = ($distConf, $distVars);
641 if ( -f $dest ) {
642     ($oldConf, $oldVars) = ConfigParse($dest);
643     ($newConf, $newVars) = ConfigMerge($oldConf, $oldVars, $distConf, $distVars);
644 }
645
646 #
647 # Update various config parameters.  The old config is in Conf{}
648 # and the new config is an array in text form in $newConf->[].
649 #
650 $Conf{EMailFromUserName}  ||= $Conf{BackupPCUser};
651 $Conf{EMailAdminUserName} ||= $Conf{BackupPCUser};
652
653 #
654 # Guess $Conf{CgiURL}
655 #
656 if ( !defined($Conf{CgiURL}) ) {
657     if ( $Conf{CgiDir} =~ m{cgi-bin(/.*)} ) {
658         $Conf{CgiURL} = "'http://$Conf{ServerHost}/cgi-bin$1/BackupPC_Admin'";
659     } else {
660         $Conf{CgiURL} = "'http://$Conf{ServerHost}/cgi-bin/BackupPC_Admin'";
661     }
662 }
663
664 #
665 # The smbclient commands have moved from hard-coded to the config file.
666 # $Conf{SmbClientArgs} no longer exists, so merge it into the new
667 # commands if it still exists.
668 #
669 if ( defined($Conf{SmbClientArgs}) ) {
670     if ( $Conf{SmbClientArgs} ne "" ) {
671         foreach my $param ( qw(SmbClientRestoreCmd SmbClientFullCmd
672                                 SmbClientIncrCmd) ) {
673             $newConf->[$newVars->{$param}]{text}
674                             =~ s/(-E\s+-N)/$1 $Conf{SmbClientArgs}/;
675         }
676     }
677     delete($Conf{SmbClientArgs});
678 }
679
680 #
681 # CSS is now stored in a file rather than a big config variable.
682 #
683 delete($Conf{CSSstylesheet});
684
685 #
686 # The blackout timing settings are now stored in a list of hashes, rather
687 # than three scalar parameters.
688 #
689 if ( defined($Conf{BlackoutHourBegin}) ) {
690     $Conf{BlackoutPeriods} = [
691          {
692              hourBegin => $Conf{BlackoutHourBegin},
693              hourEnd   => $Conf{BlackoutHourEnd},
694              weekDays  => $Conf{BlackoutWeekDays},
695          } 
696     ];
697     delete($Conf{BlackoutHourBegin});
698     delete($Conf{BlackoutHourEnd});
699     delete($Conf{BlackoutWeekDays});
700 }
701
702 #
703 # $Conf{RsyncLogLevel} has been replaced by $Conf{XferLogLevel}
704 #
705 if ( defined($Conf{RsyncLogLevel}) ) {
706     $Conf{XferLogLevel} = $Conf{RsyncLogLevel};
707     delete($Conf{RsyncLogLevel});
708 }
709
710 #
711 # In 2.1.0 the default for $Conf{CgiNavBarAdminAllHosts} is now 1
712 #
713 $Conf{CgiNavBarAdminAllHosts} = 1;
714
715 #
716 # IncrFill should now be off
717 #
718 $Conf{IncrFill} = 0;
719
720 #
721 # Empty $Conf{ParPath} if it isn't a valid executable
722 # (pre-3.0.0 configure.pl incorrectly set it to a
723 # hardcoded value).
724 #
725 $Conf{ParPath} = '' if ( $Conf{ParPath} ne '' && !-x $Conf{ParPath} );
726
727 #
728 # Figure out sensible arguments for the ping command
729 #
730 if ( defined($Conf{PingArgs}) ) {
731     $Conf{PingCmd} = '$pingPath ' . $Conf{PingArgs};
732 } elsif ( !defined($Conf{PingCmd}) ) {
733     if ( $^O eq "solaris" || $^O eq "sunos" ) {
734         $Conf{PingCmd} = '$pingPath -s $host 56 1';
735     } elsif ( ($^O eq "linux" || $^O eq "openbsd" || $^O eq "netbsd")
736             && !system("$Conf{PingPath} -c 1 -w 3 localhost") ) {
737         $Conf{PingCmd} = '$pingPath -c 1 -w 3 $host';
738     } else {
739         $Conf{PingCmd} = '$pingPath -c 1 $host';
740     }
741     delete($Conf{PingArgs});
742 }
743
744 #
745 # Figure out sensible arguments for the df command
746 #
747 if ( !defined($Conf{DfCmd}) ) {
748     if ( $^O eq "solaris" || $^O eq "sunos" ) {
749         $Conf{DfCmd} = '$dfPath -k $topDir';
750     }
751 }
752
753 #
754 # $Conf{SmbClientTimeout} is now $Conf{ClientTimeout}
755 #
756 if ( defined($Conf{SmbClientTimeout}) ) {
757     $Conf{ClientTimeout} = $Conf{SmbClientTimeout};
758     delete($Conf{SmbClientTimeout});
759 }
760
761 #
762 # Replace --devices with -D in RsyncArgs and RsyncRestoreArgs
763 #
764 foreach my $param ( qw(RsyncArgs RsyncRestoreArgs) ) {
765     next if ( !defined($newVars->{$param}) );
766     $newConf->[$newVars->{$param}]{text} =~ s/--devices/-D/g;
767 }
768
769 #
770 # Merge any new user-editable parameters into CgiUserConfigEdit
771 # by copying the old settings forward.
772 #
773 if ( defined($Conf{CgiUserConfigEdit}) ) {
774     #
775     # This is a real hack.  The config file merging is done in text
776     # form without actually instantiating the new conf structure.
777     # So we need to extract the new hash of settings, update it,
778     # and merge the text.  Ugh...
779     #
780     my $new;
781     my $str = $distConf->[$distVars->{CgiUserConfigEdit}]{text};
782
783     $str =~ s/^\s*\$Conf\{.*?\}\s*=\s*/\$new = /m;
784     eval($str);
785     foreach my $p ( keys(%$new) ) {
786         $new->{$p} = $Conf{CgiUserConfigEdit}{$p}
787                 if ( defined($Conf{CgiUserConfigEdit}{$p}) );
788     }
789     $Conf{CgiUserConfigEdit} = $new;
790     my $d = Data::Dumper->new([$new], [*value]);
791     $d->Indent(1);
792     $d->Terse(1);
793     my $value = $d->Dump;
794     $value =~ s/(.*)\n/$1;\n/s;
795     $newConf->[$newVars->{CgiUserConfigEdit}]{text}
796             =~ s/(\s*\$Conf\{.*?\}\s*=\s*).*/$1$value/s;
797 }
798
799 #
800 # Apply any command-line configuration parameter settings
801 #
802 foreach my $param ( keys(%{$opts{"config-override"}}) ) {
803     my $val = eval { $opts{"config-override"}{$param} };
804     if ( @$ ) {
805         printf("Can't eval --config-override setting %s=%s\n",
806                         $param, $opts{"config-override"}{$param});
807         exit(1);
808     }
809     if ( !defined($newVars->{$param}) ) {
810         printf("Unkown config parameter %s in --config-override\n", $param);
811         exit(1);
812     }
813     $newConf->[$newVars->{$param}]{text} = $opts{"config-override"}{$param};
814 }
815
816 #
817 # Now backup and write the config file
818 #
819 my $confCopy = "$dest.pre-__VERSION__";
820 if ( -f $dest && !-f $confCopy ) {
821     #
822     # Make copy of config file, preserving ownership and modes
823     #
824     printf("Making backup copy of $dest -> $confCopy\n");
825     my @stat = stat($dest);
826     my $mode = $stat[2];
827     my $uid  = $stat[4];
828     my $gid  = $stat[5];
829     die("can't copy($dest, $confCopy)\n")
830                                 unless copy($dest, $confCopy);
831     die("can't chown $uid, $gid $confCopy\n")
832                                 unless my_chown($uid, $gid, $confCopy);
833     die("can't chmod $mode $confCopy\n")
834                                 unless my_chmod($mode, $confCopy);
835 }
836 open(OUT, ">", $dest) || die("can't open $dest for writing\n");
837 binmode(OUT);
838 my $blockComment;
839 foreach my $var ( @$newConf ) {
840     if ( length($blockComment)
841           && substr($var->{text}, 0, length($blockComment)) eq $blockComment ) {
842         $var->{text} = substr($var->{text}, length($blockComment));
843         $blockComment = undef;
844     }
845     $blockComment = $1 if ( $var->{text} =~ /^([\s\n]*#{70}.*#{70}[\s\n]+)/s );
846     $var->{text} =~ s/^\s*\$Conf\{(.*?)\}(\s*=\s*['"]?)(.*?)(['"]?\s*;)/
847                 defined($Conf{$1}) && ref($Conf{$1}) eq ""
848                                    && $Conf{$1} ne $OrigConf{$1}
849                                    ? "\$Conf{$1}$2$Conf{$1}$4"
850                                    : "\$Conf{$1}$2$3$4"/emg;
851     print OUT $var->{text};
852 }
853 close(OUT);
854 if ( !defined($oldConf) ) {
855     die("can't chmod 0640 mode $dest\n")  unless my_chmod(0640, $dest);
856     die("can't chown $Uid, $Gid $dest\n") unless my_chown($Uid, $Gid, $dest);
857 }
858
859 if ( $Conf{CgiDir} ne "" ) {
860     printf("Installing cgi script BackupPC_Admin in $DestDir$Conf{CgiDir}\n");
861     mkpath("$DestDir$Conf{CgiDir}", 0, 0755);
862     InstallFile("cgi-bin/BackupPC_Admin", "$DestDir$Conf{CgiDir}/BackupPC_Admin",
863                 04554);
864 }
865
866 print <<EOF;
867
868 Ok, it looks like we are finished.  There are several more things you
869 will need to do:
870
871   - Browse through the config file, $Conf{ConfDir}/config.pl,
872     and make sure all the settings are correct.  In particular,
873     you will need to set \$Conf{CgiAdminUsers} so you have
874     administration privileges in the CGI interface.
875
876   - Edit the list of hosts to backup in $Conf{ConfDir}/hosts.
877
878   - Read the documentation in $Conf{InstallDir}/doc/BackupPC.html.
879     Please pay special attention to the security section.
880
881   - Verify that the CGI script BackupPC_Admin runs correctly.  You might
882     need to change the permissions or group ownership of BackupPC_Admin.
883     If this is an upgrade and you are using mod_perl, you will need
884     to restart Apache.  Otherwise it will have stale code.
885
886   - BackupPC should be ready to start.  Don't forget to run it
887     as user $Conf{BackupPCUser}!  The installation also contains an
888     init.d/backuppc script that can be copied to /etc/init.d
889     so that BackupPC can auto-start on boot.  This will also enable
890     administrative users to start the server from the CGI interface.
891     See init.d/README.
892
893 Enjoy!
894 EOF
895
896 if ( `$Conf{PerlPath} -V` =~ /uselargefiles=undef/ ) {
897     print <<EOF;
898
899 Warning: your perl, $Conf{PerlPath}, does not support large files.
900 This means BackupPC won't be able to backup files larger than 2GB.
901 To solve this problem you should build/install a new version of perl
902 with large file support enabled.  Use
903
904     $Conf{PerlPath} -V | egrep uselargefiles
905
906 to check if perl has large file support (undef means no support).
907 EOF
908 }
909
910 eval "use File::RsyncP;";
911 if ( !$@ && $File::RsyncP::VERSION < 0.68 ) {
912     print("\nWarning: you need to upgrade File::RsyncP;"
913         . " I found $File::RsyncP::VERSION and BackupPC needs 0.68\n");
914 }
915
916 exit(0);
917
918 ###########################################################################
919 # Subroutines
920 ###########################################################################
921
922 sub InstallFile
923 {
924     my($prog, $dest, $mode, $binary) = @_;
925     my $first = 1;
926     my($uid, $gid) = ($Uid, $Gid);
927
928     if ( -f $dest ) {
929         #
930         # preserve ownership and modes of files that already exist
931         #
932         my @stat = stat($dest);
933         $mode = $stat[2];
934         $uid  = $stat[4];
935         $gid  = $stat[5];
936     }
937     unlink($dest) if ( -f $dest );
938     if ( $binary ) {
939         die("can't copy($prog, $dest)\n") unless copy($prog, $dest);
940     } else {
941         open(PROG, $prog)     || die("can't open $prog for reading\n");
942         open(OUT, ">", $dest) || die("can't open $dest for writing\n");
943         binmode(PROG);
944         binmode(OUT);
945         while ( <PROG> ) {
946             s/__INSTALLDIR__/$Conf{InstallDir}/g;
947             s/__LOGDIR__/$Conf{LogDir}/g;
948             s/__CONFDIR__/$Conf{ConfDir}/g;
949             s/__TOPDIR__/$Conf{TopDir}/g;
950             s/^(\s*my \$useFHS\s*=\s*)\d;/${1}$opts{fhs};/
951                                     if ( $prog =~ /Lib.pm/ );
952             s/__BACKUPPCUSER__/$Conf{BackupPCUser}/g;
953             s/__CGIDIR__/$Conf{CgiDir}/g;
954             s/__IMAGEDIR__/$Conf{CgiImageDir}/g;
955             s/__IMAGEDIRURL__/$Conf{CgiImageDirURL}/g;
956             if ( $first && /^#.*bin\/perl/ ) {
957                 #
958                 # Fill in correct path to perl (no taint for >= 2.0.1).
959                 #
960                 print OUT "#!$Conf{PerlPath}\n";
961             } else {
962                 print OUT;
963             }
964             $first = 0;
965         }
966         close(PROG);
967         close(OUT);
968     }
969     die("can't chown $uid, $gid $dest") unless my_chown($uid, $gid, $dest);
970     die("can't chmod $mode $dest")      unless my_chmod($mode, $dest);
971 }
972
973 sub FindProgram
974 {
975     my($path, $prog) = @_;
976
977     if ( defined($opts{"bin-path"}{$prog}) ) {
978         return $opts{"bin-path"}{$prog};
979     }
980     foreach my $dir ( split(/:/, $path) ) {
981         my $file = File::Spec->catfile($dir, $prog);
982         return $file if ( -x $file );
983     }
984     return;
985 }
986
987 sub ConfigParse
988 {
989     my($file) = @_;
990     open(C, $file) || die("can't open $file");
991     binmode(C);
992     my($out, @conf, $var);
993     my $comment = 1;
994     my $allVars = {};
995     my $endLine = undef;
996     while ( <C> ) {
997         if ( /^#/ && !defined($endLine) ) {
998             if ( $comment ) {
999                 $out .= $_;
1000             } else {
1001                 if ( $out ne "" ) {
1002                     $allVars->{$var} = @conf if ( defined($var) );
1003                     push(@conf, {
1004                         text => $out,
1005                         var => $var,
1006                     });
1007                 }
1008                 $var = undef;
1009                 $comment = 1;
1010                 $out = $_;
1011             }
1012         } elsif ( /^\s*\$Conf\{([^}]*)/ ) {
1013             $comment = 0;
1014             if ( defined($var) ) {
1015                 $allVars->{$var} = @conf if ( defined($var) );
1016                 push(@conf, {
1017                     text => $out,
1018                     var => $var,
1019                 });
1020                 $out = $_;
1021             } else {
1022                 $out .= $_;
1023             }
1024             $var = $1;
1025             $endLine = $1 if ( /^\s*\$Conf\{[^}]*} *= *<<(.*);/ );
1026             $endLine = $1 if ( /^\s*\$Conf\{[^}]*} *= *<<'(.*)';/ );
1027         } else {
1028             $endLine = undef if ( defined($endLine) && /^\Q$endLine[\n\r]*$/ );
1029             $out .= $_;
1030         }
1031     }
1032     if ( $out ne "" ) {
1033         $allVars->{$var} = @conf if ( defined($var) );
1034         push(@conf, {
1035             text => $out,
1036             var  => $var,
1037         });
1038     }
1039     close(C);
1040     return (\@conf, $allVars);
1041 }
1042
1043 sub ConfigMerge
1044 {
1045     my($old, $oldVars, $new, $newVars) = @_;
1046     my $posn = 0;
1047     my($res, $resVars);
1048
1049     #
1050     # Find which config parameters are not needed any longer
1051     #
1052     foreach my $var ( @$old ) {
1053         next if ( !defined($var->{var}) || defined($newVars->{$var->{var}}) );
1054         #print(STDERR "Deleting old config parameter $var->{var}\n");
1055         $var->{delete} = 1;
1056     }
1057     #
1058     # Find which config parameters are new
1059     #
1060     foreach my $var ( @$new ) {
1061         next if ( !defined($var->{var}) );
1062         if ( defined($oldVars->{$var->{var}}) ) {
1063             $posn = $oldVars->{$var->{var}};
1064         } else {
1065             #print(STDERR "New config parameter $var->{var}: $var->{text}\n");
1066             push(@{$old->[$posn]{new}}, $var);
1067         }
1068     }
1069     #
1070     # Create merged config file
1071     #
1072     foreach my $var ( @$old ) {
1073         next if ( $var->{delete} );
1074         push(@$res, $var);
1075         foreach my $new ( @{$var->{new}} ) {
1076             push(@$res, $new);
1077         }
1078     }
1079     for ( my $i = 0 ; $i < @$res ; $i++ ) {
1080         $resVars->{$res->[$i]{var}} = $i;
1081     }
1082     return ($res, $resVars);
1083 }
1084
1085 sub my_chown
1086 {
1087     my($uid, $gid, $file) = @_;
1088
1089     return 1 if ( !$opts{"set-perms"} );
1090     return chown($uid, $gid, $file);
1091 }
1092
1093 sub my_chmod
1094 {
1095     my ($mode, $file) = @_;
1096
1097     return 1 if ( !$opts{"set-perms"} );
1098     return chmod($mode, $file);
1099 }
1100
1101 sub prompt
1102 {
1103     my($question, $default, $option) = @_;
1104
1105     $default = $opts{$option} if ( defined($opts{$option}) );
1106     if ( $opts{batch} ) {
1107         print("$question [$default]\n");
1108         return $default;
1109     }
1110     print("$question [$default]? ");
1111     my $reply = <STDIN>;
1112     $reply =~ s/[\n\r]*//g;
1113     return $reply if ( $reply !~ /^$/ );
1114     return $default;
1115 }
1116
1117 __END__
1118
1119 =head1 SYNOPSIS
1120
1121 configure.pl [options]
1122
1123 =head1 DESCRIPTION
1124
1125 configure.pl is a script that is used to install or upgrade a BackupPC
1126 installation.  It is usually run interactively without arguments.  It
1127 also supports a batch mode where all the options can be specified
1128 via the command-line.
1129
1130 For upgrading BackupPC you need to make sure that BackupPC is not
1131 running prior to running BackupPC.
1132
1133 Typically configure.pl needs to run as the super user (root).
1134
1135 =head1 OPTIONS
1136
1137 =over 8
1138
1139 =item B<--batch>
1140
1141 Run configure.pl in batch mode.  configure.pl will run without
1142 prompting the user.  The other command-line options are used
1143 to specify the settings that the user is usually prompted for.
1144
1145 =item B<--backuppc-user=USER>
1146
1147 Specify the BackupPC user name that owns all the BackupPC
1148 files and runs the BackupPC programs.  Default is backuppc.
1149
1150 =item B<--bin-path PROG=PATH>
1151
1152 Specify the path for various external programs that BackupPC
1153 uses.  Several --bin-path options may be specified.  configure.pl
1154 usually finds sensible defaults based on searching the PATH.
1155 The format is:
1156
1157     --bin-path PROG=PATH
1158
1159 where PROG is one of perl, tar, smbclient, nmblookup, rsync, ping,
1160 df, ssh, sendmail, hostname, split, par2, cat, gzip, bzip2 and
1161 PATH is that full path to that program.
1162
1163 Examples
1164
1165     --bin-path cat=/bin/cat --bin-path bzip2=/home/user/bzip2
1166
1167 =item B<--compress-level=N>
1168
1169 Set the configuration compression level to N.  Default is 3
1170 if Compress::Zlib is installed.
1171
1172 =item B<--config-dir CONFIG_DIR>
1173
1174 Configuration directory for new installations.  Defaults
1175 to /etc/BackupPC with FHS.  Automatically extracted
1176 from --config-path for existing installations.
1177
1178 =item B<--config-path CONFIG_PATH>
1179
1180 Path to the existing config.pl configuration file for BackupPC.
1181 This option should be specified for batch upgrades to an
1182 existing installation.  The option should be omitted when
1183 doing a batch new install.
1184
1185 =item B<--cgi-dir CGI_DIR>
1186
1187 Path to Apache's cgi-bin directory where the BackupPC_Admin
1188 script will be installed.  This option only needs to be
1189 specified for a batch new install.
1190
1191 =item B<--data-dir DATA_DIR>
1192
1193 Path to the BackupPC data directory.  This is where all the backup
1194 data is stored, and it should be on a large file system. This option
1195 only needs to be specified for a batch new install.
1196
1197 Example:
1198
1199     --data-dir /data/BackupPC
1200
1201 =item B<--dest-dir DEST_DIR>
1202
1203 An optional prefix to apply to all installation directories.
1204 Usually this is not needed, but certain auto-installers like
1205 to stage an install in a temporary directory, and then copy
1206 the files to their real destination.  This option can be used
1207 to specify the temporary directory prefix.  Note that if you
1208 specify this option, BackupPC won't run correctly if you try
1209 to run it from below the --dest-dir directory, since all the
1210 paths are set assuming BackupPC is installed in the intended
1211 final locations.
1212
1213 =item B<--fhs>
1214
1215 Use locations specified by the Filesystem Hierarchy Standard
1216 for installing BackupPC.  This is enabled by default for new
1217 installations.  To use the pre-3.0 installation locations,
1218 specify --no-fhs.
1219
1220 =item B<--help|?>
1221
1222 Print a brief help message and exits.
1223
1224 =item B<--hostname HOSTNAME>
1225
1226 Host name (this machine's name) on which BackupPC is being installed.
1227 This option only needs to be specified for a batch new install.
1228
1229 =item B<--html-dir HTML_DIR>
1230
1231 Path to an Apache html directory where various BackupPC image files
1232 and the CSS files will be installed.  This is typically a directory
1233 below Apache's DocumentRoot directory.  This option only needs to be
1234 specified for a batch new install.
1235
1236 Example:
1237
1238     --html-dir /var/www/htdocs/BackupPC
1239
1240 =item B<--html-dir-url URL>
1241
1242 The URL (without http://hostname) required to access the BackupPC html
1243 directory specified with the --html-dir option.  This option only needs
1244 to be specified for a batch new install.
1245
1246 Example:
1247
1248     --html-dir-url /BackupPC
1249
1250 =item B<--install-dir INSTALL_DIR>
1251
1252 Installation directory for BackupPC scripts, libraries, and
1253 documentation.  This option only needs to be specified for a
1254 batch new install.
1255
1256 Example:
1257
1258     --install-dir /usr/local/BackupPC
1259
1260 =item B<--log-dir LOG_DIR>
1261
1262 Log directory.  Defaults to /var/log/BackupPC with FHS.
1263
1264 =item B<--man>
1265
1266 Prints the manual page and exits.
1267
1268 =item B<--set-perms>
1269
1270 When installing files and creating directories, chown them to
1271 the BackupPC user and chmod them too.  This is enabled by default.
1272 To disable (for example, if staging a destination directory)
1273 then specify --no-set-perms.
1274
1275 =item B<--uid-ignore>
1276
1277 configure.pl verifies that the script is being run as the super user
1278 (root).  Without the --uid-ignore option, in batch mode the script will
1279 exit with an error if not run as the super user, and in interactive mode
1280 the user will be prompted.  Specifying this option will cause the script
1281 to continue even if the user id is not root.
1282
1283 =head1 EXAMPLES
1284
1285 For a standard interactive install, run without arguments:
1286
1287     configure.pl
1288
1289 For a batch new install you need to specify answers to all the
1290 questions that are normally prompted:
1291
1292     configure.pl                                   \
1293         --batch                                    \
1294         --cgi-dir /var/www/cgi-bin/BackupPC        \
1295         --data-dir /data/BackupPC                  \
1296         --hostname myHost                          \
1297         --html-dir /var/www/html/BackupPC          \
1298         --html-dir-url /BackupPC                   \
1299         --install-dir /usr/local/BackupPC
1300
1301 For a batch upgrade, you only need to specify the path to the
1302 configuration file:
1303         
1304     configure.pl --batch --config-path /data/BackupPC/conf/config.pl
1305
1306 =head1 AUTHOR
1307
1308 Craig Barratt <cbarratt@users.sourceforge.net>
1309
1310 =head1 COPYRIGHT
1311
1312 Copyright (C) 2001-2010  Craig Barratt.
1313
1314 This program is free software; you can redistribute it and/or modify
1315 it under the terms of the GNU General Public License as published by
1316 the Free Software Foundation; either version 2 of the License, or
1317 (at your option) any later version.
1318
1319 =cut