Server : Apache System : Linux server1.cgrithy.com 3.10.0-1160.95.1.el7.x86_64 #1 SMP Mon Jul 24 13:59:37 UTC 2023 x86_64 User : nobody ( 99) PHP Version : 8.1.23 Disable Function : NONE Directory : /scripts/ |
#!/usr/local/cpanel/3rdparty/bin/perl # cpanel - scripts/ea-tomcat85 Copyright 2018 cPanel, Inc. # All rights Reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited use strict; use warnings; package scripts::ea_tomcat85; use Cpanel::Config::LoadUserDomains (); use Cpanel::AccessIds (); use Cpanel::JSON (); use File::Path::Tiny (); # Path::Tiny’s version is overly complicated use Path::Tiny; use Capture::Tiny 'capture'; use XML::LibXML (); use Cwd (); use App::CmdDispatch; use Path::Iter (); use Cpanel::SafeDir::Read (); my $cmds = { add => { code => \&add, clue => 'add <user>', abstract => 'add tomcat 8.5 support', help => "add tomcat 8.5 instance to the given user.", }, rem => { code => \&rem, clue => 'rem <user>', abstract => 'remove tomcat 8.5 support', help => "Remove tomcat 8.5 instance from the given user.\n\tSince this is destructive you must also verify you want to continue. Passing --verify=<user> will make it work. If you don’t pass it an explanatory message is given with instructions.", }, list => { code => sub { _bail( $_[0], "“list” does not take any arguments" ) if @_ > 1; for my $user ( _get_tomcat85_users() ) { print "$user\n"; } }, clue => 'list', abstract => 'list users with tomcat 8.5 instance', help => 'List users, one per line, that have a tomcat 8.5 instance.', }, all => { code => sub { my ( $app, $operation ) = @_; if ( !defined $operation || ( $operation ne "stop" && $operation ne "restart" ) ) { _bail( $_[0], "“all” requires an additional argument, either “stop” or “restart”" ); } for my $user ( _get_tomcat85_users() ) { print "Starting $user …\n"; my $homedir = _get_homedir($user); Cpanel::AccessIds::do_as_user_with_exception( $user, sub { local $ENV{HOME} = $homedir; my $orig_umask = umask(0027); _ubic_tc($operation); umask($orig_umask); } ); print " … done ($user).\n"; } }, clue => 'all <stop|restart>', abstract => 'Stop/restart all tomcat 8.5 instances', help => 'Stop or restart all users’s tomcat 8.5 instances.', }, }; my $hint_blurb = "This tool supports the following commands (i.e. $0 {command} …):"; my $opts = { 'help:pre_hint' => $hint_blurb, 'help:pre_help' => "Various tomcat 8.5 related admin utilities\n\n$hint_blurb", default_commands => "help", alias => { remove => "rem" }, }; run(@ARGV) if !caller; sub run { my (@argv) = @_; die "This script should only be called as root\n" if $> != 0; local $ENV{TERM} = $ENV{TERM} || "xterm-256color"; # non-CLI modulino avoid needless: Cannot find termcap: TERM not set at …/Term/ReadLine.pm line 373. App::CmdDispatch->new( $cmds, $opts )->run(@argv); } ################ #### commands ## ################ sub add { my ( $app, $user, $homedir ) = _process_args(@_); _bail( $app, "The user already has a tomcat 8.5 instance." ) if -d "$homedir/ea-tomcat85" && !_tomcat85_dir_is_empty_or_apps_only("$homedir/ea-tomcat85"); print "Adding $user’s tomcat 8.5 instance …\n"; my ( $http_port, $ajp_port ) = _get_ports($user); my $curdir = Cwd::cwd(); Cpanel::AccessIds::do_as_user_with_exception( $user, sub { local $ENV{HOME} = $homedir; my $orig_umask = umask(0027); mkdir "$homedir/ea-tomcat85"; # it may exist so don’t check it’s RV die "Could not create directory “$homedir/ea-tomcat85”: $!\n" if !-d "$homedir/ea-tomcat85"; chdir "$homedir/ea-tomcat85" or die "Could not change into “$homedir/ea-tomcat85”: $!\n"; for my $dir (qw(conf bin logs run temp webapps/ROOT work/Catalina/localhost/ROOT)) { File::Path::Tiny::mk($dir) or die "Could not create directory “$dir”: $!\n"; } my %symlinks = ( '/opt/cpanel/ea-tomcat85/README.USER-SERVICE-MANAGEMENT' => 'README.USER-SERVICE-MANAGEMENT', '/opt/cpanel/ea-tomcat85/README.FASTERSTARTUP' => 'README.FASTERSTARTUP', '/opt/cpanel/ea-tomcat85/README.SECURITY' => 'README.SECURITY', '/opt/cpanel/ea-tomcat85/README.APACHE-PROXY' => 'README.APACHE-PROXY', '/opt/cpanel/ea-tomcat85/README.USER-INSTANCE' => 'README.USER-INSTANCE', ); for my $target ( keys %symlinks ) { symlink( $target, $symlinks{$target} ) or warn "Could not symlink “$symlinks{$target}” to “$target”: $!\n"; } my $data_start = tell DATA; path("bin/setenv.sh")->spew(<DATA>); seek( DATA, $data_start, 0 ); system("cp -r /opt/cpanel/ea-tomcat85/user-conf/* conf/"); system("chmod 640 conf/*"); my $dom = XML::LibXML->load_xml( location => "conf/server.xml", load_ext_dtd => 0, ext_ent_handler => sub { } ); $dom->findnodes('//Server[@shutdown="SHUTDOWN"]')->shift()->setAttribute( port => -1 ); # disable the shutdown port my $ajp_connector_exists = 0; for my $conn ( $dom->findnodes("//Server/Service/Connector") ) { # hide version exposure # Could set `server` in addition to xpoweredBy but: # 1. it is a generic value for tomcat 4.1-8.5 so all an attacker can gelan is that it is tomcat # 2. if we set it to something else it will be a known value and they will be able to # determine not only that its tomcat but also the version $conn->setAttribute( xpoweredBy => "false" ); # set the ports to the user’s ports if ( $conn->getAttribute('protocol') eq 'HTTP/1.1' ) { $conn->setAttribute( port => $http_port ); $conn->removeAttribute('redirectPort'); } elsif ( $conn->getAttribute('protocol') eq 'AJP/1.3' ) { $conn->setAttribute( port => $ajp_port ); $conn->removeAttribute('redirectPort'); $conn->setAttribute( secretRequired => "false" ); $ajp_connector_exists = 1; } } if ( !$ajp_connector_exists ) { # Create <Connector port="10001" protocol="AJP/1.3" xpoweredBy="false"/> my $ajp = $dom->createElement('Connector'); $ajp->setAttribute( port => $ajp_port ); $ajp->setAttribute( protocol => 'AJP/1.3' ); $ajp->setAttribute( xpoweredBy => "false" ); $ajp->setAttribute( secretRequired => "false" ); $dom->findnodes('//Server/Service[@name="Catalina"]')->shift()->addChild($ajp); } my $valve = $dom->createElement("Valve"); $valve->setAttribute( className => "org.apache.catalina.valves.ErrorReportValve" ); $valve->setAttribute( showReport => "false" ); $valve->setAttribute( showServerInfo => "false" ); for my $host ( $dom->findnodes("//Host") ) { # hide version exposure: Create a ErrorReportValve w/ showServerInfo and showReport attributes to false $host->addChild($valve); # Host – autoDeploy, deployOnStartup, unpackWARs, and deployXML false # - FWiW, ASF does exploded deploys instead of ^^^ $host->setAttribute( autoDeploy => "false" ); $host->setAttribute( deployOnStartup => "false" ); $host->setAttribute( deployXML => "false" ); $host->setAttribute( unpackWARs => "false" ); } _write_dom( "conf/server.xml" => $dom ); _sysdie( "add service management" => ( qw(/usr/local/cpanel/scripts/cpuser_service_manager add ea-tomcat85), "--init-script=/opt/cpanel/ea-tomcat85/bin/user-init.sh" ) ); _ubic_tc("start"); umask($orig_umask); } ); chdir $curdir or warn "Could not chdir back to “$curdir”: $!\n"; print " … done!\n"; return; } sub rem { my ( $app, $user, $homedir ) = _process_args(@_); _bail( $app, "The user does not have a tomcat 8.5 instance." ) if !-d "$homedir/ea-tomcat85" || _tomcat85_dir_is_empty_or_apps_only("$homedir/ea-tomcat85"); print "Removing $user’s tomcat 8.5 instance …\n"; if ( !grep m/^--verify=\Q$user\E$/, @_ ) { die "This operation can not be undone, please pass the --verify=<USER> to indicate you’ve backed up anything in $homedir/ea-tomcat85/ that you want to keep!\n"; } my $curdir = Cwd::cwd(); Cpanel::AccessIds::do_as_user_with_exception( $user, sub { local $ENV{HOME} = $homedir; chdir $homedir or die "Could not change into “$homedir”: $!\n"; my $orig_umask = umask(0027); _ubic_tc("stop"); _sysdie( "remove service management" => qw(/usr/local/cpanel/scripts/cpuser_service_manager rem ea-tomcat85) ); # Can’t just File::Path::Tiny::rm("$homedir/ea-tomcat85") # because we want to keep these if they are not empty rmdir("$homedir/ea-tomcat85/webapps/ROOT"); rmdir("$homedir/ea-tomcat85/webapps"); my $fetch = Path::Iter::get_iterator("$homedir/ea-tomcat85"); while ( my $path = $fetch->() ) { next if $path eq "$homedir/ea-tomcat85" || $path =~ m{^\Q$homedir\E/ea-tomcat85/webapps(:?/|)}; -l $path || -f _ ? unlink($path) : File::Path::Tiny::rm($path); } rmdir("$homedir/ea-tomcat85"); # if its not empty (webapps) then this fails, that is what we want umask($orig_umask); } ); chdir $curdir or warn "Could not chdir back to “$curdir”: $!\n"; print " … done!\n"; return; } ############### #### helpers ## ############### sub _ubic_tc { my ($cmd) = @_; # would be cool if Cpanel::FindBin (or whatever) did this for us: CPANEL-22345 my $real_perl = readlink("/usr/local/cpanel/3rdparty/bin/perl"); my $cp_bin_dir = $real_perl; $cp_bin_dir =~ s{/perl$}{}; local $ENV{PATH} = "$cp_bin_dir:$ENV{PATH}"; # not only does this allow it to find our ubic-admin, it allows its env-shebang to pick up our perl # This is necessary to reflect reality under start/restart since Cpanel::AccessIds does not reset ENV # - HOME is already set because other callers need it also local $ENV{USER} = ( getpwuid($>) )[0]; system( "ubic", $cmd, "ea-tomcat85" ); return $? ? 0 : 1; } sub _sysdie { my ( $do_this, @cmd ) = @_; my $cmd_str = join( " ", @cmd ); system(@cmd) && die "Failed to $do_this (exit $?)\nThis will need done manually:\n\t`$cmd_str`\n"; return; } sub _bail { my ( $app, $msg ) = @_; chomp($msg); die "$msg\n" if $ENV{"scripts::ea_tomcat85::bail_die"}; warn "$msg\n"; $app->help(); exit(1); # there is no return()ing from this lol } sub _process_args { my ( $app, $user ) = @_; _bail( $app, "User argument is missing." ) if !$user; _bail( $app, "User argument is invalid." ) if !exists { Cpanel::Config::LoadUserDomains::loaduserdomains( undef, 0, 1 ) }->{$user}; return ( $app, $user, _get_homedir($user) ); } sub _get_homedir { my ($user) = @_; return ( getpwnam($user) )[7]; } sub _get_ports { my ($user) = @_; my ( $http_port, $ajp_port ); my $port_count = 2; _load_modulino("/usr/local/cpanel/scripts/cpuser_port_authority"); # see if they have them already so that we don’t keep allocating them if ( -f $scripts::cpuser_port_authority::port_authority_conf ) { my @ports; my $hr = eval { Cpanel::JSON::LoadFile($scripts::cpuser_port_authority::port_authority_conf) } || {}; for my $port ( keys %{$hr} ) { next if $hr->{$port}{owner} ne $user || !exists $hr->{$port}{service} || $hr->{$port}{service} ne 'ea-tomcat85'; push @ports, $port; } if ( @ports >= $port_count ) { ( $http_port, $ajp_port ) = sort @ports; } } if ( !$http_port || !$ajp_port ) { my @cmd = ( qw(/usr/local/cpanel/scripts/cpuser_port_authority give), $user, $port_count, '--service=ea-tomcat85' ); my ( $stdout, $stderr, $exit ) = capture { system(@cmd) }; # we could scripts::cpuser_port_authority::run("give", … but its nice to output the command, and the exit value is nice to detect success/failure if ($exit) { my $cmd = join( " ", @cmd ); $stderr =~ s{\[\?1034h}{}; die "$stderr\nFailed to allocate ports!\n`$cmd` exited $exit\n"; } else { my @lines = split( /\n/, $stdout ); chomp(@lines); ( $http_port, $ajp_port ) = sort grep { m/^[1-9][0-9]+$/ } @lines; } } if ( !$http_port || !$ajp_port ) { die "Failed to get ports ($port_count)!\n"; } return ( $http_port, $ajp_port ); } sub _load_modulino { my ($modulino) = @_; eval { require "$modulino" }; die "This feature is not available on this version of cPanel. You need v76 or newer\n" if $@; return 1; } sub _write_dom { my ( $path, $dom ) = @_; # get nicely indented XML (from http://grantm.github.io/perl-libxml-by-example/dom.html#modifying-the-dom) for my $node ( $dom->findnodes('//text()') ) { $node->parentNode->removeChild($node) unless $node =~ /\S/; } return path($path)->spew( $dom->toString(1) ); } sub _get_cmd { $cmds } sub _get_tomcat85_users { my @tomcat85_users; my %cpusers = Cpanel::Config::LoadUserDomains::loaduserdomains( undef, 0, 1 ); for my $user ( sort keys %cpusers ) { my $homedir = _get_homedir($user); push @tomcat85_users, $user if -d "$homedir/ea-tomcat85" && !_tomcat85_dir_is_empty_or_apps_only("$homedir/ea-tomcat85"); } return @tomcat85_users; } sub _tomcat85_dir_is_empty_or_apps_only { my ($tomcat85_dir) = @_; my @contents = Cpanel::SafeDir::Read::read_dir($tomcat85_dir); return 1 if !@contents || ( -d "$tomcat85_dir/webapps" && @contents == 1 ); return; } 1; __DATA__ # Your customizations can go here, for example, CATALINA_OPTS # example from https://wiki.apache.org/tomcat/HowTo/FasterStartUp#Entropy_Source # Trade some security for startup speed by using non-blocking entropy: # CATALINA_OPTS="$CATALINA_OPTS -Djava.security.egd=file:/dev/./urandom" # DO NOT EDIT THIS LINE OR ANYTHING BELOW THIS LINE . /opt/cpanel/ea-tomcat85/bin/user-setenv.sh