package Cpanel::Easy::ModJk5;

# cpanel - Cpanel/Easy/ModJk5.pm                  Copyright(c) 2014 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use Cpanel::FileUtils                    ();
use File::Copy::Recursive                ();    # Cpanel::Easy already does...
use SafeFile                             ();
use Cpanel::Sys                          ();
use Cpanel::Rand                         ();
use IPC::Open3                           ();
use Cpanel::SafeDir                      ();
use Cpanel::SafeDir::Read                ();
use Cpanel::Env                          ();
use Cpanel::SafeRun::Errors              ();
use Cpanel::Easy::Utils::Tomcat          ();
use Cpanel::Easy::Apache::Utils::Support ();

my $jakarta_dir             = q{/usr/local/jakarta};
my $version                 = '5.5.36';
my $version_short           = '5.5';
my $mysql_connector_version = '5.1.19';
my $jsvc_version            = '1.0.10';

our $easyconfig = {
    'name'                       => qq{Tomcat $version_short},
    'verify_on'                  => 'This will require an experienced tomcat admin.',
    'version'                    => $version,
    'hastargz'                   => 1,
    'src_cd2'                    => qq{apache-tomcat-$version},
    'url'                        => "http://tomcat.apache.org/tomcat-${version_short}-doc/index.html",
    'note'                       => "Experts ONLY: Do not install this unless you have a tomcat admin.",
    'skip'                       => 0,
    'treat_as_off_while_skipped' => 1,
    'support'                    => $Cpanel::Easy::Apache::Utils::Support::ENDOFLIFE,
    'meta'                       => {
        'rc_local' => !-e '/etc/rc.d/rc.local' && -e '/etc/rc.local' ? '/etc/rc.local' : '/etc/rc.d/rc.local',
        'tomcat_dir'          => "apache-tomcat-$version",
        'jsvc_dir'            => "commons-daemon-$jsvc_version-native-src/unix/",
        'mysqlconnector_file' => "mysql-connector-java-$mysql_connector_version/mysql-connector-java-$mysql_connector_version-bin.jar",
    },
    'ensurepkg'    => [qw( gd gd-devel )],
    'perl_modules' => {
        'GD'                      => 1,
        'GD::Graph'               => 1,
        'Statistics::Descriptive' => 1,
        'Template'                => 1,
        'Unix::PID'               => 1,
    },
    'implies' => { 'Cpanel::Easy::ModJk' => 1 },
    'modself' => sub {
        my $easy    = shift;
        my $self_hr = shift;

        # Tomcat 5.5 is now deprecated.  Don't bother
        # presenting to the user if it's not currently installed.
        # Once it's gone, it's gone.
        if ( !$easy->tomcat_55_available() ) {
            $self_hr->{'hastargz'} = 0;
            $self_hr->{'skip'}     = 1;
        }

        # If Tomcat 7 is not avaiable, don't include this rule, so that we
        # don't generate messages about a version of Tomcat that is supposed to
        # be hidden from the user
        if ( $easy->tomcat_7_available() ) {
            $self_hr->{'implies'}{'Cpanel::Easy::Tomcat::7_0'} = 0;
        }
    },
    'when_i_am_off' => sub {
        my ($self) = @_;

        # Stop tomcat
        system '/usr/sbin/stoptomcat' if -x '/usr/sbin/stoptomcat';

        # TODO: the contents of the 'meta' hash are not set when the module is off,
        # so this code block will not work as-is.
        my $tomcat_dir = sprintf( '%s/%s', $jakarta_dir, $self->{'__'}->{'meta'}->{'tomcat_dir'} );
        if ( eval { require Cpanel::Logd::Dynamic; } ) {
            if ( my $cr = Cpanel::Logd::Dynamic->can('delete_logd_link_entry') ) {
                $cr->( 'cp_modjk5_out', "$tomcat_dir/logs/catalina.out", );
                $cr->( 'cp_modjk5_err', "$tomcat_dir/logs/catalina.err", );
            }
        }

        # remove symlink for tomcat 5.5 to designate, "I am off"
        unlink "$jakarta_dir/tomcat" if -e "$jakarta_dir/tomcat";

        # remove from rc.local if needed
        #
        # TODO: the contents of the 'meta' hash are not set when the module is off,
        # so this code block will not work as-is.
        #
        # TODO: We also need to *delete* the line rather than just commenting it out,
        # so that if they re-enable Tomcat 5.5, the starttomcat line will be added back in correctly.
        if ( $self->{'__'}{'meta'}{'rc_local'} && -e $self->{'__'}{'meta'}{'rc_local'} ) {
            my %err;
            my $rc = Cpanel::FileUtils::regex_rep_file( $self->{'__'}{'meta'}{'rc_local'}, { qr{^(/usr/sbin/starttomcat.*)$} => q{# $1}, }, \%err, );

            $self->print_alert( q{Could not remove '[_1]' from rc.local}, 'tomcat related directives' ) if !$rc;
        }
    },
    'step' => {
        '0' => {
            'name'    => 'Checking jdk',
            'command' => sub {
                my $self = shift;
                my ( $path, @message ) = $self->get_path_installed('Cpanel::Easy::OptLib::jdk');
                return ( 1, 'Ok' ) if $path;
                return ( 0, @message );
            },
        },
        '0.01' => {
            'name'    => 'Fixing distributed mod_jk workers.properties',
            'command' => sub {
                my $self    = shift;
                my $src_dir = $self->{'opt_mod_src_dir'};
                my $jk_dir  = Cpanel::Easy::Utils::Tomcat::get_src_modjk_confdir();

                # remove mod_jk provided file, then overwrite with our own cpanel file
                if ( -e "$src_dir/workers.properties" && -e "$jk_dir/workers.properties" ) {
                    unlink "$jk_dir/workers.properties";
                    Cpanel::FileUtils::safecopy( "$src_dir/workers.properties", "$jk_dir/workers.properties" )
                      or return ( 0, q{Failed to backup '[_1]' using [_2]}, 'workers.properties', 'safecopy()' );
                }
                return ( 1, 'Ok' );
            },
        },
        '0.1' => {
            'name'    => 'Setting up variables',
            'command' => sub {
                my ($self) = @_;
                my $tomcat_dir = "$jakarta_dir/tomcat";
                $self->{'__'}{'tcpass'} = Cpanel::Rand::getranddata( 12, 0 );
                $self->{'__'}{'tomcat_port'} = 8080;
                if ( -e "$tomcat_dir/conf/server.xml" && !-z "$tomcat_dir/conf/server.xml" ) {
                    my $corrupted = 0;
                    if ( open my $server_xml_fh, '<', "$tomcat_dir/conf/server.xml" ) {
                        local $/ = undef;
                        my $server_xml = readline($server_xml_fh);
                        close $server_xml_fh;
                        if ( $server_xml =~ /<\/opt>\s*$/s ) {

                            # Corrupt server.xml file caused by a XML::Simple round-trip
                            $corrupted = 1;
                            $self->print_alert(q{The server.xml file is corrupted. Moving to server.xml.broken});
                            unlink("$tomcat_dir/conf/server.xml.broken");
                            Cpanel::FileUtils::safemv( "$tomcat_dir/conf/server.xml", "$tomcat_dir/conf/server.xml.broken" )
                              or return ( 0, q{Failed to rename '[_1]' to .broken}, "$tomcat_dir/conf/server.xml" );
                        }
                    }

                    if ( !$corrupted && eval q{ require XML::Simple; 1 } ) {

                        # see case 3524 event 23552 for info on the logic/structure/etc below
                        my $ref = eval { XML::Simple::XMLin("$tomcat_dir/conf/server.xml") };
                        if ( ref $ref ) {
                            if ( $ref->{'Service'}{'name'} eq 'Catalina' ) {
                                my $possible = $ref->{'Service'}{'Connector'}[0]->{'port'};
                                $self->{'__'}{'tomcat_port'} = $possible if $possible ne '8080' && $possible =~ m{ \A \d+ \z }xms;
                            }
                        }
                    }
                    elsif ($corrupted) {
                        $self->print_alert(q{Unable to load server.xml to retrieve port. Assuming port 8080...});
                    }
                    else {
                        $self->print_alert(q{Unable to load server.xml to retrieve port (please install XML::Simple). Assuming port 8080...});
                    }

                }
                return ( 1, 'ok' );
            },
        },
        '1' => {
            'name'    => 'backing up config files',
            'command' => sub {
                my ($self) = @_;
                my $tomcat_dir = sprintf( '%s/%s', $jakarta_dir, $self->{'__'}->{'meta'}->{'tomcat_dir'} );

                if ( -d "$tomcat_dir/conf" ) {
                    my $tmp_dir = "$self->{'opt_mod_src_dir'}/cptomcat";

                    $self->{'tomcat-conf-backup'} = 1;
                    $self->{'__'}{'meta'}{'tmp_dir'} = $tmp_dir;

                    # NOTE: Creating $tmp_dir is a race condition!
                    mkdir( $tmp_dir, 0750 ) || return ( 0, q{Failed to create Tomcat backup directory, '[_1]'}, $tmp_dir );
                    local $File::Copy::Recursive::CPRFComp = 0;
                    File::Copy::Recursive::dircopy( "$tomcat_dir/conf", $tmp_dir )
                      or return ( 0, q{Failed to backup Tomcat configurations from '[_1]' to '[_2]'}, "$tomcat_dir/conf", $tmp_dir );
                }
                return ( 1, 'ok' );
            },
        },
        '2' => {
            'name'    => qq{Setting up tomcat in $jakarta_dir},
            'command' => sub {
                my $self         = shift;
                my $symlink_path = "$jakarta_dir/tomcat";
                my $srcdir       = sprintf( '%s/%s', $self->{'opt_mod_src_dir'}, $self->{'__'}->{'meta'}->{'tomcat_dir'} );
                my $dstdir       = sprintf( '%s/%s', $jakarta_dir, $self->{'__'}->{'meta'}->{'tomcat_dir'} );

                mkdir $jakarta_dir unless -d $jakarta_dir;

                if ( -e $symlink_path || -l $symlink_path ) {
                    unlink $symlink_path;    # if its a file or symlink
                }
                else {
                    File::Copy::Recursive::pathrmdir($symlink_path);    # dir instead
                }

                if ( -e $symlink_path ) {
                    $self->print_alert( q{'[_1]' could not be removed}, $symlink_path );
                }

                local $File::Copy::Recursive::CPRFComp = 0;
                File::Copy::Recursive::dircopy( $srcdir, $dstdir );

                # handle File::Copy bug/feature until FCR handles it gracefully
                for my $file ( Cpanel::SafeDir::Read::read_dir($srcdir) ) {
                    my $mode = ( stat("$srcdir/$file") )[2] & 07777;
                    chmod $mode, "$dstdir/$file";
                }

                # symlink and verify location
                symlink $dstdir, $symlink_path;
                my $readlink = readlink($symlink_path);
                if ( $readlink ne $dstdir ) {
                    $self->print_alert( q{Failed to symlink '[_1]' to '[_2]': '[_3]'}, $symlink_path, $dstdir, $! );
                    return ( 0, q{'[_1]' points to '[_2]' and should point to '[_3]'}, $symlink_path, $readlink, $dstdir );
                }

                my $log_dir = "$dstdir/logs";
                if ( !-d $log_dir ) {
                    if ( -e $log_dir ) {
                        unlink $log_dir or return ( 0, q{Problem creating directory '[_1]'}, $log_dir );
                    }

                    Cpanel::SafeDir::safemkdir($log_dir)
                      or return ( 0, q{Problem creating directory '[_1]'}, $log_dir );
                }

                return ( 1, 'ok' );
            },
        },
        '3.5' => {
            'name'    => 'Copy mod_jk config files not managed by install process',
            'command' => sub {
                my $self   = shift;
                my $srcdir = Cpanel::Easy::Utils::Tomcat::get_src_modjk_confdir();
                my @rc;

                if ( -d $srcdir ) {
                    my $dstdir = qq{$jakarta_dir/tomcat/conf};
                    local $File::Copy::Recursive::CPRFComp = 0;

                    if ( File::Copy::Recursive::dircopy( $srcdir, $dstdir ) ) {
                        @rc = ( 1, 'Ok' );
                    }
                    else {
                        @rc = ( 0, q{Failed to copy '[_1]' to '[_2]'}, $srcdir, $dstdir );
                    }
                }
                else {
                    @rc = ( 0, q{Missing '[_1]' configuration directory, '[_2]'}, 'mod_jk', $srcdir );
                }

                return @rc;
            },
        },
        '4.1' => {
            'name'    => 'Configuring workers.properties',
            'command' => sub {
                my ($self) = @_;

                return ( 1, 'ok' ) if $self->{'tomcat-conf-backup'};    # no need to fiddle with them since the conf file backup restore will just wipe them out

                my $file = qq{$jakarta_dir/tomcat/conf/workers.properties};
                my %err;
                my $rc = Cpanel::FileUtils::regex_rep_file(
                    $file,
                    {
                        qr{^[\s\t]*workers.tomcat_home=/var/tomcat3}      => qq{workers.tomcat_home=$jakarta_dir/tomcat},
                        qr{^[\s\t]*workers.java_home=/opt/IBMJava2-13}    => q{workers.java_home=/usr/local/jdk/jre},
                        qr{^[\s\t]*worker.inprocess.jvm_lib=.*jvm.dll}    => q{#worker.inprocess.jvm_lib=\$(workers.java_home)\$(ps)jre\$(ps)bin\$(ps)classic\$(ps)jvm.dll},
                        qr{^[\s\t]*#worker.inprocess.jvm_lib=.*libjvm.so} => q{worker.inprocess.jvm_lib=.*libjvm.so},
                    },
                    \%err,
                );

                return ( 0, q{Error editing '[_1]' with '[_2]'}, $file, 'regex_rep_file()' ) if !$rc;

                return ( 1, 'ok' );
            },
        },
        '4.2' => {
            'name'    => 'Configuring workers2.properties',
            'command' => sub {
                my ($self) = @_;
                return ( 1, 'ok' ) if $self->{'tomcat-conf-backup'};    # no need to fiddle with them since the conf file backup restore will just wipe them out

                my $file = qq{$jakarta_dir/tomcat/conf/workers2.properties};
                if ( !-e $file ) {
                    $self->print_alert( q{[info] make install did not create '[_1]'. Skipping...}, $file );
                    return ( 1, 'ok' );
                }

                my %err;
                my $rc = Cpanel::FileUtils::regex_rep_file(
                    $file,
                    {
                        qr{^([\s\t]*\[logger\])}                                                         => q{# $1},
                        qr{^([\s\t]*level=DEBUG)}                                                        => q{# $1},
                        qr{^([\s\t]*\[lb:lb\])}                                                          => q{# $1},
                        qr{^([\s\t]*info=Default load balancer.)}                                        => q{# $1},
                        qr{^([\s\t]*\[lb:lb_1\])}                                                        => q{# $1},
                        qr{^([\s\t]*info=A second load balancer.)}                                       => q{# $1},
                        qr{^([\s\t]*\[channel.socket:localhost:8019\])}                                  => q{# $1},
                        qr{^([\s\t]*info=A second tomcat instance.)}                                     => q{# $1},
                        qr{^([\s\t]*tomcatId=localhost:8019)}                                            => q{# $1},
                        qr{^([\s\t]*lb_factor=1)}                                                        => q{# $1},
                        qr{^([\s\t]*group:lb:lb)}                                                        => q{# $1},
                        qr{^([\s\t]*group:lb:lb_1)}                                                      => q{# $1},
                        qr{^([\s\t]*\[channel.un:/opt/33/work/jk2.socket\])}                             => q{# $1},
                        qr{^([\s\t]*info=A second channel connecting to localhost:8019 via unix socket)} => q{# $1},
                        qr{^([\s\t]*\[channel.jni:jni\])}                                                => q{# $1},
                        qr{^([\s\t]*info=The jni channel, used if tomcat is started inprocess)}          => q{# $1},
                    },
                    \%err,
                );

                return ( 0, q{Error editing '[_1]' with '[_2]'}, $file, 'regex_rep_file()' ) if !$rc;
                return ( 1, 'ok' );
            },
        },
        '4.3' => {
            'name'    => 'Configuring tomcat-user.xml',
            'command' => sub {
                my ($self) = @_;
                if ( $self->{'tomcat-conf-backup'} ) {
                    $self->{'__'}{'tcpass'} = _parse_tcpass_from_backup($self);
                    return ( 1, 'ok' );    # no need to fiddle with them since the conf file backup restore will just wipe them out...
                }

                my $file = qq{$jakarta_dir/tomcat/conf/tomcat-users.xml};
                if ( !-e $file ) {
                    $self->print_alert( q{[info] make install did not create '[_1]'. Skipping...}, $file );
                    return ( 1, 'ok' );
                }

                my %err;
                my %qr = (
                    qr{^[\s\t]*</tomcat-users>} => sprintf( '  <user name="root" password="%s" roles="admin,manager" />\n</tomcat-users>', $self->{'__'}{'tcpass'} ),
                );
                my $rc = Cpanel::FileUtils::regex_rep_file( $file, \%qr, \%err );

                return ( 0, q{Error editing '[_1]' with '[_2]'}, $file, 'regex_rep_file()' ) if !$rc;
                return ( 1, 'ok' );
            },
        },
        '5' => {
            'name'    => 'Checking jsvc',
            'command' => sub {
                my ($self) = @_;
                my $jsvc_dir = sprintf( '%s/%s', $self->{'opt_mod_src_dir'}, $self->{'__'}{'meta'}{'jsvc_dir'} );

                local $ENV{'JAVA_HOME'}  = '/usr/local/jdk';
                local $ENV{'JAVACFLAGS'} = '-J-Xmx64m';

                my $start = $self->cwd();
                chdir $jsvc_dir or return ( 0, q{Could not chdir into '[_1]': [_2]}, $self->{'__'}{'meta'}{'jsvc_dir'}, $! );

                my @rc_patch = $self->apply_patch( sprintf( '%s/jsvc-libcap-location.patch', $self->{'opt_mod_patch_dir'} ) );
                return @rc_patch unless $rc_patch[0];

                chmod 0700, 'configure'
                  or return ( 0, q{chmod '[_1]', '[_2]' failed: $!}, 'chmod 0700, configure' );

                my @return = ( 1, 'ok' );
                my @configure = $self->run_system_cmd_returnable( [ './configure', '--with-java=' . $ENV{'JAVA_HOME'} ] );

                if ( $configure[0] ) {
                    my @make = $self->run_system_cmd_returnable( ['make'] );
                    if ( $make[0] ) {

                        # ? docs don't seem to favor this ...
                        #       (IE: by describing the process after make as "It should be straightforward from here on.")
                        # ... but instead: make install w/ ./configure --prefix=... or similar ?
                        Cpanel::FileUtils::safecopy( 'jsvc', "$jakarta_dir/tomcat/bin" )
                          or return ( 0, q{'[_1]' did not return true}, 'safecopy()' );
                    }
                    else {
                        @return = @make;
                    }
                }
                else {
                    push @{ $self->{'attach_files_to_report'} }, $self->cwd() . '/config.log' if ( -e $self->cwd() . '/config.log' );
                    @return = @configure;
                }

                chdir $start or return ( 0, q{Could not chdir back into '[_1]': [_2]}, $start, $! );
                return @return;
            },
        },
        '8' => {
            'name'    => 'installing [start/stop]tomcat scripts',
            'command' => sub {
                my ($self) = @_;
                my $jkfiles = sprintf( '%s/modjk5_files', $self->{'opt_mod_src_dir'} );

                for my $name (qw( starttomcat stoptomcat)) {
                    my $path = "/usr/sbin/$name";
                    unlink $path if -l $path;
                    Cpanel::FileUtils::safecopy( "$jkfiles/$name", $path )
                      or return ( 0, q{Failed to copy '[_1]' to /usr/sbin}, $name );
                    chmod 0700, "/usr/sbin/$name";
                    chown 0, 0, "/usr/sbin/$name";
                }

                return ( 1, 'ok' );
            },
        },
        '9' => {
            'name'    => 'Ensuring TomCat user',
            'command' => sub {
                my ($self) = @_;
                return ( 1, 'ok' ) if getpwnam('tomcat');
                my $nobodygid = ( getgrnam('nobody') )[2];

                return $self->run_system_cmd_returnable( [ qw( useradd tomcat -g ), $nobodygid, '-r' ] );
                return ( 1, 'ok' );
            },
        },
        '10' => {
            'name'    => 'Adding TomCat to chkserv.d',
            'command' => sub {
                my ($self) = @_;
                my $tomcat = sprintf( '%s/modjk5_files/tomcat', $self->{'opt_mod_src_dir'} );
                Cpanel::FileUtils::safecopy( $tomcat, '/etc/chkserv.d/' )
                  or return ( 0, q{Failed to copy '[_1]' script to /etc/chkserv.d}, 'tomcat' );
                local %ENV = %ENV;
                Cpanel::Env::cleanenv( 'http_purge' => 1 );
                return $self->run_system_cmd_returnable( ['/usr/local/cpanel/scripts/restartsrv_chkservd'] );
            },
        },
        '11' => {
            'name'    => 'Setting up the web.xml',
            'command' => sub {
                my ($self) = @_;
                setupwebxml() or return ( 0, q{'[_1]' did not return true}, 'setupwebxml()' );
                return ( 1, 'ok' );
            },
        },
        '12' => {
            'name'    => 'Restoring config files',
            'command' => sub {
                my ($self)  = @_;
                my $dst_dir = "$jakarta_dir/tomcat/conf";
                my $src_dir = $self->{'__'}{'meta'}{'tmp_dir'};

                return ( 1, 'ok' ) if !$self->{'tomcat-conf-backup'};

                # $self->{'__'}{'meta'}{'tmp_dir'}/conf -> /usr/local/jakarta/tomcat/conf
                # except $self->{'tomcat-conf-backup'} and setupwebxml()
                my %ignore = (
                    qq{$dst_dir/workers.properties}  => '',
                    qq{$dst_dir/workers2.properties} => '',
                    qq{$dst_dir/tomcat-users.xml}    => '',
                    qq{$dst_dir/web.xml}             => '',
                );

                if ( opendir my $tmp_dh, $src_dir ) {
                    while ( my $file = readdir($tmp_dh) ) {
                        chomp $file;
                        next if $file =~ m{^[.]+$};
                        next if exists $ignore{$file};

                        # Passing 1 as 3rd arg to safecopy disables recursive copy a la cp -rf (case 51349)
                        Cpanel::FileUtils::safecopy( "$src_dir/$file", "$dst_dir/$file", 1 )
                          or return ( 0, q{Failed to restore Tomcat config from '[_1]' to '[_2]'}, "$src_dir/$file", "$dst_dir/$file" );
                    }
                    closedir $tmp_dh;
                }
                else {
                    return ( 0, q{Failed to open directory, '[_1]': '[_2]'}, $src_dir, $! );
                }

                File::Copy::Recursive::pathempty($src_dir)
                  or return ( 0, q{Failed to clean up temporary directory, '[_1]'}, $src_dir );

                return ( 1, 'ok' );
            },
        },
        '13' => {
            'name'    => 'Setting up rc.local',
            'command' => sub {
                my ($self) = @_;

                my $rlock = SafeFile::safeopen( \*RCL, '+<', $self->{'__'}{'meta'}{'rc_local'} );

                if ( !grep /starttomcat/, <RCL> ) {
                    print RCL "/usr/sbin/starttomcat\n\n";
                }
                SafeFile::safeclose( \*RCL, $rlock );

                return ( 1, 'ok' );
            },
        },
        '14' => {
            'name'    => 'Installing helper scripts into /usr/local/cpanel/scripts',
            'command' => sub {
                my ($self) = @_;
                my $jkfiles = sprintf( '%s/modjk5_files', $self->{'opt_mod_src_dir'} );

                for my $name (qw( addservlets2 gentomcatlist2 remservlets )) {
                    my $path = "/usr/local/cpanel/scripts/$name";
                    unlink $path if -l $path;
                    Cpanel::FileUtils::safecopy( "$jkfiles/$name", $path )
                      or return ( 0, q{Failed to copy '[_1]' to /usr/local/cpanel/scripts}, $name );
                    chmod 0700, "/usr/local/cpanel/scripts/$name";
                    chown 0, 0, "/usr/local/cpanel/scripts/$name";
                }

                return ( 1, 'ok' );
            },
        },
        '15' => {
            'name'    => 'Finishing install',
            'command' => sub {
                my ($self) = @_;

                my @cmds = (
                    [ 'find',                      $jakarta_dir, qw(-type l -exec chown root -h {} ; ) ],
                    [ qw( chown -hR root:nobody ), "$jakarta_dir/tomcat/bin" ],
                    [ 'find',                      $jakarta_dir, qw(-type d ( -name bin ) -prune -o -exec chown -h tomcat:nobody {} ; ) ],
                );

                for my $cmd (@cmds) {
                    my $errs = Cpanel::SafeRun::Errors::saferunallerrors( @{$cmd} );
                    $errs && return ( 0, qq{Command '[_1]' returned errors: '[_2]'}, ( join ' ', @cmds ), $errs );
                }

                my @ret = link_if_not_linked(qw(/usr/local/jdk/jre /usr/local/jre));
                return @ret unless $ret[0];

                my $uj = '/usr/java';
                if ( -l $uj ) {
                    unlink $uj;
                }
                elsif ( -d $uj ) {
                    Cpanel::SafeDir::safermdir($uj);
                }
                -e $uj && return ( 0, q{'Could not remove [_1]'}, $uj );

                my @links = (
                    [qw( /usr/local/jdk               /usr/java )],
                    [ '/usr/local/jdk/lib/tools.jar', "$jakarta_dir/tomcat/common/lib/tools.jar" ],
                );

                for my $link (@links) {
                    @ret = link_if_not_linked( @{$link} );
                    return @ret unless $ret[0];
                }

                return ( 1, 'ok' );
            },
        },
        '16' => {
            'name'    => 'Setting up connector and tests',
            'command' => sub {
                my ($self) = @_;
                my $jktest = sprintf( '%s/modjk5-tests', $self->{'opt_mod_src_dir'} );
                my $connector = sprintf( '%s/%s', $self->{'opt_mod_src_dir'}, $self->{'__'}{'meta'}{'mysqlconnector_file'} );
                my $dst = "$jakarta_dir/tomcat";

                Cpanel::FileUtils::safecopy( $connector, "$dst/common/lib", 0 ) or return ( 0, q{Failed to copy '[_1]' to '[_2]'}, $connector, "$dst/common/lib" );

                mkdir "$dst/samples", 0755;

                my @sources = @{ get_dir_files($jktest) };

                @sources
                  or return ( 0, q{Unable to read directory, '[_1]'}, $self->{'__'}{'meta'}{'mysqlconnector_file'} );

                for my $source (@sources) {
                    Cpanel::FileUtils::safecopy( $source, "$dst/samples", 0 )
                      or return ( 0, q{Failed to copy '[_1]' to '[_2]'}, $source, "$dst/samples" );
                }

                return ( 1, 'ok' );
              }
        },

        # see case 30294
        '16.1' => {
            'name'    => 'Patching server.xml to keep jsvc from making it world readable and thus exposing sensitive information',
            'command' => sub {
                my ($self) = @_;
                my $conf_dir = "$jakarta_dir/tomcat/conf";
                if ( -e "$conf_dir/server.xml" ) {
                    if ( open my $server_xml_fh, '+<', "$conf_dir/server.xml" ) {
                        local $/ = undef;
                        my $server_xml = readline($server_xml_fh);

                        # Manually parse through the server.xml file keeping its current organization intact
                        if ( $server_xml =~ /(.*?)(<Server[\s>].*<\/Server[^>]*>)(.*)/s ) {
                            my ( $pre, $server, $post ) = ( $1, $2, $3 );
                            if ( $server =~ /(.*?)(<GlobalNamingResources[\s>].*<\/GlobalNamingResources[^>]*>)(.*)/s ) {
                                $pre .= $1;
                                $post = $3 . $post;
                                my $gnr     = $2;
                                my $new_gnr = $gnr;
                                while ( $gnr =~ /(<Resource[^>]*>)/sg ) {
                                    my $resource = $1;
                                    if ( $resource =~ /name\s*=\s*["']?UserDatabase["']?/s && $resource !~ /\breadonly\s*=/s ) {
                                        my $new_resource = $resource;
                                        $new_resource =~ s/(\s*\/?>)$/ readonly="true"$1/;
                                        $new_gnr      =~ s/\Q${resource}\E/${new_resource}/;
                                    }
                                }
                                if ( $new_gnr ne $gnr ) {
                                    seek( $server_xml_fh, 0, 0 );
                                    print $server_xml_fh $pre . $new_gnr . $post;
                                    truncate( $server_xml_fh, tell($server_xml_fh) );
                                }

                            }
                        }

                        close $server_xml_fh;
                    }
                }
                else {
                    $self->print_alert( q{'[_1]' does not exist.}, "$conf_dir/server.xml" );
                }

                return ( 1, 'ok' );
            },
        },
        '17' => {
            'name'    => 'Adding tomcat startup to post config gen',
            'command' => sub {
                my ($self) = @_;
                $self->add_command( 'post-confgen-commands', ['/usr/local/cpanel/scripts/gentomcatlist2'] );
                return ( 1, 'Ok' );
            },
        },

        '18' => {
            'name'    => 'Info you might want to know',
            'command' => sub {
                my ($self) = @_;

                my $passwd_str = $self->{'__'}{'tcpass'} ? "    The admin password is '$self->{'__'}{'tcpass'}'" : '';
                $self->print_to_log_and_screen(
                    qq{
The admin url is http://$self->{'hostname'}:$self->{'__'}{'tomcat_port'}/
    The admin user is 'root'
$passwd_str
                }
                );

                return ( 1, 'ok' );
            },
        },
        '19' => {
            'name'    => 'Adding tomcat test to post restart test list',
            'command' => sub {
                my ($self) = @_;
                my $tomcat_port = $self->{'__'}{'tomcat_port'};

                push @{ $self->{'_'}{'post_httpd_restart_tests'} }, {
                    'name'    => 'Basic tomcat port running test',
                    'command' => sub {
                        my ($self) = @_;
                        $self->print_alert(q{starting tomcat...});
                        my ( $rc, @msg );
                        {
                            local %ENV = %ENV;
                            Cpanel::Env::cleanenv( 'http_purge' => 1 );
                            ( $rc, @msg ) = $self->run_system_cmd_returnable( ['/usr/sbin/starttomcat'], 1, 1 );    # post-confgen-commands' too late, pre-confgen-commands too early
                        }
                        $self->print_alert(q{done});

                        if ($rc) {
                            sleep 30;
                            $self->print_alert("Verifying Tomcat setup");
                            my $content = $self->get_localhost_uri( '/', 0, '', $tomcat_port );
                            if ( !$content ) {
                                print "\n";
                                return (
                                    0,
                                    qq{There was no content, the test may be unable to connect to '[_1]' simply because tomcat is still initializing.\nYou can try to go to http://localhost:[_1]/ to manually check it.},
                                    $tomcat_port
                                );
                            }
                            elsif ( $content =~ m{setup Tomcat successfully} ) {
                                return ( 1, 'ok' );
                            }
                            else {
                                return ( 0, q{content received but not the default Apache tomcat page, probably ok} );
                            }
                        }
                        else {
                            return ( 0, @msg );
                        }
                    },
                };

                # TODO: add test for .jsp file via apache (IE port 80 not port $self->{'__'}{'tomcat_port'})
                #       needs a temporary JKMount on /usr/local/apache/htdocs/

                return ( 1, 'ok' );
            },
        },
        '20' => {
            'name'    => 'log rotation',
            'command' => sub {
                my ($self) = @_;
                my $log_dir = sprintf( '%s/%s/logs', $jakarta_dir, $self->{'__'}->{'meta'}->{'tomcat_dir'} );

                if ( eval { require Cpanel::Logd::Dynamic; } ) {
                    if ( my $cr = Cpanel::Logd::Dynamic->can('update_logd_link_entry') ) {
                        $cr->( 'cp_modjk5_out', "$log_dir/catalina.out", );
                        $cr->( 'cp_modjk5_err', "$log_dir/catalina.err", );
                    }
                }
                else {
                    $self->print_alert(q{unsupported, skipping...});
                }

                return ( 1, 'ok' );
            },
        },
    },    # end steps
};    # end easyconfig hr

sub _parse_tcpass_from_backup {
    my ($self) = @_;
    my $regex = qr{<user name="root" password="(\w+)" roles="admin,manager" />};

    if ( open my $bu_fh, '<', "$self->{'__'}{'meta'}{'tmp_dir'}/conf/tomcat-users.xml" ) {
        my $slurp = do { local $/; <$bu_fh> };
        close $bu_fh;
        if ( $slurp =~ $regex ) {
            return $1;
        }
    }

    return '';
}

sub setupwebxml {

    my $smap = <<EOM;
    <servlet-mapping>
        <servlet-name>httpservlet</servlet-name>
        <url-pattern>/servlet/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>httpservlets</servlet-name>
        <url-pattern>/servlets/*</url-pattern>
    </servlet-mapping>
EOM

    my $s = <<EOM;
   <servlet>
        <servlet-name>httpservlet</servlet-name>
        <servlet-class>
          org.apache.catalina.servlets.InvokerServlet
        </servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>httpservlets</servlet-name>
        <servlet-class>
          org.apache.catalina.servlets.InvokerServlet
        </servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
EOM

    my $conflock    = SafeFile::safeopen( \*HC, '+<', "$jakarta_dir/tomcat/conf/web.xml" );
    my $line        = 0;
    my $incomment   = 0;
    my $endsmapline = 0;
    my $endsline    = 0;
    my @HC;

    while (<HC>) {
        $line++;
        if (/[\s\t]*\<!/)     { $incomment = 1; }
        if (/[\s\t]*[\-]+\>/) { $incomment = 0; }
        if ( !$incomment ) {
            if (/^[\s\t]*\<\/servlet\-mapping\>/) { $endsmapline = $line; }
            if (/^[\s\t]*\<\/servlet\>/)          { $endsline    = $line; }
        }
        push( @HC, $_ );
    }

    seek( HC, 0, 0 );

    $line = 0;
    foreach (@HC) {
        $line++;
        if ( $line == ( $endsmapline + 1 ) ) {
            print HC "$smap\n";
        }
        if ( $line == ( $endsline + 1 ) ) {
            print HC "$s\n";
        }
        print HC $_;
    }

    truncate( HC, tell(HC) );
    SafeFile::safeclose( \*HC, $conflock );

    return 1;
}

sub get_dir_files {
    my $dir = shift;
    my @ret = ();
    if ( -d $dir && opendir my $handle, $dir ) {
        @ret = readdir $handle;
    }
    @ret = grep { !/^[.]{1,2}$/ } @ret;    # exclude . and ..
    substr $_, 0, 0, "$dir/" for @ret;     # prefix it with directory
    return \@ret;
}

sub link_if_not_linked {
    my ( $linkee, $linker ) = @_;          # $linker is to be a symlink to $linkee

    if ( -l $linker ) {                    # already a symlink
        my $linked_to = readlink $linker;    # but to what?
        return $linked_to eq $linkee
          ? ( 1, 'Ok' )                      # Link already exists as required, we're good
          : ( 0, "Cannot create $linker as symlink to $linkee, $linker is already a symlink but not to $linkee" );
    }

    -e $linker && return ( 0, "Cannot create $linker as symlink to $linkee, $linker already exists and is not a symlink" );
    my @result = mkdir_for_symlink($linker);
    $result[0] or return (@result);
    symlink $linkee, $linker or return ( 0, "creation of $linker as symlink to $linkee failed" );
    return ( 1, 'Ok' );
}

sub mkdir_for_symlink {
    my $symlinkpath = shift;

    my @ok = ( 1, 'Ok' );

    return @ok unless $symlinkpath =~ s{/[^/]+/?$}{};
    return @ok if -d $symlinkpath;

    return ( 0, "Cannot create dir $symlinkpath for symlink, already exists and is not a directory" )
      if -e $symlinkpath;

    return ( 0, "Problem creating directory $symlinkpath for symlink" )
      if !Cpanel::SafeDir::safemkdir($symlinkpath);

    return @ok;
}

1;

__END__

=head1 I want different format <Host> entires

If this file exists it will be used to create <Host> entries instead of the default:

/var/cpanel/templates/tomcat/server.xml.host_entry.default

It is a template toolkit format file: L<http://search.cpan.org/perldoc?Template::Manual::Intro>

With the following variables availble:

  name -> string
  appbase -> string
  alias_list -> array

=head2 Basic example

  <Host name="[% name %]" appBase="[% appbase %]">
    [%- FOREACH alias IN alias_list %]
      <Alias>[% alias %]</Alias>
    [% END -%]
    <Context path="" reloadable="true" docBase="[% appbase %]" debug="1"/>
    <Context path="/manager" debug="0" privileged="true"
       docBase="/usr/local/jakarta/tomcat/server/webapps/manager">
    </Context>
  </Host>
