File Coverage

lib/Mail/Toaster/FreeBSD.pm
Criterion Covered Total %
statement 24 220 10.9
branch 0 108 0.0
condition 0 27 0.0
subroutine 8 21 38.1
pod 5 13 38.4
total 37 389 9.5


line stmt bran cond sub pod time code
1             package Mail::Toaster::FreeBSD;
2 1     1   916 use strict;
  1         1  
  1         25  
3 1     1   3 use warnings;
  1         2  
  1         31  
4              
5             our $VERSION = '5.50';
6              
7 1     1   3 use Carp;
  1         1  
  1         36  
8 1     1   4 use File::Copy;
  1         1  
  1         36  
9 1     1   3 use Params::Validate qw( :all );
  1         1  
  1         113  
10 1     1   4 use POSIX;
  1         1  
  1         6  
11              
12 1     1   1259 use lib 'lib';
  1         1  
  1         5  
13 1     1   68 use parent 'Mail::Toaster::Base';
  1         1  
  1         4  
14              
15             sub drive_spin_down {
16 0     0 0   my $self = shift;
17 0           my %p = validate( @_, { 'drive' => SCALAR, $self->get_std_opts } );
18 0           my %args = $self->toaster->get_std_args( %p );
19              
20 0           my $drive = $p{'drive'};
21              
22 0 0         return $p{'test_ok'} if defined $p{'test_ok'};
23              
24             #TODO: see if the drive exists!
25              
26 0 0         my $camcontrol = $self->util->find_bin( "camcontrol", %args)
27             or return $self->error( "couldn't find camcontrol", %args );
28              
29 0           print "spinning down backup drive $drive...";
30 0           $self->util->syscmd( "$camcontrol stop $drive", %args );
31 0           print "done.\n";
32 0           return 1;
33             }
34              
35             sub get_defines {
36 0     0 0   my ($self, $flags) = @_;
37              
38             # flags are the "make -DWITH_OPTION" flags
39 0 0         return '' if ! $flags;
40              
41 0           my $make_defines;
42 0           foreach my $def ( split( /,/, $flags ) ) {
43 0 0         if ( $def =~ /=/ ) { # DEFINE=VALUE format, use as is
44 0           $make_defines .= " $def ";
45             }
46             else {
47 0           $make_defines .= " -D$def "; # otherwise, prepend the -D flag
48             }
49             }
50 0           return $make_defines;
51             };
52              
53             sub get_port_category {
54 0     0 0   my $self = shift;
55 0 0         my $port = shift or die "missing port in request\n";
56              
57 0           my ($path) = glob("/usr/ports/*/$port/distinfo");
58 0 0         if ( ! $path ) {
59 0           ($path) = glob("/usr/ports/*/$port/Makefile");
60             };
61 0 0         return if ! $path;
62 0           return (split '/', $path)[3];
63             }
64              
65             sub get_version {
66 0     0 0   my $self = shift;
67              
68 0           my (undef, undef, $version) = POSIX::uname;
69 0           $self->audit( "version is $version" );
70              
71 0           return $version;
72             }
73              
74             sub install_port {
75 0     0 1   my $self = shift;
76 0 0         my $portname = shift or return $self->error("missing port/package name" );
77 0           my %p = validate( @_,
78             { dir => { type => SCALAR, optional => 1 },
79             category => { type => SCALAR, optional => 1 },
80             check => { type => SCALAR, optional => 1 },
81             flags => { type => SCALAR, optional => 1 },
82             options => { type => SCALAR, optional => 1 },
83             $self->get_std_opts,
84             },
85             );
86              
87 0           my $options = $p{options};
88 0           my %args = $self->get_std_args( %p );
89              
90 0 0         return $p{test_ok} if defined $p{test_ok};
91              
92 0   0       my $check = $p{check} || $portname;
93 0 0         return 1 if $self->is_port_installed( $check, verbose=>1);
94              
95 0   0       my $port_dir = $p{dir} || $portname;
96 0           $port_dir =~ s/::/-/g;
97              
98 0 0 0       my $category = $p{category} || $self->get_port_category($portname)
99             or die "unable to find port directory for port $portname\n";
100              
101 0           my $path = "/usr/ports/$category/$port_dir";
102 0 0         -d $path or $self->error( "missing $path: $!\n" );
103              
104 0           $self->util->audit("install_port: installing $portname");
105              
106 0           my $make_defines = $self->get_defines($p{flags});
107              
108 0 0         if ($options) {
109 0           $self->port_options( port => $portname, cat => $category, opts => $options );
110             }
111              
112             # reset our PATH, to make sure we use our system supplied tools
113 0           $ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
114              
115             # the vast majority of ports work great this way
116 0           $self->audit( "running: make -C $path $make_defines install clean");
117 0           system "make -C $path clean";
118 0           system "make -C $path $make_defines";
119 0           system "make -C $path $make_defines install";
120 0 0         if ( $portname eq "ezmlm-idx" ) {
121 0           copy( "$path/work/ezmlm-0.53/ezmlmrc", "/usr/local/bin" );
122             }
123 0           system "make -C $path clean";
124              
125 0 0         return 1 if $self->is_port_installed( $check, verbose=>1 );
126              
127 0           $self->util->audit( "install_port: $portname install, FAILED" );
128              
129 0 0         if ( $portname =~ /\Ap5\-(.*)\z/ ) {
130 0           my $p_name = $1;
131 0           $p_name =~ s/\-/::/g;
132 0 0         $self->util->install_module_cpan($p_name) and return 1;
133             };
134              
135 0           $self->install_port_try_manual( $portname, $path );
136 0           return $self->error( "Install of $portname failed. Please fix and try again.", %args);
137             }
138              
139             sub is_port_installed {
140 0     0 1   my $self = shift;
141 0 0         my $port = shift or return $self->error("missing port name", fatal=>0);
142 0           my %p = validate( @_,
143             { 'alt' => { type => SCALAR | UNDEF, optional => 1 },
144             $self->get_std_opts,
145             },
146             );
147              
148 0   0       my $alt = $p{alt} || $port;
149              
150 0           my ( $r, @args );
151              
152 0           $self->util->audit( " checking for port $port", verbose=>0);
153              
154 0 0         return $p{test_ok} if defined $p{test_ok};
155              
156 0           my @packages;
157 0 0         if ( -x '/usr/sbin/pkg' ) {
158 0           @packages = `/usr/sbin/pkg info`; chomp @packages;
  0            
159             }
160             else {
161 0           my $pkg_info = $self->util->find_bin( 'pkg_info', verbose => 0 );
162 0           @packages = `$pkg_info`; chomp @packages;
  0            
163             }
164              
165 0           my @matches = grep {/^$port\-/} @packages;
  0            
166 0 0         if ( scalar @matches == 0 ) { @matches = grep {/^$port/} @packages; };
  0            
  0            
167 0 0         if ( scalar @matches == 0 ) { @matches = grep {/^$alt\-/ } @packages; };
  0            
  0            
168 0 0         if ( scalar @matches == 0 ) { @matches = grep {/^$alt/ } @packages; };
  0            
  0            
169 0 0         return if scalar @matches == 0; # no matches
170 0 0         $self->util->audit( "WARN: found multiple matches for port $port",verbose=>1)
171             if scalar @matches > 1;
172              
173 0           my ($installed_as) = split(/\s/, $matches[0]);
174 0           $self->util->audit( "found port $port installed as $installed_as" );
175 0           return $installed_as;
176             }
177              
178             sub install_portupgrade {
179 0     0 0   my $self = shift;
180 0           my %p = validate( @_, { $self->get_std_opts } );
181              
182 0           my %args = $self->toaster->get_std_args( %p );
183              
184 0   0       my $package = $self->conf->{'package_install_method'} || "packages";
185              
186 0 0         if ( defined $p{'test_ok'} ) { return $p{'test_ok'}; }
  0            
187              
188             # if we're running FreeBSD 6, try installing the package as it will do the
189             # right thing. On older systems we want to install a (much newer) version
190             # of portupgrade from ports
191              
192 0 0         if ( $self->get_version =~ m/\A6/ ) {
193 0           $self->install_package( "portupgrade", %args );
194             }
195              
196 0 0         if ( $package eq "packages" ) {
197 0           $self->install_package( "ruby18_static",
198             alt => "ruby-1.8",
199             %args,
200             );
201             }
202              
203 0           $self->install_port( port => "portupgrade", %args );
204              
205 0 0         return 1 if $self->is_port_installed( "portupgrade" );
206 0           return;
207             }
208              
209             sub install_package {
210 0     0 1   my $self = shift;
211 0 0         my $package = shift or die "missing package in request\n";
212 0           my %p = validate(
213             @_,
214             { 'alt' => { type => SCALAR, optional => 1, },
215             'url' => { type => SCALAR, optional => 1, },
216             $self->get_std_opts,
217             },
218             );
219              
220 0           my ( $alt, $pkg_url ) = ( $p{'alt'}, $p{'url'} );
221 0           my %args = $self->toaster->get_std_args( %p );
222              
223 0           $self->util->audit("install_package: checking if $package is installed");
224              
225 0 0         return $p{'test_ok'} if defined $p{'test_ok'};
226              
227 0 0         return 1 if $self->is_port_installed( $package, alt => $alt, %args );
228              
229 0           print "install_package: installing $package....\n";
230 0 0         $ENV{"PACKAGESITE"} = $pkg_url if $pkg_url;
231              
232 0           my ($pkg_add, $r2);
233 0 0         if ( -x '/usr/sbin/pkg' ) {
234 0           $pkg_add = '/usr/sbin/pkg';
235 0           $r2 = $self->util->syscmd( "$pkg_add install -y $package", verbose => 0 );
236             }
237              
238 0 0         if (! -x $pkg_add) {
239 0           $pkg_add = $self->util->find_bin( "pkg_add", %args );
240 0 0 0       if ( !$pkg_add || !-x $pkg_add ) {
241 0           return $self->error( "couldn't find pkg_add",fatal=>0)
242             };
243 0           $r2 = $self->util->syscmd( "$pkg_add -r $package", verbose => 0 );
244             }
245              
246 0 0         if ( !$r2 ) { print "\t $pkg_add failed\t "; }
  0            
247 0           else { print "\t $pkg_add success\t " };
248              
249 0           my $r = $self->is_port_installed( $package, alt => $alt, %args );
250 0 0         if ( ! $r ) {
251 0           carp " : Sorry, I couldn't install $package!\n";
252 0           return;
253             }
254              
255 0           return $r;
256             }
257              
258             sub install_port_try_manual {
259 0     0 0   my ($self, $portname, $path ) = @_;
260 0           print <<"EO_PORT_TRY_MANUAL";
261              
262             Automatic installation of port $portname failed! You can try to install $portname manually
263             using the following commands:
264              
265             cd $path
266             make
267             make install clean
268              
269             If that does not work, make sure your ports tree is up to date and
270             try again. See also "Dealing With Broken Ports":
271              
272             http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/ports-broken.html
273              
274             If manual installation fails, there may be something "unique" about your system
275             or the port may be broken. You can:
276              
277             a. Wait until the port is fixed
278             b. Try fixing the port
279             c. Get someone else to fix it
280              
281             EO_PORT_TRY_MANUAL
282             }
283              
284             sub port_options {
285 0     0 0   my $self = shift;
286 0           my %p = validate(
287             @_,
288             { port => SCALAR,
289             opts => SCALAR,
290             cat => SCALAR,
291             $self->get_std_opts,
292             },
293             );
294              
295 0           my ( $port, $cat, $opts ) = ( $p{port}, $p{cat}, $p{opts} );
296 0           my %args = $self->toaster->get_std_args( %p );
297              
298 0 0         return $p{test_ok} if defined $p{test_ok};
299              
300 0           my $opt_dir = "/var/db/ports/$cat".'_'.$port;
301 0 0         if ( !-d $opt_dir ) {
302 0           $self->util->mkdir_system( dir => $opt_dir, %args,);
303             }
304              
305 0           my $prefix = '# This file installed by Mail::Toaster';
306 0           $self->util->file_write( "$opt_dir/options", lines => [$prefix,$opts], %args );
307             }
308              
309             sub update_ports {
310 0     0 1   my $self = shift;
311 0           my %p = validate( @_, { $self->get_std_opts, } );
312 0           my %args = $self->toaster->get_std_args( %p );
313              
314 0 0         return $p{test_ok} if defined $p{test_ok};
315              
316 0 0         return $self->error( "you do not have write permission to /usr/ports.",%args) if ! $self->util->is_writable('/usr/ports', %args);
317              
318 0   0       my $supfile = $self->conf->{'cvsup_supfile_ports'} || "portsnap";
319              
320 0           return $self->portsnap( %args);
321              
322 0           return 1;
323             }
324              
325             sub portsnap {
326 0     0 0   my $self = shift;
327 0           my %p = validate( @_, { $self->get_std_opts, },);
328              
329 0           my %args = $self->toaster->get_std_args( %p );
330              
331 0 0         return $p{'test_ok'} if defined $p{'test_ok'};
332              
333             # should be installed already on FreeBSD 5.5 and 6.x
334 0           my $portsnap = $self->util->find_bin( "portsnap", fatal => 0 );
335 0           my $ps_conf = "/etc/portsnap.conf";
336              
337 0 0 0       unless ( $portsnap && -x $portsnap ) {
338 0           $self->install_port( "portsnap" );
339              
340 0           $ps_conf = '/usr/local/etc/portsnap.conf';
341 0 0         if ( !-e $ps_conf ) {
342 0 0         if ( -e "$ps_conf.sample" ) {
343 0           copy( "$ps_conf.sample", $ps_conf );
344             }
345             else {
346 0           warn "WARNING: portsnap configuration file is missing!\n";
347             }
348             }
349              
350 0           $portsnap = $self->util->find_bin( "portsnap", fatal => 0 );
351 0 0 0       unless ( $portsnap && -x $portsnap ) {
352 0           return $self->util->error(
353             "portsnap is not installed (correctly). I cannot go on!");
354             }
355             }
356              
357 0 0         if ( !-e $ps_conf ) {
358 0           $portsnap .= " -s portsnap.freebsd.org";
359             }
360              
361             # grabs the latest updates from the portsnap servers
362 0           system $portsnap, 'fetch';
363              
364 0 0         if ( !-e "/usr/ports/.portsnap.INDEX" ) {
365 0           print "\a
366             COFFEE BREAK TIME: this step will take a while, dependent on how fast your
367             disks are. After this initial extract, portsnap updates are much quicker than
368             doing a cvsup and require less bandwidth (good for you, and the FreeBSD
369             servers). Please be patient.\n\n";
370 0           sleep 2;
371 0           system $portsnap, "extract";
372             }
373             else {
374 0           system $portsnap, "update";
375             }
376              
377 0           return 1;
378             }
379              
380             sub conf_check {
381 0     0 1   my $self = shift;
382 0           my %p = validate(
383             @_,
384             { 'check' => { type => SCALAR, },
385             'line' => { type => SCALAR, },
386             'file' => { type => SCALAR, optional => 1, },
387             $self->get_std_opts,
388             },
389             );
390              
391 0           my %args = $self->get_std_args( %p );
392 0           my $check = $p{check};
393 0           my $line = $p{line};
394 0   0       my $file = $p{file} || "/etc/rc.conf";
395 0           $self->util->audit("conf_check: looking for $check");
396              
397 0 0         return $p{'test_ok'} if defined $p{'test_ok'};
398              
399 0           my $changes;
400             my @lines;
401 0 0         @lines = $self->util->file_read( $file ) if -f $file;
402 0           foreach ( @lines ) {
403 0 0         next if $_ !~ /^$check\=/;
404 0 0         return $self->util->audit("\tno change") if $_ eq $line;
405 0           $self->util->audit("\tchanged:\n$_\n\tto:\n$line\n" );
406 0           $_ = $line;
407 0           $changes++;
408             };
409 0 0         if ( $changes ) {
410 0           return $self->util->file_write( $file, lines => \@lines, %args );
411             };
412              
413 0           return $self->util->file_write( $file, append => 1, lines => [$line], %args );
414             }
415              
416             1;
417             __END__
418              
419              
420             =head1 NAME
421              
422             Mail::Toaster::FreeBSD - FreeBSD specific Mail::Toaster functions.
423              
424             =head1 SYNOPSIS
425              
426             Primarily functions for working with FreeBSD ports (updating, installing, configuring with custom options, etc) but also includes a suite of methods for FreeBSD managing jails.
427              
428              
429             =head1 DESCRIPTION
430              
431             Usage examples for each subroutine are included.
432              
433              
434             =head1 SUBROUTINES
435              
436             =over
437              
438             =item new
439              
440             use Mail::Toaster::FreeBSD;
441             my $fbsd = Mail::Toaster::FreeBSD->new;
442              
443              
444             =item is_port_installed
445              
446             Checks to see if a port is installed.
447              
448             $fbsd->is_port_installed( "p5-CGI" );
449              
450             arguments required
451             port - the name of the port/package
452              
453             arguments optional:
454             alt - alternate package name. This can help as ports evolve and register themselves differently in the ports database.
455              
456             result:
457             0 - not installed
458             1 - if installed
459              
460              
461             =item jail_create
462              
463             $fbsd->jail_create( );
464              
465             arguments required:
466             ip - 10.0.1.1
467              
468             arguments optional:
469             hostname - jail36.example.com,
470             jail_home - /home/jail,
471              
472             If hostname is not passed and reverse DNS is set up, it will
473             be looked up. Otherwise, the hostname defaults to "jail".
474              
475             jail_home defaults to "/home/jail".
476              
477             Here's an example of how I use it:
478              
479             ifconfig fxp0 inet alias 10.0.1.175/32
480              
481             perl -e 'use Mail::Toaster::FreeBSD;
482             my $fbsd = Mail::Toaster::FreeBSD->new;
483             $fbsd->jail_create( ip=>"10.0.1.175" )';
484              
485             After running $fbsd->jail_create, you need to set up the jail.
486             At the very least, you need to:
487              
488             1. set root password
489             2. create a user account
490             3. get remote root
491             a) use sudo (pkg_add -r sudo; visudo)
492             b) add user to wheel group (vi /etc/group)
493             c) modify /etc/ssh/sshd_config to permit root login
494             4. install perl (pkg_add -r perl)
495              
496             Here's how I set up my jails:
497              
498             pw useradd -n matt -d /home/matt -s /bin/tcsh -m -h 0
499             passwd root
500             pkg_add -r sudo rsync perl5.8
501             rehash; visudo
502             sh /etc/rc
503              
504             Ssh into the jail from another terminal. Once successfully
505             logged in with root privs, you can drop the initial shell
506             and access the jail directly.
507              
508             Read the jail man pages for more details. Read the perl code
509             to see what else it does.
510              
511              
512             =item jail_delete
513              
514             Delete a jail.
515              
516             $freebsd->jail_delete( ip=>'10.0.1.160' );
517              
518             This script unmounts the proc and dev filesystems and then nukes the jail directory.
519              
520             It would be a good idea to shut down any processes in the jail first.
521              
522              
523             =item jail_start
524              
525             Starts up a FreeBSD jail.
526              
527             $fbsd->jail_start( ip=>'10.0.1.1', hostname=>'jail03.example.com' );
528              
529              
530             arguments required:
531             ip - 10.0.1.1,
532              
533             arguments optional:
534             hostname - jail36.example.com,
535             jail_home - /home/jail,
536              
537             If hostname is not passed and reverse DNS is set up, it will be
538             looked up. Otherwise, the hostname defaults to "jail".
539              
540             jail_home defaults to "/home/jail".
541              
542             Here's an example of how I use it:
543              
544             perl -e 'use Mail::Toaster::FreeBSD;
545             $fbsd = Mail::Toaster::FreeBSD->new;
546             $fbsd->jail_start( ip=>"10.0.1.175" )';
547              
548              
549              
550             =item install_port
551              
552             $fbsd->install_port( "openldap" );
553              
554             That's it. Really. Well, OK, sometimes it can get a little more complex. install_port checks first to determine if a port is already installed and if so, skips right on by. It is very intelligent that way. However, sometimes port maintainers do goofy things and we need to override settings that would normally work. A good example of this is currently openldap.
555              
556             If you want to install OpenLDAP 2, then you can install from any of:
557              
558             /usr/ports/net/openldap23-server
559             /usr/ports/net/openldap23-client
560             /usr/ports/net/openldap24-server
561             /usr/ports/net/openldap24-client
562              
563             So, a full complement of settings could look like:
564              
565             $freebsd->install_port( "openldap-client",
566             dir => "openldap24-server",
567             check => "openldap-client-2.4",
568             flags => "NOPORTDOCS=true",
569             fatal => 0,
570             );
571              
572             arguments required:
573             port - the name of the directory in which the port resides
574              
575             arguments optional:
576             dir - overrides 'port' for the build directory
577             check - what to test for to determine if the port is installed (see note #1)
578             flags - comma separated list of arguments to pass when building
579              
580             NOTES:
581              
582             #1 - On rare occasion, a port will get installed as a name other than the ports name. Of course, that wreaks all sorts of havoc so when one of them nasties is found, you can optionally pass along a fourth parameter which can be used as the port installation name to check with.
583              
584              
585             =item install_package
586              
587             $fbsd->install_package( "maildrop" );
588              
589             Suggested usage:
590              
591             unless ( $fbsd->install_package( "maildrop" ) ) {
592             $fbsd->install_port( "maildrop" );
593             };
594              
595             Installs the selected package from FreeBSD packages. If the first install fails, it will try again using an alternate FTP site (ftp2.freebsd.org). If that fails, it returns 0 (failure) so you know it failed and can try something else, like installing via ports.
596              
597             If the package is registered in FreeBSD's package registry as another name and you want to check against that name (so it doesn't try installing a package that's already installed), instead, pass it along as alt.
598              
599             arguments required:
600             port - the name of the package to install
601              
602             arguments optional:
603             alt - a name the package is registered in the ports tree as
604             url - a URL to fetch the package from
605              
606             See the pkg_add man page for more details on using an alternate URL.
607              
608              
609             =item update_ports
610              
611             Updates the FreeBSD ports tree (/usr/ports/).
612              
613             $fbsd->update_ports();
614              
615             arguments required:
616             conf - a hashref
617              
618             See the docs for toaster-watcher.conf for complete details.
619              
620              
621             =item conf_check
622              
623             $fbsd->conf_check(check=>"snmpd_enable", line=>"snmpd_enable=\"YES\"");
624              
625             The above example is for snmpd. This checks to verify that an snmpd_enable line exists in /etc/rc.conf. If it doesn't, then it will add it by appending the second argument to the file.
626              
627              
628             =back
629              
630             =head1 AUTHOR
631              
632             Matt Simerson <matt@tnpi.net>
633              
634             =head1 BUGS
635              
636             None known. Report any to author.
637              
638             =head1 TODO
639              
640             Needs more documentation.
641              
642             =head1 SEE ALSO
643              
644             The following are all man/perldoc pages:
645              
646             Mail::Toaster
647             Mail::Toaster::Conf
648             toaster.conf
649             toaster-watcher.conf
650              
651             http://mail-toaster.org/
652             http://www.tnpi.net/computing/freebsd/
653              
654              
655             =head1 COPYRIGHT
656              
657             Copyright 2003-2012, The Network People, Inc. All Rights Reserved.
658              
659             Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
660              
661             Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
662              
663             Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
664              
665             Neither the name of the The Network People, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
666              
667             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
668              
669             =cut