Server IP : 195.201.23.43 / Your IP : 3.148.109.137 Web Server : Apache System : Linux webserver2.vercom.be 5.4.0-192-generic #212-Ubuntu SMP Fri Jul 5 09:47:39 UTC 2024 x86_64 User : kdecoratie ( 1041) PHP Version : 7.1.33-63+ubuntu20.04.1+deb.sury.org+1 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals, MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/webmin/virtual-server/ |
Upload File : |
# Functions for PHP configuration # get_domain_php_mode(&domain) # Returns 'mod_php' if PHP is run via Apache's mod_php, 'cgi' if run via # a CGI script, 'fcgid' if run via fastCGI. This is detected by looking for the # Action lines in httpd.conf. sub get_domain_php_mode { local ($d) = @_; local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_get_web_php_mode", $d); } elsif (!$p) { return "Virtual server does not have a website"; } &require_apache(); local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'}); if ($virt) { # First check for FPM socket, using a single ProxyPassMatch local @ppm = &apache::find_directive("ProxyPassMatch", $vconf); foreach my $ppm (@ppm) { if ($ppm =~ /unix:([^\|]+)/ || $ppm =~ /fcgi:\/\/(localhost|127\.0\.0\.1):\d+/) { return 'fpm'; } } # Also check for FPM socket in a FilesMatch block foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) { next if ($f->{'words'}->[0] ne '\.php$'); foreach my $h (&apache::find_directive("SetHandler", $f->{'members'})) { if ($h =~ /proxy:fcgi:\/\/(localhost|127\.0\.0\.1)/ || $h =~ /proxy:unix:/) { return 'fpm'; } } } # Look for an action, possibly in a directory, that runs the FCGI # wrapper for PHP scripts local @actions = &apache::find_directive("Action", $vconf); local $pdir = &public_html_dir($d); local ($dir) = grep { $_->{'words'}->[0] eq $pdir || $_->{'words'}->[0] eq $pdir."/" || &path_glob_match($_->{'words'}->[0], $pdir) } &apache::find_directive_struct("Directory", $vconf); if ($dir) { push(@actions, &apache::find_directive("Action", $dir->{'members'})); foreach my $f (&apache::find_directive("FCGIWrapper", $dir->{'members'})) { if ($f =~ /^\Q$d->{'home'}\E\/fcgi-bin\/php\S+\.fcgi/) { return 'fcgid'; } } } # Look for an action that runs PHP via the CGI wrapper foreach my $a (@actions) { if ($a =~ /^application\/x-httpd-php[0-9\.]+\s+\/cgi-bin\/php\S+\.cgi/) { return 'cgi'; } } # Look for a mapping from PHP scripts to plain text for 'none' mode if ($dir) { local @types = &apache::find_directive( "AddType", $dir->{'members'}); foreach my $t (@types) { if ($t =~ /text\/plain\s+\.php/) { return 'none'; } } } } # Fall back to mod_php, if enabled if (&get_apache_mod_php_version()) { return 'mod_php'; } return 'none'; } # save_domain_php_mode(&domain, mode, [port], [new-domain]) # Changes the method a virtual web server uses to run PHP. Returns undef on # success or an error message on failure. sub save_domain_php_mode { local ($d, $mode, $port, $newdom) = @_; local $p = &domain_has_website($d); $p || return "Virtual server does not have a website"; local $tmpl = &get_template($d->{'template'}); local $oldmode = &get_domain_php_mode($d); local @vers = sort { &compare_version_numbers($a->[0], $b->[0]) } &list_available_php_versions($d, $mode); # Work out the default PHP version for FPM if ($mode eq "fpm") { local @fpms = grep { !$_->{'err'} } &list_php_fpm_configs(); @fpms || return "No FPM versions found!"; my $curr = &get_php_fpm_config($d->{'php_fpm_version'}); if (!$curr) { # Current version isn't actually valid! Fall back to default delete($d->{'php_fpm_version'}); } if (!$d->{'php_fpm_version'}) { # Work out the default FPM version from the template if (@vers) { # Use version from template, or the max version my $fpm; if ($tmpl->{'web_phpver'}) { ($fpm) = grep { $_->[0] eq $tmpl->{'web_phpver'} } @vers; } $fpm ||= $vers[$#vers]; $d->{'php_fpm_version'} = $fpm->[0]; } else { # This should never happen? my $defconf = $tmpl->{'web_phpver'} ? &get_php_fpm_config($tmpl->{'web_phpver'}) : undef; $defconf ||= $fpms[0]; $d->{'php_fpm_version'} = $defconf->{'shortversion'}; } } } if ($mode =~ /mod_php|none/ && $oldmode !~ /mod_php|none/) { # Save the PHP version for later recovery local $oldver = &get_domain_php_version($d, $oldmode); $d->{'last_php_version'} = $oldver; } my $oldplog; if ($mode ne $oldmode) { # Save the PHP error log path $oldplog = &get_domain_php_error_log($d); } # Work out source php.ini files local (%srcini, %subs_ini); $mode eq "none" || @vers || return "No PHP versions found for mode $mode"; foreach my $ver (@vers) { $subs_ini{$ver->[0]} = 0; local $srcini = $tmpl->{'web_php_ini_'.$ver->[0]}; if (!$srcini || $srcini eq "none" || !-r $srcini) { $srcini = &get_global_php_ini($ver->[0], $mode); } else { $subs_ini{$ver->[0]} = 1; } $srcini{$ver->[0]} = $srcini; } local @srcinis = &unique(values %srcini); # Copy php.ini file into etc directory, for later per-site modification local $etc = "$d->{'home'}/etc"; if (!-d $etc) { &make_dir_as_domain_user($d, $etc, 0755); } foreach my $ver (@vers) { # Create separate .ini file for each PHP version, if missing local $subs_ini = $subs_ini{$ver->[0]}; local $srcini = $srcini{$ver->[0]}; local $inidir = "$etc/php$ver->[0]"; if ($srcini && !-r "$inidir/php.ini") { # Copy file, set permissions, fix session.save_path, and # clear out extension_dir (because it can differ between # PHP versions) if (!-d $inidir) { &make_dir_as_domain_user($d, $inidir, 0755); } if (-r "$etc/php.ini" && !-l "$etc/php.ini") { # We are converting from the old style of a single # php.ini file to the new multi-version one .. just # copy the existing file for all versions, which is # assumed to be working ©_source_dest_as_domain_user( $d, "$etc/php.ini", "$inidir/php.ini"); } elsif ($subs_ini) { # Perform substitions on config file local $inidata = &read_file_contents($srcini); $inidata || return "Failed to read $srcini, ". "or file is empty"; $inidata = &substitute_virtualmin_template($inidata,$d); &open_tempfile_as_domain_user( $d, INIDATA, ">$inidir/php.ini"); &print_tempfile(INIDATA, $inidata); &close_tempfile_as_domain_user($d, INIDATA); } else { # Just copy verbatim local ($ok, $err) = ©_source_dest_as_domain_user( $d, $srcini, "$inidir/php.ini"); $ok || return "Failed to copy $srcini to ". "$inidir/php.ini : $err"; } # Clear any caching on file &unflush_file_lines("$inidir/php.ini"); undef($phpini::get_config_cache{"$inidir/php.ini"}); local ($uid, $gid) = (0, 0); if (!$tmpl->{'web_php_noedit'}) { ($uid, $gid) = ($d->{'uid'}, $d->{'ugid'}); } if (&foreign_check("phpini") && -r "$inidir/php.ini") { # Fix up session save path, extension_dir and # gc_probability / gc_divisor &foreign_require("phpini"); local $pconf = &phpini::get_config("$inidir/php.ini"); local $tmp = &create_server_tmp($d); &phpini::save_directive($pconf, "session.save_path", $tmp); &phpini::save_directive($pconf, "upload_tmp_dir", $tmp); if (scalar(@srcinis) == 1 && scalar(@vers) > 1) { # Only if the same source is used for multiple # PHP versions. &phpini::save_directive($pconf, "extension_dir", undef); } # On some systems, these are not set and so sessions are # never cleaned up. local $prob = &phpini::find_value( "session.gc_probability", $pconf); local $div = &phpini::find_value( "session.gc_divisor", $pconf); &phpini::save_directive($pconf, "session.gc_probability", 1) if (!$prob); &phpini::save_directive($pconf, "session.gc_divisor", 100) if (!$div); # Set timezone to match system local $tz; if (&foreign_check("time")) { &foreign_require("time"); if (&time::has_timezone()) { $tz = &time::get_current_timezone(); } } if ($tz) { &phpini::save_directive($pconf, "date.timezone", $tz); } &flush_file_lines("$inidir/php.ini"); } &set_ownership_permissions($uid, $gid, 0755, "$inidir/php.ini"); } } # Call plugin-specific function to perform webserver setup if ($p ne 'web') { my $err = &plugin_call($p, "feature_save_web_php_mode", $d, $mode, $port, $newdom); $d->{'php_mode'} = $mode if (!$err); return $err; } &require_apache(); # Create wrapper scripts if ($mode ne "mod_php" && $mode ne "fpm" && $mode ne "none") { &create_php_wrappers($d, $mode); } # Setup PHP-FPM pool if ($mode eq "fpm") { &create_php_fpm_pool($d); } else { &delete_php_fpm_pool($d); } # Add the appropriate directives to the Apache config local $conf = &apache::get_config(); local @ports = ( $d->{'web_port'}, $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) ); @ports = ( $port ) if ($port); # Overridden to just do SSL or non-SSL local $fdest = "$d->{'home'}/fcgi-bin"; local $pfound = 0; foreach my $p (@ports) { local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p); next if (!$vconf); $pfound++; # Find <directory> sections containing PHP directives. # If none exist, add them in either the directory for # public_html, or the <virtualhost> if it already has them local @phpconfs; local @dirstrs = &apache::find_directive_struct("Directory", $vconf); foreach my $dirstr (@dirstrs) { local @wrappers = &apache::find_directive("FCGIWrapper", $dirstr->{'members'}); local @actions = grep { $_ =~ /^application\/x-httpd-php/ } &apache::find_directive("Action", $dirstr->{'members'}); if (@wrappers || @actions) { push(@phpconfs, $dirstr); } } if (!@phpconfs) { # No directory has them yet. Add to the <virtualhost> if it # already directives for cgi, the <directory> otherwise. # Unless we are using fcgid, in which case it must always be # added to the directory. local @pactions = grep { $_ =~ /^application\/x-httpd-php\d+/ } &apache::find_directive("Action", $vconf); local $pdir = &public_html_dir($d); local ($dirstr) = grep { $_->{'words'}->[0] eq $pdir || $_->{'words'}->[0] eq $pdir."/" } &apache::find_directive_struct("Directory", $vconf); if ($mode eq "fcgid") { $dirstr || return "No <Directory> section ". "found for mod_fcgid directives"; push(@phpconfs, $dirstr); } elsif ($dirstr && !@pactions) { push(@phpconfs, $dirstr); } else { push(@phpconfs, $virt); } } # Work out which PHP version each directory uses currently local %pdirs; if (!$newdom) { %pdirs = map { $_->{'dir'}, $_->{'version'} } &list_domain_php_directories($d); } # Update all of the directories local @avail = map { $_->[0] } &list_available_php_versions($d, $mode); local %allvers = map { $_, 1 } @all_possible_php_versions; foreach my $phpstr (@phpconfs) { # Remove all Action and AddType directives for suexec PHP local $phpconf = $phpstr->{'members'}; local @actions = &apache::find_directive("Action", $phpconf); @actions = grep { !/^application\/x-httpd-php\d+/ } @actions; local @types = &apache::find_directive("AddType", $phpconf); @types = grep { !/^application\/x-httpd-php\d+/ && !/\.php[0-9\.]*$/ } @types; # Remove all AddHandler and FCGIWrapper directives for fcgid local @handlers = &apache::find_directive("AddHandler", $phpconf); @handlers = grep { !(/^fcgid-script\s+\.php(.*)$/ && ($1 eq '' || $allvers{$1})) } @handlers; local @wrappers = &apache::find_directive("FCGIWrapper", $phpconf); @wrappers = grep { !(/^\Q$fdest\E\/php[0-9\.]+\.fcgi\s+\.php(.*)$/ && ($1 eq '' || $allvers{$1})) } @wrappers; # Add needed Apache directives. Don't add the AddHandler, # Alias and Directory if already there. local $ver = $pdirs{$phpstr->{'words'}->[0]} || $tmpl->{'web_phpver'} || $avail[$#avail]; $ver = $avail[$#avail] if (&indexof($ver, @avail) < 0); if ($mode eq "cgi") { foreach my $v (@avail) { push(@actions, "application/x-httpd-php$v ". "/cgi-bin/php$v.cgi"); } } elsif ($mode eq "fcgid") { push(@handlers, "fcgid-script .php"); foreach my $v (@avail) { push(@handlers, "fcgid-script .php$v"); } push(@wrappers, "$fdest/php$ver.fcgi .php"); foreach my $v (@avail) { push(@wrappers, "$fdest/php$v.fcgi .php$v"); } } elsif ($mode eq "none") { foreach my $v (@avail) { push(@types, "text/plain .php$v"); } push(@types, "text/plain .php"); } if ($mode eq "cgi" || $mode eq "mod_php") { foreach my $v (@avail) { push(@types,"application/x-httpd-php$v .php$v"); } } if ($mode eq "cgi") { push(@types, "application/x-httpd-php$ver .php"); } elsif ($mode eq "mod_php" || $mode eq "fcgid") { push(@types, "application/x-httpd-php .php"); } @types = &unique(@types); &apache::save_directive("Action", \@actions, $phpconf, $conf); &apache::save_directive("AddType", \@types, $phpconf, $conf); &apache::save_directive("AddHandler", \@handlers, $phpconf, $conf); &apache::save_directive("FCGIWrapper", \@wrappers, $phpconf, $conf); # For fcgid mode, the directory needs to have Options ExecCGI local ($opts) = &apache::find_directive("Options", $phpconf); if ($opts && $mode eq "fcgid" && $opts !~ /ExecCGI/) { $opts .= " +ExecCGI"; &apache::save_directive("Options", [ $opts ], $phpconf, $conf); } } # For FPM mode, we need a proxy directive at the top level local $fsock = &get_php_fpm_socket_file($d, 1); local $fport = $d->{'php_fpm_port'}; local $fmode = $fport ? 'port' : -r $fsock ? 'socket' : $tmpl->{'php_sock'} ? 'socket' : 'port'; local @ppm = &apache::find_directive("ProxyPassMatch", $vconf); local @oldppm = grep { /unix:([^\|]+)/ || /fcgi:\/\/(localhost|127\.0\.0\.1):\d+/ } @ppm; if ($fsock) { @ppm = grep { !/unix:([^\|]+)/ } @ppm; } if ($fport) { @ppm = grep { !/fcgi:\/\/(localhost|127\.0\.0\.1):\d+/ } @ppm; } local $files; foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) { $files = $f if ($f->{'words'}->[0] eq '\.php$'); } if ($mode eq "fpm" && ($apache::httpd_modules{'core'} < 2.4 || @oldppm)) { # Use a proxy directive for older Apache or if this is what's # already in use local $phd = $phpconfs[0]->{'words'}->[0]; if ($fmode eq 'socket') { # Use socket file push(@ppm, "^/(.*\.php(/.*)?)\$ unix:${fsock}|fcgi://127.0.0.1${phd}/\$1"); } else { # Allocate and use a port number $fport = &get_php_fpm_socket_port($d); push(@ppm, "^/(.*\.php(/.*)?)\$ fcgi://127.0.0.1:${fport}${phd}/\$1"); } } elsif ($mode eq "fpm" && $apache::httpd_modules{'core'} >= 2.4) { # Can use a FilesMatch block with SetHandler inside instead my $wanth; if ($fmode eq 'socket') { # Use a socket file $wanth = 'proxy:unix:'.$fsock."|fcgi://127.0.0.1"; } else { # Allocate, save and use a TCP port my $fport = &get_php_fpm_socket_port($d); $wanth = 'proxy:fcgi://127.0.0.1:'.$fport; } if (!$files) { # Add a new FilesMatch block with the socket $files = { 'name' => 'FilesMatch', 'type' => 1, 'value' => '\.php$', 'words' => ['\.php$'], 'members' => [ { 'name' => 'SetHandler', 'value' => $wanth, }, ], }; &apache::save_directive_struct( undef, $files, $vconf, $conf); } else { # Add the SetHandler directive to the FilesMatch block &apache::save_directive("SetHandler", [$wanth], $files->{'members'}, $conf); } } else { # For non-FPM mode, remove the whole files block, # and forget about any ports or sockets if ($files) { &apache::save_directive_struct( $files, undef, $vconf, $conf); } @ppm = grep { !/unix:|fcgi:/ } @ppm; delete($d->{'php_fpm_port'}); } &apache::save_directive("ProxyPassMatch", \@ppm, $vconf, $conf); # For non-mod_php mode, we need a RemoveHandler .php directive at # the <virtualhost> level to supress mod_php which may still be active local @remove = &apache::find_directive("RemoveHandler", $vconf); @remove = grep { !(/^\.php(.*)$/ && ($1 eq '' || $allvers{$1})) } @remove; if ($mode ne "mod_php") { push(@remove, ".php"); foreach my $v (@avail) { push(@remove, ".php$v"); } } @remove = &unique(@remove); &apache::save_directive("RemoveHandler", \@remove, $vconf, $conf); # For non-mod_php mode, use php_admin_value to turn off mod_php in # case it gets enabled in a .htaccess file if (&get_apache_mod_php_version()) { local @admin = &apache::find_directive("php_admin_value", $vconf); @admin = grep { !/^engine\s+/ } @admin; if ($mode ne "mod_php") { push(@admin, "engine Off"); } &apache::save_directive("php_admin_value", \@admin, $vconf, $conf); } # For fcgid mode, set IPCCommTimeout to either the configured value # or the PHP max execution time + 1, so that scripts run via fastCGI # aren't disconnected if ($mode eq "fcgid") { local $maxex; if ($config{'fcgid_max'} eq "*") { # Don't set $maxex = undef; } elsif ($config{'fcgid_max'} eq "") { # From PHP config local $inifile = &get_domain_php_ini($d, $ver); if (-r $inifile) { &foreign_require("phpini"); local $iniconf = &phpini::get_config($inifile); $maxex = &phpini::find_value( "max_execution_time", $iniconf); } } else { # Fixed number $maxex = int($config{'fcgid_max'})-1; } if (defined($maxex)) { &set_fcgid_max_execution_time($d, $maxex, $mode, $p); } } else { # For other modes, don't set &apache::save_directive("IPCCommTimeout", [ ], $vconf, $conf); } # For fcgid mode, set max request size to 1GB, which is the default # in older versions of mod_fcgid but is smaller in versions 2.3.6 and # later. local $setmax; if ($mode eq "fcgid") { if ($gconfig{'os_type'} eq 'debian-linux' && $gconfig{'os_version'} >= 6) { # Debian 6 and Ubuntu 10 definately use mod_fcgid 2.3.6+ $setmax = 1; } elsif ($gconfig{'os_type'} eq 'redhat-linux' && $gconfig{'os_version'} >= 14 && &foreign_check("software")) { # CentOS 6 and Fedora 14+ may have it.. &foreign_require("software"); local @pinfo = &software::package_info("mod_fcgid"); if (&compare_versions($pinfo[4], "2.3.6") >= 0) { $setmax = 1; } } } &apache::save_directive("FcgidMaxRequestLen", $setmax ? [ 1024*1024*1024 ] : [ ], $vconf, $conf); &flush_file_lines(); } # Update PHP mode cache $d->{'php_mode'} = $mode; local @vlist = map { $_->[0] } &list_available_php_versions($d); if ($mode !~ /mod_php|none/ && $oldmode =~ /mod_php|none/ && $d->{'last_php_version'} && &indexof($d->{'last_php_version'}, @vlist) >= 0) { # Restore PHP version from before mod_php or none modes my $err = &save_domain_php_directory($d, &public_html_dir($d), $d->{'last_php_version'}, 1); return $err if ($err); } if (defined($oldplog)) { # Restore the old PHP error log &save_domain_php_error_log($d, $oldplog); } # Link ~/etc/php.ini to the per-version ini file &create_php_ini_link($d, $mode); &create_php_bin_links($d, $mode); ®ister_post_action(\&restart_apache); $pfound || return "Apache virtual host was not found"; return undef; } # set_fcgid_max_execution_time(&domain, value, [mode], [port]) # Set the IPCCommTimeout directive to follow the given PHP max execution time sub set_fcgid_max_execution_time { local ($d, $max, $mode, $port) = @_; $mode ||= &get_domain_php_mode($d); return 0 if ($mode ne "fcgid"); local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_set_fcgid_max_execution_time", $d, $max, $mode, $port); } elsif (!$p) { return "Virtual server does not have a website"; } local @ports = ( $d->{'web_port'}, $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) ); @ports = ( $port ) if ($port); # Overridden to just do SSL or non-SSL local $conf = &apache::get_config(); local $pfound = 0; local $changed = 0; foreach my $p (@ports) { local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p); next if (!$vconf); $pfound++; local @newdir = &apache::find_directive("FcgidIOTimeout", $vconf); local $dirname = @newdir ? "FcgidIOTimeout" : "IPCCommTimeout"; local $oldvalue = &apache::find_directive($dirname, $vconf); local $want = $max ? $max + 1 : $max_php_fcgid_timeout; if ($oldvalue ne $want) { &apache::save_directive($dirname, [ $want ], $vconf, $conf); &flush_file_lines($virt->{'file'}); $changed++; } } $pfound || &error("Apache virtual host was not found"); if ($changed) { ®ister_post_action(\&restart_apache); } } # get_fcgid_max_execution_time(&domain) # Returns the current max FCGId execution time, or undef for unlimited sub get_fcgid_max_execution_time { local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_get_fcgid_max_execution_time", $d); } elsif (!$p) { return "Virtual server does not have a website"; } local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'}); local $v = &apache::find_directive("IPCCommTimeout", $vconf); $v ||= &apache::find_directive("FcgidIOTimeout", $vconf); return $v == 9999 ? undef : $v ? $v-1 : 40; } # set_php_max_execution_time(&domain, max) # Updates the max execution time in all php.ini files, and possibly in the FPM # config file sub set_php_max_execution_time { local ($d, $max) = @_; &foreign_require("phpini"); foreach my $ini (&list_domain_php_inis($d)) { local $f = $ini->[1]; local $conf = &phpini::get_config($f); &phpini::save_directive($conf, "max_execution_time", $max); &flush_file_lines($f); } my $mode = &get_domain_php_mode($d); if ($mode eq "fpm") { &save_php_fpm_ini_value($d, "max_execution_time", $max == 0 ? undef : $max); } } # get_php_max_execution_time(&domain) # Returns the max execution time from a php.ini file sub get_php_max_execution_time { local ($d, $max) = @_; &foreign_require("phpini"); foreach my $ini (&list_domain_php_inis($d)) { local $f = $ini->[1]; local $conf = &phpini::get_config($f); local $max = &phpini::find_value("max_execution_time", $conf); return $max if ($max ne ''); } return undef; } # create_php_wrappers(&domain, phpmode) # Creates all phpN.cgi wrappers for some domain sub create_php_wrappers { local ($d, $mode) = @_; local $dest = $mode eq "fcgid" ? "$d->{'home'}/fcgi-bin" : &cgi_bin_dir($_[0]); local $tmpl = &get_template($d->{'template'}); if (!-d $dest) { # Need to create fcgi-bin &make_dir_as_domain_user($d, $dest, 0755); } local $suffix = $mode eq "fcgid" ? "fcgi" : "cgi"; local $dirvar = $mode eq "fcgid" ? "PWD" : "DOCUMENT_ROOT"; # Make wrappers mutable &set_php_wrappers_writable($d, 1); # For each version of PHP, create a wrapper local $pub = &public_html_dir($d); local $children = &get_domain_php_children($d); foreach my $v (&list_available_php_versions($d, $mode)) { next if (!$v->[1]); # No executable available?! &open_tempfile_as_domain_user($d, PHP, ">$dest/php$v->[0].$suffix"); local $t = "php".$v->[0].$suffix; if ($tmpl->{$t} && $tmpl->{$t} ne 'none') { # Use custom script from template local $s = &substitute_domain_template($tmpl->{$t}, $d); $s =~ s/\t/\n/g; $s .= "\n" if ($s !~ /\n$/); &print_tempfile(PHP, $s); } else { # Automatically generate local $shell = -r "/bin/bash" ? "/bin/bash" : "/bin/sh"; local $common = "#!$shell\n". "PHPRC=\$$dirvar/../etc/php$v->[0]\n". "export PHPRC\n". "umask 022\n"; if ($mode eq "fcgid") { local $defchildren = $tmpl->{'web_phpchildren'}; $defchildren = undef if ($defchildren eq "none"); if ($defchildren) { $common .= "PHP_FCGI_CHILDREN=$defchildren\n"; $common .= "export PHP_FCGI_CHILDREN\n"; } $common .= "PHP_FCGI_MAX_REQUESTS=99999\n"; $common .= "export PHP_FCGI_MAX_REQUESTS\n"; } elsif ($mode eq "cgi") { $common .= "if [ \"\$REDIRECT_URL\" != \"\" ]; then\n"; $common .= " SCRIPT_NAME=\$REDIRECT_URL\n"; $common .= " export SCRIPT_NAME\n"; $common .= "fi\n"; } &print_tempfile(PHP, $common); if ($v->[1] =~ /-cgi$/) { # php-cgi requires the SCRIPT_FILENAME variable &print_tempfile(PHP, "SCRIPT_FILENAME=\$PATH_TRANSLATED\n"); &print_tempfile(PHP, "export SCRIPT_FILENAME\n"); } &print_tempfile(PHP, "exec $v->[1]\n"); } &close_tempfile_as_domain_user($d, PHP); &set_permissions_as_domain_user($d, 0755, "$dest/php$v->[0].$suffix"); # Put back the old number of child processes if ($children >= 0) { &save_domain_php_children($d, $children, 1); } # Also copy the .fcgi wrapper to public_html, which is needed due to # broken-ness on some Debian versions! if ($mode eq "fcgid" && $gconfig{'os_type'} eq 'debian-linux' && $gconfig{'os_version'} < 5) { ©_source_dest_as_domain_user( $d, "$dest/php$v->[0].$suffix", "$pub/php$v->[0].$suffix"); &set_permissions_as_domain_user( $d, 0755, "$pub/php$v->[0].$suffix"); } } # Re-apply resource limits if (defined(&supports_resource_limits) && &supports_resource_limits()) { local $pd = $d->{'parent'} ? &get_domain($d->{'parent'}) : $d; &set_php_wrapper_ulimits($d, &get_domain_resource_limits($pd)); } # Make wrappers immutable, to prevent deletion by users (which can crash Apache) &set_php_wrappers_writable($d, 0); } # set_php_wrappers_writable(&domain, flag, [subdomains-too]) # If possible, make PHP wrapper scripts mutable or immutable sub set_php_wrappers_writable { local ($d, $writable, $subs) = @_; if (&has_command("chattr")) { foreach my $dir ("$d->{'home'}/fcgi-bin", &cgi_bin_dir($d)) { foreach my $f (glob("$dir/php?.*cgi")) { my @st = stat($f); if (-r $f && !-l $f && $st[4] == $d->{'uid'}) { &system_logged("chattr ". ($writable ? "-i" : "+i")." ".quotemeta($f). " >/dev/null 2>&1"); } } } if ($subs) { # Also do sub-domains, as their CGI directories are under # parent's domain. foreach my $sd (&get_domain_by("subdom", $d->{'id'})) { &set_php_wrappers_writable($sd, $writable); } } } } # set_php_wrapper_ulimits(&domain, &resource-limits) # Add, update or remove ulimit lines to set RAM and process restrictions sub set_php_wrapper_ulimits { local ($d, $rv) = @_; foreach my $dir ("$d->{'home'}/fcgi-bin", &cgi_bin_dir($d)) { foreach my $f (glob("$dir/php?.*cgi")) { local $lref = &read_file_lines_as_domain_user($d, $f); foreach my $u ([ 'v', int($rv->{'mem'}/1024) ], [ 'u', $rv->{'procs'} ], [ 't', $rv->{'time'}*60 ]) { if ($u->[0] eq 't' && $dir eq "$d->{'home'}/fcgi-bin") { # CPU time limit makes no sense for fcgi, as it # breaks the long-running php-cgi processes next; } # Find current line local $lnum; for(my $i=0; $i<@$lref; $i++) { if ($lref->[$i] =~ /^ulimit\s+\-(\S)\s+(\d+)/ && $1 eq $u->[0]) { $lnum = $i; last; } } if ($lnum && $u->[1]) { # Set value $lref->[$lnum] = "ulimit -$u->[0] $u->[1]"; } elsif ($lnum && !$u->[1]) { # Remove limit splice(@$lref, $lnum, 1); } elsif (!$lnum && $u->[1]) { # Add at top of file splice(@$lref, 1, 0, "ulimit -$u->[0] $u->[1]"); } } # If using process limits, we can't exec PHP as there will # be no chance for the limit to be applied :( local $ll = scalar(@$lref) - 1; if ($lref->[$ll] =~ /php/) { if ($rv->{'procs'} && $lref->[$ll] =~ /^exec\s+(.*)/) { # Remove exec $lref->[$ll] = $1; } elsif (!$rv->{'procs'} && $lref->[$ll] !~ /^exec\s+/) { # Add exec $lref->[$ll] = "exec ".$lref->[$ll]; } } &flush_file_lines_as_domain_user($d, $f); } } } # set_php_fpm_ulimits(&domain, &resource-limits) # Update the FPM config with resource limits sub set_php_fpm_ulimits { my ($d, $res) = @_; my $conf = &get_php_fpm_config($d); return 0 if (!$conf); if ($res->{'procs'}) { my $php_max_children = $res->{'procs'} > $max_php_fcgid_children ? $max_php_fcgid_children : $res->{'procs'}; &save_php_fpm_config_value($d, "pm.max_children", $php_max_children); &save_php_fpm_config_value($d, "pm.start_servers", &get_php_start_servers($php_max_children)); &save_php_fpm_config_value($d, "pm.max_spare_servers", &get_php_max_spare_servers($php_max_children)); } else { my $tmpl = &get_template($d->{'template'}); my $php_max_children = $tmpl->{'web_phpchildren'}; $php_max_children = get_php_max_childred_allowed() if ($php_max_children eq "none" || !$php_max_children); &save_php_fpm_config_value($d, "pm.max_children", $php_max_children); &save_php_fpm_config_value($d, "pm.start_servers", &get_php_start_servers($php_max_children)); &save_php_fpm_config_value($d, "pm.max_spare_servers", &get_php_max_spare_servers($php_max_children)); } ®ister_post_action(\&restart_php_fpm_server, $conf); } # supported_php_modes([&domain]) # Returns a list of PHP execution modes possible for a domain sub supported_php_modes { my ($d) = @_; my $p = &domain_has_website($d); if ($p ne 'web') { return &plugin_call($p, "feature_web_supported_php_modes", $d); } my @rv = ( "none" ); foreach my $ver (&list_available_php_versions()) { push(@rv, @{$ver->[2]}); } return &unique(@rv); } # php_mode_numbers_map() # Returns a map from mode names (like 'cgi') to template numbers sub php_mode_numbers_map { return { 'mod_php' => 0, 'cgi' => 1, 'fcgid' => 2, 'fpm' => 3, 'none' => 4, }; } # list_available_php_versions([&domain], [forcemode]) # Returns a list of PHP versions and their executables installed on the system, # for use by a domain. If forcemode is not set, the mode is taken from the # domain. sub list_available_php_versions { local ($d, $mode) = @_; if ($d) { $mode ||= &get_domain_php_mode($d); } return () if ($mode eq "none"); &require_apache(); # In FPM mode, only the versions for which packages are installed can be used my @rv; if ($mode eq "fpm" || !$mode) { foreach my $conf (grep { !$_->{'err'} } &list_php_fpm_configs()) { my $ver = $conf->{'shortversion'}; my $cmd = $conf->{'cmd'} || &php_command_for_version($ver, 0); if (!$cmd && $ver =~ /^5\./) { # Try just PHP version 5 $ver = 5; $cmd = &php_command_for_version($ver, 0); } $cmd ||= &has_command("php"); if ($cmd) { push(@rv, [ $ver, $cmd, ["fpm"] ]); } } } # Add the mod_php version if installed if ($mode eq "mod_php" || !$mode) { my $v = &get_apache_mod_php_version(); if ($v) { my $cmd = &has_command("php$v") || &has_command("php"); push(@rv, [ $v, $cmd, ["mod_php"] ]); } } # For CGI and fCGId modes, check which PHP commands exist foreach my $v (@all_possible_php_versions) { my $phpn = &php_command_for_version($v, 1); $vercmds{$v} = $phpn if ($phpn); } # Add extra configured PHP commands, and determine their versions foreach my $path (split(/\t+/, $config{'php_paths'})) { next if (!-x $path); &clean_environment(); local $out = &backquote_command("$path -v 2>&1 </dev/null"); &reset_environment(); if ($out =~ /PHP\s+(\d+.\d+)/ && !$vercmds{$1}) { $vercmds{$1} = $path; } } local $php = &has_command("php-cgi"); if ($php && scalar(keys %vercmds) != scalar(@all_possible_php_versions)) { # What version is the php command? If it is a version we don't have # a command for yet, use it. if (!$php_command_version_cache) { &clean_environment(); local $out = &backquote_command("$php -v 2>&1 </dev/null"); &reset_environment(); if ($out =~ /PHP\s+(\d+\.\d+)/) { my $v = $1; $v = int($v) if (int($v) <= 5); $php_command_version_cache = $v; } } if ($php_command_version_cache) { $vercmds{$php_command_version_cache} ||= $php; } } # Add versions found to the final list foreach my $v (sort { $a <=> $b } (keys %vercmds)) { my ($already) = grep { $_->[0] eq $v } @rv; if ($already) { $already->[2] = [ &unique(@{$already->[2]}, "fcgid", "cgi") ]; } else { push(@rv, [ $v, $vercmds{$v}, ["fcgid", "cgi"] ]); } } # If a mode was given, filter down to it if ($mode) { @rv = grep { &indexof($mode, @{$_->[2]}) >= 0 } @rv; } @rv = sort { $a->[0] <=> $b->[0] } @rv; return @rv; } # php_command_for_version(ver, [cgi-mode]) # Given a version like 5.4 or 5, returns the full path to the PHP executable sub php_command_for_version { my ($v, $cgimode) = @_; $cgimode ||= 0; if (!$php_command_for_version_cache{$v,$cgimode}) { my @opts; if ($gconfig{'os_type'} eq 'solaris') { # On Solaris with CSW packages, php-cgi is in a directory named # after the PHP version push(@opts, "/opt/csw/php$v/bin/php-cgi"); } push(@opts, "php$v-cgi", "php-cgi$v", "php$v"); $v =~ s/^(\d+\.\d+)\.\d+$/$1/; my $nodotv = $v; $nodotv =~ s/\.//; if ($nodotv ne $v) { # For a version like 5.4, check for binaries like php54 and # /opt/rh/php54/root/usr/bin/php push(@opts, "php$nodotv-cgi", "php-cgi$nodotv", "/opt/rh/php$nodotv/root/usr/bin/php-cgi", "/opt/rh/rh-php$nodotv/root/usr/bin/php-cgi", "/opt/atomic/atomic-php$nodotv/root/usr/bin/php-cgi", "/opt/atomic/atomic-php$nodotv/root/usr/bin/php", "/opt/rh/php$nodotv/bin/php-cgi", "/opt/remi/php$nodotv/root/usr/bin/php-cgi", "php$nodotv", "/opt/rh/php$nodotv/root/usr/bin/php", "/opt/rh/rh-php$nodotv/root/usr/bin/php", "/opt/rh/php$nodotv/bin/php", glob("/opt/phpfarm/inst/bin/php-cgi-$v.*")); } if ($cgimode == 1) { # Only include -cgi commands @opts = grep { /-cgi/ } @opts; } elsif ($cgimode == 2) { # Skip -cgi commands @opts = grep { !/-cgi/ } @opts; } my $phpn; foreach my $o (@opts) { $phpn = &has_command($o); last if ($phpn); } $php_command_for_version_cache{$v,$cgimode} = $phpn; } return $php_command_for_version_cache{$v,$cgimode}; } # get_php_version(number|command, [&domain]) # Given a PHP based version like 4 or 5, or a path to PHP, return the real # version number, like 5.2.7 sub get_php_version { local ($cmd, $d) = @_; if (exists($get_php_version_cache{$cmd})) { return $get_php_version_cache{$cmd}; } if ($cmd !~ /^\//) { # A number was given .. find the matching command my $shortcmd = $cmd; $shortcmd =~ s/^(\d+\.\d+)\..*/$1/; # Reduce version to 5.x local ($phpn) = grep { $_->[0] == $cmd || $_->[0] == $shortcmd } &list_available_php_versions($d); if (!$phpn && $cmd =~ /^5\./) { # Also try just version '5' ($phpn) = grep { $_->[0] == 5 } &list_available_php_versions($d); } if (!$phpn && $cmd == 5) { # If the system ONLY has PHP 7, consider it compatible with # PHP major version 5 ($phpn) = grep { $_->[0] >= $cmd } &list_available_php_versions($d); } if (!$phpn) { $get_php_version_cache{$cmd} = undef; return undef; } $cmd = $phpn->[1] || &has_command("php$cmd") || &has_command("php"); } &clean_environment(); local $out = &backquote_command("$cmd -v 2>&1 </dev/null"); &reset_environment(); if ($out =~ /PHP\s+([0-9\.]+)/) { $get_php_version_cache{$cmd} = $1; return $1; } $get_php_version_cache{$cmd} = undef; return undef; } # can_domain_php_directories(&domain, [mode]) # Returns 1 if the PHP version can be set on specific directories sub can_domain_php_directories { my ($d, $mode) = @_; local $p = &domain_has_website($d); if ($p && $p ne 'web' && &plugin_defined($p, "feature_can_domain_php_directories")) { return &plugin_call($p, "feature_can_domain_php_directories", $d, $mode); } $mode ||= &get_domain_php_mode($d); return $mode eq "fpm" ? 0 : 1; } # list_domain_php_directories(&domain) # Returns a list of directories for which different versions of PHP have # been configured. sub list_domain_php_directories { local ($d) = @_; local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_list_web_php_directories", $d); } elsif (!$p) { return "Virtual server does not have a website"; } &require_apache(); local $conf = &apache::get_config(); local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'}); return ( ) if (!$virt); local $mode = &get_domain_php_mode($d); if ($mode eq "mod_php") { # All are run as version from Apache module local @avail = &list_available_php_versions($d, $mode); if (@avail) { return ( { 'dir' => &public_html_dir($d), 'version' => $avail[0]->[0], 'mode' => $mode } ); } else { return ( ); } } elsif ($mode eq "fpm") { # Version is stored in the domain's config return ( { 'dir' => &public_html_dir($d), 'version' => $d->{'php_fpm_version'}, 'mode' => $mode } ); } elsif ($mode eq "none") { # No PHP, so no directories return ( ); } # Find directories with either FCGIWrapper or AddType directives, and check # which version they specify for .php files local @dirs = &apache::find_directive_struct("Directory", $vconf); local @rv; foreach my $dir (@dirs) { local $n = $mode eq "cgi" ? "AddType" : $mode eq "fcgid" ? "FCGIWrapper" : undef; foreach my $v (&apache::find_directive($n, $dir->{'members'})) { local $w = &apache::wsplit($v); if (&indexof(".php", @$w) > 0) { # This is for .php files .. look at the php version if ($w->[0] =~ /php([0-9\.]+)\.(cgi|fcgi)/ || $w->[0] =~ /x-httpd-php([0-9\.]+)/) { # Add version and dir to list push(@rv, { 'dir' => $dir->{'words'}->[0], 'version' => $1, 'mode' => $mode }); last; } } } } return @rv; } # save_domain_php_directory(&domain, dir, phpversion, [skip-ini-copy]) # Sets up a directory to run PHP scripts with a specific version of PHP. # Should only be called on domains in cgi or fcgid mode! Returns undef on # success, or an error message on failure (ie. because the virtualhost couldn't # be found, or the PHP mode was wrong) sub save_domain_php_directory { local ($d, $dir, $ver, $noini) = @_; local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_save_web_php_directory", $d, $dir, $ver); } elsif (!$p) { return "Virtual server does not have a website!"; } &require_apache(); local $mode = &get_domain_php_mode($d); return "PHP versions cannot be set in mod_php mode" if ($mode eq "mod_php"); local $oldlog = &get_domain_php_error_log($d); if ($mode eq "fpm") { # Remove the old version pool and create a new one if needed. # Since it will be on the same port, no Apache changes are needed. my $phd = &public_html_dir($d); $dir eq $phd || return "FPM version can only be changed for the top-level directory"; if ($ver ne $d->{'php_fpm_version'}) { my $oldlisten = &get_php_fpm_config_value($d, "listen"); my $confs = &list_php_fpm_config_values($d); my @phpvs = ©able_fpm_configs($confs); &delete_php_fpm_pool($d); $d->{'php_fpm_version'} = $ver; &save_domain($d); &create_php_fpm_pool($d, $oldlisten); foreach my $pv (@phpvs) { &save_php_fpm_config_value($d, $pv->[0], $pv->[1]); } } } else { # Config needs to be updated for each Apache virtualhost local $any = 0; local @ports = ( $d->{'web_port'}, $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) ); local %allvers = map { $_, 1 } @all_possible_php_versions; foreach my $p (@ports) { local $conf = &apache::get_config(); local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p); next if (!$virt); # Check for an existing <Directory> block local @dirs = &apache::find_directive_struct("Directory", $vconf); local ($dirstr) = grep { $_->{'words'}->[0] eq $dir } @dirs; if ($dirstr) { # Update the AddType or FCGIWrapper directives, so that # .php scripts use the specified version, and all other # .phpN use version N. if ($mode eq "cgi") { local @types = &apache::find_directive( "AddType", $dirstr->{'members'}); @types = grep { $_ !~ /^application\/x-httpd-php[57]/ } @types; foreach my $v (&list_available_php_versions($d)) { push(@types, "application/x-httpd-php$v->[0] ". ".php$v->[0]"); } push(@types, "application/x-httpd-php$ver .php"); &apache::save_directive("AddType", \@types, $dirstr->{'members'}, $conf); &flush_file_lines($dirstr->{'file'}); } elsif ($mode eq "fcgid") { local $dest = "$d->{'home'}/fcgi-bin"; local @wrappers = &apache::find_directive( "FCGIWrapper", $dirstr->{'members'}); @wrappers = grep { !(/^\Q$dest\E\/php\S+\.fcgi\s+\.php(\S*)$/ && ($1 eq '' || $allvers{$1})) } @wrappers; foreach my $v (&list_available_php_versions($d)) { push(@wrappers, "$dest/php$v->[0].fcgi .php$v->[0]"); } push(@wrappers, "$dest/php$ver.fcgi .php"); @wrappers = &unique(@wrappers); &apache::save_directive("FCGIWrapper", \@wrappers, $dirstr->{'members'}, $conf); &flush_file_lines($dirstr->{'file'}); } } else { # Add the directory local @phplines; if ($mode eq "cgi") { # Directives for plain CGI foreach my $v (&list_available_php_versions($d)) { push(@phplines, "Action application/x-httpd-php$v->[0] ". "/cgi-bin/php$v->[0].cgi"); push(@phplines, "AddType application/x-httpd-php$v->[0] ". ".php$v->[0]"); } push(@phplines, "AddType application/x-httpd-php$ver .php"); } elsif ($mode eq "fcgid") { # Directives for fcgid local $dest = "$d->{'home'}/fcgi-bin"; push(@phplines, "AddHandler fcgid-script .php"); push(@phplines, "FCGIWrapper $dest/php$ver.fcgi .php"); foreach my $v (&list_available_php_versions($d)) { push(@phplines, "AddHandler fcgid-script .php$v->[0]"); push(@phplines, "FCGIWrapper $dest/php$v->[0].fcgi ". ".php$v->[0]"); } } my $olist = $apache::httpd_modules{'core'} >= 2.2 ? " ".&get_allowed_options_list() : ""; my $granteddir = "Require all granted"; if ($apache::httpd_modules{'core'} < 2.4) { $granteddir = "Allow from all"; } local @lines = ( " <Directory $dir>", " Options +IncludesNOEXEC +SymLinksifOwnerMatch +ExecCGI", " $granteddir", " AllowOverride All".$olist, (map { " ".$_ } @phplines), " </Directory>" ); local $lref = &read_file_lines($virt->{'file'}); splice(@$lref, $virt->{'eline'}, 0, @lines); &flush_file_lines($virt->{'file'}); undef(@apache::get_config_cache); } $any++; } return "No Apache virtualhosts found" if (!$any); } # Make sure we have all the wrapper scripts &create_php_wrappers($d, $mode); # Re-create php.ini link &create_php_ini_link($d, $mode); &create_php_bin_links($d, $mode); # Copy in php.ini file for version if missing if (!$noini) { my @inifiles = &find_domain_php_ini_files($d); my ($iniver) = grep { $_->[0] eq $ver } @inifiles; if (!$iniver) { &save_domain_php_mode($d, $mode); } } # Re-save PHP version &save_domain_php_error_log($d, $oldlog); ®ister_post_action(\&restart_apache); return undef; } # delete_domain_php_directory(&domain, dir) # Delete the <Directory> section for a custom PHP version in some directory sub delete_domain_php_directory { local ($d, $dir) = @_; local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_delete_web_php_directory", $d, $dir); } elsif (!$p) { return "Virtual server does not have a website"; } &require_apache(); local $conf = &apache::get_config(); local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'}); return 0 if (!$virt); local $mode = &get_domain_php_mode($d); local @dirs = &apache::find_directive_struct("Directory", $vconf); local ($dirstr) = grep { $_->{'words'}->[0] eq $dir } @dirs; if ($dirstr) { local $lref = &read_file_lines($dirstr->{'file'}); splice(@$lref, $dirstr->{'line'}, $dirstr->{'eline'}-$dirstr->{'line'}+1); &flush_file_lines($dirstr->{'file'}); undef(@apache::get_config_cache); ®ister_post_action(\&restart_apache); return 1; } return 0; } # list_domain_php_inis(&domain, [force-mode]) # Returns a list of php.ini files used by a domain, and their PHP versions and # commands. sub list_domain_php_inis { local ($d, $mode) = @_; local @inis; foreach my $v (&list_available_php_versions($d, $mode)) { local $ifile = "$d->{'home'}/etc/php$v->[0]/php.ini"; if (-r $ifile) { push(@inis, [ $v->[0], $ifile, $v->[1] ]); } } if (!@inis) { local $ifile = "$d->{'home'}/etc/php.ini"; if (-r $ifile) { push(@inis, [ undef, $ifile, undef ]); } } return @inis; } # find_domain_php_ini_files(&domain) # Returns the same information as list_domain_php_inis, but looks at files under # the home directory only sub find_domain_php_ini_files { local ($d) = @_; local @inis; foreach my $f (glob("$d->{'home'}/etc/php*/php.ini")) { if ($f =~ /php([0-9\.]+)\/php.ini$/) { push(@inis, [ $1, $f ]); } } return @inis; } # get_domain_php_ini(&domain, php-version, [dir-only]) # Returns the php.ini file path for this domain and a PHP version sub get_domain_php_ini { local ($d, $phpver, $dir) = @_; local @inis = &list_domain_php_inis($d); local ($ini) = grep { $_->[0] == $phpver } @inis; if (!$ini) { ($ini) = grep { !$_->[0]} @inis; } if (!$ini && -r "$d->{'home'}/etc/php.ini") { # For domains with no matching version file $ini = [ undef, "$d->{'home'}/etc/php.ini" ]; } if (!$ini) { return undef; } else { $ini->[1] =~ s/\/php.ini$//i if ($dir); return $ini->[1]; } } # get_global_php_ini(phpver, mode) # Returns the full path to the global PHP config file sub get_global_php_ini { local ($ver, $mode) = @_; local $nodotv = $ver; $nodotv =~ s/\.//g; local $shortv = $ver; $shortv =~ s/^(\d+\.\d+)\..*$/$1/g; foreach my $i ("/opt/rh/php$nodotv/root/etc/php.ini", "/opt/rh/php$nodotv/lib/php.ini", "/etc/opt/rh/rh-php$nodotv/php.ini", "/opt/remi/php$nodotv/root/etc/php.ini", "/etc/opt/remi/php$nodotv/php.ini", "/opt/atomic/atomic-php$nodotv/root/etc/php.ini", "/etc/php.ini", $mode eq "mod_php" ? ("/etc/php$ver/apache/php.ini", "/etc/php$ver/apache2/php.ini", "/etc/php$nodotv/apache/php.ini", "/etc/php$nodotv/apache2/php.ini", "/etc/php$shortv/apache/php.ini", "/etc/php$shortv/apache2/php.ini", ) : ("/etc/php$ver/cgi/php.ini", "/etc/php$nodotv/cgi/php.ini", "/etc/php$shortv/cgi/php.ini", "/etc/php/$ver/cgi/php.ini", "/etc/php/$nodotv/cgi/php.ini", "/etc/php/$shortv/cgi/php.ini", ), "/opt/csw/php$ver/lib/php.ini", "/usr/local/lib/php.ini", "/usr/local/etc/php.ini", "/usr/local/etc/php.ini-production") { return $i if (-r $i); } return undef; } # get_php_mysql_socket(&domain) # Returns the PHP mysql socket path to use for some domain, from the # global config file. Returns 'none' if not possible, or an empty string # if not set. sub get_php_mysql_socket { local ($d) = @_; return 'none' if (!&foreign_check("phpini")); local $mode = &get_domain_php_mode($d); local @vers = &list_available_php_versions($d, $mode); return 'none' if (!@vers); local $tmpl = &get_template($d->{'template'}); local $inifile = $tmpl->{'web_php_ini_'.$vers[0]->[0]}; if (!$inifile || $inifile eq "none" || !-r $inifile) { $inifile = &get_global_php_ini($vers[0]->[0], $mode); } &foreign_require("phpini"); local $gconf = &phpini::get_config($inifile); local $sock = &phpini::find_value("mysql.default_socket", $gconf); return $sock; } # get_domain_php_fpm_mode(&domain) # Get the PHP-FPM process manager mode for some domain sub get_domain_php_fpm_mode { my ($d) = @_; my $pm = &get_php_fpm_config_value($d, "pm"); return $pm || 'dynamic'; } # get_domain_php_children(&domain) # For a domain using fcgi to run PHP, returns the number of child processes. # Returns 0 if not set, -1 if the file doesn't even exist, -2 if not supported sub get_domain_php_children { my ($d) = @_; my $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_get_web_php_children", $d); } elsif (!$p) { return "Virtual server does not have a website"; } my $mode = &get_domain_php_mode($d); if ($mode eq "fcgid") { # Set in wrapper script my ($ver) = &list_available_php_versions($d, "fcgid"); return -2 if (!$ver); my $childs = 0; &open_readfile_as_domain_user($d, WRAPPER, "$d->{'home'}/fcgi-bin/php$ver->[0].fcgi") || return -1; while(<WRAPPER>) { if (/^PHP_FCGI_CHILDREN\s*=\s*(\d+)/) { $childs = $1; } } &close_readfile_as_domain_user($d, WRAPPER); return $childs; } elsif ($mode eq "fpm") { # Set in pool config file my $conf = &get_php_fpm_config($d); return -1 if (!$conf); my $file = $conf->{'dir'}."/".$d->{'id'}.".conf"; my $lref = &read_file_lines($file, 1); my $childs = 0; foreach my $l (@$lref) { if ($l =~ /pm\.max_children\s*=\s*(\d+)/) { $childs = $1; } } &unflush_file_lines($file); return $childs == get_php_max_childred_allowed() ? 0 : $childs; } else { return -2; } } # save_domain_php_fpm_mode(&domain, mode) # Save the PHP-FPM process manager mode for some domain sub save_domain_php_fpm_mode { my ($d, $modetype) = @_; &save_php_fpm_config_value($d, 'pm', $modetype); } # save_domain_php_children(&domain, children, [no-writable]) # Update all of a domain's PHP wrapper scripts with the new number of children sub save_domain_php_children { local ($d, $children, $nowritable) = @_; local $p = &domain_has_website($d); if ($p && $p ne 'web') { return &plugin_call($p, "feature_save_web_php_children", $d, $children, $nowritable); } elsif (!$p) { return "Virtual server does not have a website"; } my $mode = &get_domain_php_mode($d); if ($mode eq "fcgid") { # Update in FCGI wrapper scripts local $count = 0; &set_php_wrappers_writable($d, 1) if (!$nowritable); foreach my $ver (&list_available_php_versions($d, "fcgi")) { local $wrapper = "$d->{'home'}/fcgi-bin/php$ver->[0].fcgi"; next if (!-r $wrapper); # Find the current line local $lref = &read_file_lines_as_domain_user($d, $wrapper); local $idx; for(my $i=0; $i<@$lref; $i++) { if ($lref->[$i] =~ /PHP_FCGI_CHILDREN\s*=\s*\d+/) { $idx = $i; } } # Update, remove or add if ($children && defined($idx)) { $lref->[$idx] = "PHP_FCGI_CHILDREN=$children"; } elsif (!$children && defined($idx)) { splice(@$lref, $idx, 1); } elsif ($children && !defined($idx)) { # Add before export line local $found = 0; for(my $e=0; $e<@$lref; $e++) { if ($lref->[$e] =~ /^export\s+PHP_FCGI_CHILDREN/) { splice(@$lref, $e, 0, "PHP_FCGI_CHILDREN=$children"); $found++; last; } } if (!$found) { # Add both lines at top splice(@$lref, 1, 0, "PHP_FCGI_CHILDREN=$children", "export PHP_FCGI_CHILDREN"); } } &flush_file_lines_as_domain_user($d, $wrapper); } &set_php_wrappers_writable($d, 0) if (!$nowritable); ®ister_post_action(\&restart_apache); return 1; } elsif ($mode eq "fpm") { # Update in FPM pool file my $conf = &get_php_fpm_config($d); return 0 if (!$conf); my $file = $conf->{'dir'}."/".$d->{'id'}.".conf"; return 0 if (!-r $file); &lock_file($file); my $lref = &read_file_lines($file); $children = get_php_max_childred_allowed() if ($children == 0); # Recommended default foreach my $l (@$lref) { if ($l =~ /pm\.max_children\s*=\s*(\d+)/) { $l = "pm.max_children = $children"; } if ($l =~ /pm\.start_servers\s*=\s*(\d+)/) { $l = "pm.start_servers = " . get_php_start_servers($children) . ""; } if ($l =~ /pm\.max_spare_servers\s*=\s*(\d+)/) { $l = "pm.max_spare_servers = " . get_php_max_spare_servers($children) . ""; } } &flush_file_lines($file); &unlock_file($file); ®ister_post_action(\&restart_php_fpm_server, $conf); return 1; } else { return 0; } } # check_php_configuration(&domain, php-version, php-command) # Returns an error message if the domain's PHP config is invalid sub check_php_configuration { local ($d, $ver, $cmd) = @_; $cmd ||= &has_command("php".$ver) || &has_command("php"); local $mode = &get_domain_php_mode($d); if ($mode eq "mod_php") { local $gini = &get_global_php_ini($ver, $mode); if ($gini) { $gini =~ s/\/php.ini$//; $ENV{'PHPRC'} = $gini; } } else { $ENV{'PHPRC'} = &get_domain_php_ini($d, $ver, 1); } &clean_environment(); local $out = &backquote_command("$cmd -d error_log= -m 2>&1"); local @errs; foreach my $l (split(/\r?\n/, $out)) { $l = &html_tags_to_text($l); if ($l =~ /(PHP\s+)?Fatal\s+error:\s*(.*)/) { my $msg = $2; $msg =~ s/\s+in\s+\S+\s+on\s+line\s+\d+//; push(@errs, $msg); } } &reset_environment(); delete($ENV{'PHPRC'}); return join(", ", @errs); } # list_php_modules(&domain, php-version, php-command) # Returns a list of PHP modules available for some domain. Uses caching. sub list_php_modules { local ($d, $ver, $cmd) = @_; local $mode = &get_domain_php_mode($d); if (!defined($main::php_modules{$ver,$d->{'id'}})) { $cmd ||= &has_command("php".$ver) || &has_command("php"); $main::php_modules{$ver} = [ ]; if ($mode eq "mod_php" || $mode eq "fpm") { # Use global PHP config, since with mod_php we can't do # per-domain configurations local $gini = &get_global_php_ini($ver, $mode); if ($gini) { $gini =~ s/\/php.ini$//; $ENV{'PHPRC'} = $gini; } } elsif ($d) { # Use domain's php.ini $ENV{'PHPRC'} = &get_domain_php_ini($d, $ver, 1); } &clean_environment(); local $_; &open_execute_command(PHP, "$cmd -m", 1); while(<PHP>) { s/\r|\n//g; if (/^\S+$/ && !/\[/) { push(@{$main::php_modules{$ver,$d->{'id'}}}, $_); } } close(PHP); &reset_environment(); delete($ENV{'PHPRC'}); } return @{$main::php_modules{$ver,$d->{'id'}}}; } # fix_php_ini_files(&domain, &fixes) # Updates values in all php.ini files in a domain. The fixes parameter is # a list of array refs, containing old values, new value and regexp flag. # If the old value is undef, anything matches. May print stuff. Returns the # number of changes made. sub fix_php_ini_files { local ($d, $fixes) = @_; local ($mode, $rv); if (defined(&get_domain_php_mode) && ($mode = &get_domain_php_mode($d)) && $mode ne "mod_php" && $mode ne "fpm" && &foreign_check("phpini")) { &foreign_require("phpini"); &$first_print($text{'save_apache10'}); foreach my $i (&list_domain_php_inis($d)) { &unflush_file_lines($i->[1]); # In case cached undef($phpini::get_config_cache{$i->[1]}); local $pconf = &phpini::get_config($i->[1]); foreach my $f (@$fixes) { local $ov = &phpini::find_value($f->[0], $pconf); local $nv = $ov; if (!defined($f->[1])) { # Always change $nv = $f->[2]; } elsif ($f->[3] && $ov =~ /\Q$f->[1]\E/) { # Regexp change $nv =~ s/\Q$f->[1]\E/$f->[2]/g; } elsif (!$f->[3] && $ov eq $f->[1]) { # Exact match change $nv = $f->[2]; } if ($nv ne $ov) { # Update in file &phpini::save_directive($pconf, $f->[0], $nv); &flush_file_lines($i->[1]); $rv++; } } } &$second_print($text{'setup_done'}); } return $rv; } # fix_php_fpm_pool_file(&domain, &fixes) # Updates values in PHP FPM config file sub fix_php_fpm_pool_file { my ($d, $fixes) = @_; my ($mode, $rv, $conf, $file); if (defined(&get_domain_php_mode) && ($mode = &get_domain_php_mode($d)) && $mode eq "fpm" && &foreign_check("phpini")) { &foreign_require("phpini"); $conf = &get_php_fpm_config($d); if ($conf) { $file = $conf->{'dir'}."/".$d->{'id'}.".conf"; } if (-r $file) { &$first_print($text{'save_apache12'}); &unflush_file_lines($file); # In case cached undef($phpini::get_config_cache{$file}); my $fpmconf = &phpini::get_config($file); foreach my $f (@{$fixes}) { my $ov = &phpini::find_value($f->[0], $fpmconf); my $nv = $ov; if (!defined($f->[1])) { # Always change $nv = $f->[2]; } elsif ($f->[3] && $ov =~ /\Q$f->[1]\E/) { # Regexp change $nv =~ s/\Q$f->[1]\E/$f->[2]/; } elsif (!$f->[3] && $ov eq $f->[1]) { # Exact match change $nv = $f->[2]; } if ($nv ne $ov) { # Update in file &phpini::save_directive($fpmconf, $f->[0], $nv); &flush_file_lines($file); $rv++; } } if ($rv) { &$second_print($text{'setup_done'}); } else { &$second_print($text{'save_apache12none'}); } } } return $rv; } # fix_php_extension_dir(&domain) # If the extension_dir in a domain's php.ini file is invalid, try to fix it sub fix_php_extension_dir { local ($d) = @_; return if (!&foreign_check("phpini")); &foreign_require("phpini"); foreach my $i (&list_domain_php_inis($d)) { local $pconf = &phpini::get_config($i->[1]); local $ed = &phpini::find_value("extension_dir", $pconf); if ($ed && !-d $ed) { # Doesn't exist .. maybe can fix my $newed = $ed; if ($newed =~ /\/lib\//) { $newed =~ s/\/lib\//\/lib64\//; } elsif ($newed =~ /\/lib64\//) { $newed =~ s/\/lib64\//\/lib\//; } if (!-d $newed) { # Couldn't find it, give up and clear $newed = undef; } &phpini::save_directive($pconf, "extension_dir", $newed); } } } # get_domain_php_version(&domain, [php-mode]) # Get the PHP version used by the domain by default (for public_html) sub get_domain_php_version { local ($d, $mode) = @_; $mode ||= &get_domain_php_mode($d); if ($mode ne "mod_php") { local @dirs = &list_domain_php_directories($d); local $phd = &public_html_dir($d); local ($hdir) = grep { $_->{'dir'} eq $phd } @dirs; $hdir ||= $dirs[0]; return $hdir ? $hdir->{'version'} : undef; } return undef; } # create_php_ini_link(&domain, [php-mode]) # Create a link from etc/php.ini to the PHP version used by the domain's # public_html directory sub create_php_ini_link { local ($d, $mode) = @_; local $ver = &get_domain_php_version($d, $mode); if ($ver) { local $etc = "$d->{'home'}/etc"; &unlink_file_as_domain_user($d, "$etc/php.ini"); &symlink_file_as_domain_user($d, "php".$ver."/php.ini", "$etc/php.ini"); } } # create_php_bin_links(&domain, [php-mode]) # Create a link from ~/bin/php to the PHP CLI for the primary version sub create_php_bin_links { my ($d, $mode) = @_; return if ($mode eq "none"); my $bindir = $d->{'home'}.'/bin'; &unlink_file_as_domain_user($d, $bindir.'/php'); my ($ver, $cli); if (($ver = &get_domain_php_version($d, $mode)) && ($cli = &php_command_for_version($ver, 2))) { &make_dir_as_domain_user($d, $bindir, 0755); &symlink_file_as_domain_user($d, $cli, $bindir.'/php'); } } # get_php_fpm_config([version|&domain]) # Returns the first valid FPM config for a domain sub get_php_fpm_config { my ($ver) = @_; if (ref($ver)) { $ver = $ver->{'php_fpm_version'}; } my @confs = grep { !$_->{'err'} } &list_php_fpm_configs(); if ($ver) { @confs = grep { $_->{'version'} eq $ver || $_->{'shortversion'} eq $ver } @confs; } return @confs ? $confs[0] : undef; } # get_php_fpm_config_file(&domain) # Returns the FPM pool config file for a domain sub get_php_fpm_config_file { my ($d) = @_; my $conf = &get_php_fpm_config($d); return undef if (!$conf); return $conf->{'dir'}."/".$d->{'id'}.".conf"; } # list_php_fpm_configs() # Returns hash refs with details of the system's php-fpm configurations. Assumes # use of standard packages. sub list_php_fpm_configs { if ($php_fpm_config_cache) { return @$php_fpm_config_cache; } # What version packages are installed? return ( ) if (!&foreign_installed("software")); &foreign_require("software"); return ( ) if (!defined(&software::package_info)); my @rv; my %donever; # Get all interesting packages my @pkgnames = ("php-fpm", (map { "php${_}-fpm" } @all_possible_short_php_versions), (map { my $v = $_; $v =~ s/\.//g; ("php${v}-php-fpm", "php${v}-fpm", "php${v}w-fpm", "rh-php${v}-php-fpm", "php${_}-fpm", "php${v}u-fpm") } @all_possible_php_versions)); if (&get_webmin_version() >= 1.985 || $software::config{'package_system'} eq 'debian') { &software::list_packages(@pkgnames); } else { &software::list_packages(); } my %pkgs; for(my $i=0; $software::packages{$i,'name'}; $i++) { $pkgs{$software::packages{$i,'name'}} = [ $software::packages{$i,'name'}, $software::packages{$i,'class'}, $software::packages{$i,'desc'}, $software::packages{$i,'arch'}, $software::packages{$i,'version'} ]; } foreach my $pname (@pkgnames) { my $pinfo = $pkgs{$pname}; next if (!$pinfo); # The php-fpm package on Ubuntu is just a meta-package if ($pname eq "php-fpm" && $pinfo->[3] eq "all" && $gconfig{'os_type'} eq 'debian-linux') { next; } # Normalize the version my $rv = { 'package' => $pname }; $rv->{'version'} = $pinfo->[4]; $rv->{'version'} =~ s/\-.*$//; $rv->{'version'} =~ s/\+.*$//; $rv->{'version'} =~ s/^\d+://; next if ($donever{$rv->{'version'}}++); $rv->{'shortversion'} = $rv->{'version'}; $rv->{'shortversion'} =~ s/^(\d+\.\d+)\..*/$1/; # Reduce version to 5.x if (($pname eq "php-fpm" || $pname eq "php5-fpm") && $rv->{'shortversion'} =~ /^5/) { # For historic reasons, we just use the version number '5' for # the first PHP 5.x version on the system. $rv->{'shortversion'} = 5; } $rv->{'pkgversion'} = $rv->{'shortversion'}; $rv->{'pkgversion'} =~ s/\.//g; push(@rv, $rv); # Config directory for per-domain pool files my @verdirs; DIR: foreach my $cdir ("/etc/php-fpm.d", "/etc/php*/fpm/pool.d", "/etc/php*/fpm/php-fpm.d", "/etc/php/*/fpm/pool.d", "/etc/opt/remi/php*/php-fpm.d", "/etc/opt/rh/rh-php*/php-fpm.d", "/usr/local/etc/php-fpm.d") { foreach my $realdir (glob($cdir)) { if ($realdir && -d $realdir) { my @files = glob("$realdir/*"); if (@files) { push(@verdirs, $realdir); } } } } if (!@verdirs) { $rv->{'err'} = $text{'php_fpmnodir'}; next; } my ($bestdir) = grep { /(php|\/)\Q$rv->{'version'}\E\// || /(php|\/)\Q$rv->{'pkgversion'}\E\// || /(php|\/)\Q$rv->{'shortversion'}\E\// } @verdirs; $bestdir ||= $verdirs[0]; $rv->{'dir'} = $bestdir; # Init script for this version &foreign_require("init"); my $shortver = $rv->{'version'}; $shortver =~ s/^(\d+\.\d+)\..*/$1/g; my $nodot = $shortver; $nodot =~ s/\.//g; foreach my $init ("php${shortver}-fpm", "php-fpm${shortver}", "rh-php${nodot}-php-fpm", "php${nodot}-php-fpm") { my $st = &init::action_status($init); if ($st) { $rv->{'init'} = $init; $rv->{'enabled'} = $st > 1; last; } } if (!$rv->{'init'}) { # Init script for any version as a fallback my @nodot = map { my $u = $_; $u =~ s/\.//g; $u } @all_possible_php_versions; foreach my $init ("php-fpm", (map { "php${_}-fpm" } @all_possible_short_php_versions), (map { "php${_}-fpm" } @all_possible_php_versions), (map { "rh-php${_}-php-fpm" } @nodot), (map { "php${_}-php-fpm" } @nodot)) { my $st = &init::action_status($init); if ($st) { $rv->{'init'} = $init; $rv->{'enabled'} = $st > 1; last; } } } if (!$rv->{'init'}) { $rv->{'err'} = $text{'php_fpmnoinit2'}; next; } # Apache modules if ($config{'web'}) { &require_apache(); if (!$apache::httpd_modules{'mod_proxy'}) { $rv->{'err'} = &text('php_fpmnomod', 'mod_proxy'); } } } # If FPM was setup without a package or init script, also allow it if ($config{'php_fpm_cmd'} && -x $config{'php_fpm_cmd'} && $config{'php_fpm_pool'} && -d $config{'php_fpm_pool'}) { my $rv = { 'cmd' => $config{'php_fpm_cmd'}, 'fromconfig' => 1, 'init' => $config{'php_fpm_init'}, 'dir' => $config{'php_fpm_pool'}, }; $rv->{'version'} = &get_php_version($rv->{'cmd'}); $rv->{'shortversion'} = $rv->{'version'}; $rv->{'shortversion'} =~ s/^(\d+\.\d+)\..*/$1/; # Reduce version to 5.x if ($rv->{'init'}) { my $st = &init::action_status($rv->{'init'}); if ($st == 0) { $rv->{'err'} = &text('php_fpmnoinit3', $rv->{'init'}); } elsif ($st == 2) { $rv->{'enabled'} = 1; } } push(@rv, $rv); } $php_fpm_config_cache = \@rv; return @rv; } # get_php_fpm_socket_file(&domain, [dont-make-dir]) # Returns the path to the default per-domain PHP-FPM socket file. Creates the # directory if needed. sub get_php_fpm_socket_file { my ($d, $nomkdir) = @_; # Universal FPM socket dir my $base = "/var/php-fpm"; # Accommodate old styles in read only mode my $basefile = $base."/".$d->{'id'}.".sock"; return $basefile if (-r $basefile); # Distro dependent FPM socket dirs (if exists) my $rhelbase = "/run/php-fpm"; my $debbase = "/run/php"; if ($config{'fpm_socket_dir'}) { $base = $config{'fpm_socket_dir'}; } elsif (-d $rhelbase && $gconfig{'os_type'} eq 'redhat-linux') { $base = $rhelbase; } elsif (-d $debbase && $gconfig{'os_type'} eq 'debian-linux') { $base = $debbase; } if (!-d $base && !$nomkdir) { &make_dir($base, 0755); } return $base."/".$d->{'id'}.".sock"; } # get_php_fpm_socket_port(&domain) # Selects a dynamic port number for a per-domain PHP FPM socket sub get_php_fpm_socket_port { my ($d) = @_; if ($d->{'php_fpm_port'}) { # Already chosen return $d->{'php_fpm_port'}; } my %used = map { $_->{'php_fpm_port'}, $_ } grep { $_->{'php_fpm_port'} } &list_domains(); my $base = $config{'php_fpm_port'} || 8000; my $rv = &allocate_free_tcp_port(\%used, $base); $rv || &error("Failed to allocate FPM port starting at $base"); $d->{'php_fpm_port'} = $rv; return $rv; } # get_domain_php_fpm_port(&domain) # Returns a status code (0=error, 1=port, 2=file) and the actual TCP port or # socket file used for FPM sub get_domain_php_fpm_port { my ($d) = @_; local $p = &domain_has_website($d); if ($p ne 'web') { if (!&plugin_defined($p, "feature_get_domain_php_fpm_port")) { return (-1, "Not supported by plugin $p"); } return &plugin_call($p, "feature_get_domain_php_fpm_port", $d); } # Find the Apache virtualhost &require_apache(); my ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'}); return (0, "No Apache virtualhost found") if (!$virt); # What port is Apache on? my $webport; foreach my $p (&apache::find_directive("ProxyPassMatch", $vconf)) { if ($p =~ /fcgi:\/\/localhost:(\d+)/ || $p =~ /fcgi:\/\/127\.0\.0\.1:(\d+)/ || $p =~ /unix:([^\|]+)/) { $webport = $1; } } foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) { next if ($f->{'words'}->[0] ne '\.php$'); foreach my $h (&apache::find_directive("SetHandler", $f->{'members'})) { if ($h =~ /proxy:fcgi:\/\/localhost:(\d+)/ || $h =~ /proxy:fcgi:\/\/127\.0\.0\.1:(\d+)/ || $h =~ /proxy:unix:([^\|]+)/) { my $webport2 = $1; if ($webport && $webport != $webport2) { return (0, "Port $webport in ProxyPassMatch ". "is different from port $webport2 ". "in FilesMatch"); } $webport ||= $webport2; } } } return (0, "No FPM SetHandler or ProxyPassMatch directive found") if (!$webport); # Which port is the FPM server actually using? my $fpmport; my $listen = &get_php_fpm_config_value($d, "listen"); if ($listen =~ /^\S+:(\d+)$/ || $listen =~ /^(\d+)$/ || $listen =~ /^(\/\S+)$/) { $fpmport = $1; } return (0, "No listen directive found in FPM config") if (!$fpmport); if ($fpmport ne $webport) { return (0, "Apache config port $webport does not ". "match FPM config $fpmport"); } return ($fpmport =~ /^\d+$/ ? 1 : 2, $fpmport); } # save_domain_php_fpm_port(&domain, port|socket) # Update the TCP port or socket used for FPM for a domain. Returns undef on # success or an error message on failure. sub save_domain_php_fpm_port { my ($d, $socket) = @_; my $p = &domain_has_website($d); if ($p ne 'web') { if (!&plugin_defined($p, "feature_save_domain_php_fpm_port")) { return "Not supported by plugin $p"; } return &plugin_call($p, "feature_save_domain_php_fpm_port", $d,$socket); } # First update the Apache config &require_apache(); &obtain_lock_web($d); my @ports = ( $d->{'web_port'}, $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) ); my $found = 0; foreach my $p (@ports) { my ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $p); next if (!$virt); foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) { next if ($f->{'words'}->[0] ne '\.php$'); my @sh = &apache::find_directive("SetHandler", $f->{'members'}); for(my $i=0; $i<@sh; $i++) { if ($sh[$i] =~ /proxy:fcgi:\/\/localhost:(\d+)/ || $sh[$i] =~ /proxy:fcgi:\/\/127\.0\.0\.1:(\d+)/ || $sh[$i] =~ /proxy:unix:([^\|]+)/) { # Found the directive to update if ($socket =~ /^\d+$/) { $sh[$i] = "proxy:fcgi://127.0.0.1:".$socket; } else { $sh[$i] = "proxy:unix:".$socket. "|fcgi://127.0.0.1"; } $found++; } } &apache::save_directive( "SetHandler", \@sh, $f->{'members'}, $conf); &flush_file_lines($virt->{'file'}); } } &release_lock_web($d); $found || return "No Apache VirtualHost containing an FPM SetHandler found"; # Second update the FPM server port my $conf = &get_php_fpm_config($d); &save_php_fpm_config_value($d, "listen", $socket); if ($socket =~ /^\//) { # Also set correct owner for the file if switching to socket mode &save_php_fpm_config_value($d, "listen.owner", $d->{'user'}); &save_php_fpm_config_value($d, "listen.group", $d->{'ugroup'}); } ®ister_post_action(\&restart_php_fpm_server, $conf); ®ister_post_action(\&restart_apache); return undef; } # check_php_fpm_port_clash(&domain) # Checks if any other FPM pool is using the same port or socket, # and if so returns it and the port number or socket file name sub check_php_fpm_port_clash { my ($d) = @_; my (undef, $port) = &get_domain_php_fpm_port($d); my $dconf = &get_php_fpm_config($d); my @fpms = &list_php_fpm_configs(); foreach my $conf (@fpms) { my @pools = &list_php_fpm_pools($conf); foreach my $p (@pools) { next if ($p eq $d->{'id'} && $conf->{'version'} eq $dconf->{'version'}); my $t = get_php_fpm_pool_config_value($conf, $p, "listen"); next if (!$t); $t =~ s/^\S+:(\d+)$/$1/g; # Remove listen: if ($t eq $port) { my $otherid = $port =~ /\/(\d+)\.sock/ ? $1 : undef; return ($p, $conf, $port, $otherid); } } } return (); } # list_php_fpm_pools(&conf) # Returns a list of all pool IDs for some FPM config sub list_php_fpm_pools { my ($conf) = @_; my @rv; opendir(DIR, $conf->{'dir'}); foreach my $f (readdir(DIR)) { if ($f =~ /^(\S+)\.conf$/) { push(@rv, $1); } } closedir(DIR); return @rv; } # create_php_fpm_pool(&domain, [force-listen]) # Create a per-domain pool config file sub create_php_fpm_pool { my ($d, $forcelisten) = @_; my $tmpl = &get_template($d->{'template'}); my $conf = &get_php_fpm_config($d); return $text{'php_fpmeconfig'} if (!$conf); my $file = $conf->{'dir'}."/".$d->{'id'}.".conf"; my $oldlisten = $forcelisten || &get_php_fpm_config_value($d, "listen"); my $mode; if ($oldlisten) { $mode = $oldlisten =~ /^\// ? 1 : 0; } else { $mode = $tmpl->{'php_sock'}; } my $port; if ($mode) { $port = $oldlisten || &get_php_fpm_socket_file($d); } else { $port = &get_php_fpm_socket_port($d); } $port = "127.0.0.1:".$port if ($port =~ /^\d+$/); &lock_file($file); if (-r $file) { # Fix up existing one, in case user or group changed &save_php_fpm_config_value($d, "user", $d->{'user'}); &save_php_fpm_config_value($d, "group", $d->{'ugroup'}); &save_php_fpm_config_value($d, "listen", $port); if (&get_php_fpm_config_value($d, "listen.owner")) { &save_php_fpm_config_value($d, "listen.owner", $d->{'user'}); &save_php_fpm_config_value($d, "listen.group", $d->{'ugroup'}); } } else { # Create a new file my $tmpl = &get_template($d->{'template'}); my $defchildren = $tmpl->{'web_phpchildren'}; if ($defchildren eq "none" || !$defchildren) { $defchildren = get_php_max_childred_allowed(); } my $defmaxspare = get_php_max_spare_servers($defchildren); my $defstartservers = get_php_start_servers($defchildren); local $tmp = &create_server_tmp($d); my $lref = &read_file_lines($file); @$lref = ( "[$d->{'id'}]", "user = ".$d->{'user'}, "group = ".$d->{'ugroup'}, "listen.owner = ".$d->{'user'}, "listen.group = ".$d->{'ugroup'}, "listen.mode = 0660", "listen = ".$port, "pm = dynamic", "pm.max_children = $defchildren", "pm.start_servers = $defstartservers", "pm.min_spare_servers = 1", "pm.max_spare_servers = $defmaxspare", "php_value[upload_tmp_dir] = $tmp", "php_value[session.save_path] = $tmp" ); &flush_file_lines($file); # Add / override custom options (with substitution) if ($tmpl->{'php_fpm'} ne 'none') { foreach my $l (split(/\t+/, &substitute_domain_template($tmpl->{'php_fpm'}, $d))) { next if ($l !~ /^\s*(\S+)\s*=\s*(.*)/); my ($n, $v) = ($1, $2); &save_php_fpm_config_value($d, $n, $v); } } } my $parent = $d->{'parent'} ? &get_domain_by($d->{'parent'}) : $d; if ($parent->{'jail'}) { my $dir = &get_domain_jailkit($parent); &save_php_fpm_config_value($d, "chroot", $dir); } &unlock_file($file); if ($port =~ /^\// && !-e $port) { # Pre-create the socket file open(TOUCH, ">$port"); close(TOUCH); &set_ownership_permissions($d->{'user'}, $d->{'ugroup'}, 0660, $port); } ®ister_post_action(\&restart_php_fpm_server, $conf); return undef; } # delete_php_fpm_pool(&domain) # Remove the per-domain pool configuration file sub delete_php_fpm_pool { my ($d) = @_; my @fpms = &list_php_fpm_configs(); my $found = 0; foreach my $conf (@fpms) { my @pools = &list_php_fpm_pools($conf); foreach my $p (@pools) { if ($p eq $d->{'id'}) { my $file = $conf->{'dir'}."/".$d->{'id'}.".conf"; if (-r $file) { &unlink_logged($file); &unflush_file_lines($file); my $sock = &get_php_fpm_socket_file($d, 1); &unlink_logged($sock) if (-r $sock); ®ister_post_action( \&restart_php_fpm_server, $conf); } } } } } # restart_php_fpm_server([&config]) # Post-action script to restart the server sub restart_php_fpm_server { my ($conf) = @_; $conf ||= &get_php_fpm_config(); &$first_print(&text('php_fpmrestart', $conf->{'shortversion'})); if ($conf->{'init'}) { &foreign_require("init"); my ($ok, $err) = &init::restart_action($conf->{'init'}); if ($ok) { &$second_print($text{'setup_done'}); return 1; } else { &$second_print(&text('php_fpmeinit', $err)); return 0; } } else { &$second_print($text{'php_fpmnoinit'}); return 0; } } # get_php_fpm_config_value(&domain, name) # Returns the value of a config setting from the domain's pool file sub get_php_fpm_config_value { my ($d, $name) = @_; my $conf = &get_php_fpm_config($d); return undef if (!$conf); return &get_php_fpm_pool_config_value($conf, $d->{'id'}, $name); } # list_php_fpm_config_values(&domain) # Returns an array ref of name/value pairs from the FPM config for a domain sub list_php_fpm_config_values { my ($d) = @_; my $conf = &get_php_fpm_config($d); return undef if (!$conf); return &list_php_fpm_pool_config_values($conf, $d->{'id'}); } # get_php_fpm_pool_config_value(&conf, domain-id, name) # Returns the value of a config setting from any pool file sub get_php_fpm_pool_config_value { my ($conf, $id, $name) = @_; my $file = $conf->{'dir'}."/".$id.".conf"; my $lref = &read_file_lines($file, 1); foreach my $l (@$lref) { if ($l =~ /^\s*(\S+)\s*=\s*(.*)/ && $1 eq $name) { return $2; } } return undef; } # list_php_fpm_pool_config_values(&conf, domain-id) # Returns an array ref of name/value pairs from the FPM config sub list_php_fpm_pool_config_values { my ($conf, $id) = @_; my $file = $conf->{'dir'}."/".$id.".conf"; return &list_php_fpm_file_config_values($file); } # list_php_fpm_file_config_values(file) # Returns an array ref of name/value pairs from an FPM pool file sub list_php_fpm_file_config_values { my ($file) = @_; my $lref = &read_file_lines($file, 1); my @rv; foreach my $l (@$lref) { if ($l =~ /^\s*(\S+)\s*=\s*(.*)/) { push(@rv, [ $1, $2 ]); } } return \@rv; } # get_php_fpm_ini_value(&domain, name) # Returns the value of a PHP ini setting from the domain's pool file sub get_php_fpm_ini_value { my ($d, $name) = @_; my $k = "php_value"; my $rv = &get_php_fpm_config_value($d, "php_value[${name}]"); if (!defined($rv)) { my $k = "php_admin_value"; $rv = &get_php_fpm_config_value($d, "php_admin_value[${name}]"); if (!defined($rv)) { $k = undef; } } return wantarray ? ($rv, $k) : $rv; } # list_php_fpm_ini_values(&domain) # Returns an array ref of name/value/admin triplets for FPM PHP ini settings sub list_php_fpm_ini_values { my ($d) = @_; my $all = &list_php_fpm_config_values($d); return undef if (!$all); my @rv; foreach my $c (@$all) { if ($c->[0] =~ /^(php_value|php_admin_value)\[(\S+)\]$/) { push(@rv, [ $2, $c->[1], $1 eq "php_admin_value" ? 1 : 0 ]); } } return \@rv; } # save_php_fpm_config_value(&domain, name, value) # Adds, updates or deletes an config setting in the domain's pool file sub save_php_fpm_config_value { my ($d, $name, $value) = @_; my $conf = &get_php_fpm_config($d); return 0 if (!$conf); return &save_php_fpm_pool_config_value($conf, $d->{'id'}, $name, $value); } # save_php_fpm_pool_config_value(&conf, id, name, value) # Adds, updates or deletes an config setting in a pool file sub save_php_fpm_pool_config_value { my ($conf, $id, $name, $value) = @_; my $file = $conf->{'dir'}."/".$id.".conf"; return if (!-r $file); &lock_file($file); my $lref = &read_file_lines($file); my $found = -1; my $lnum = 0; foreach my $l (@$lref) { if ($l =~ /^\s*(\S+)\s*=\s*(.*)/ && $1 eq $name) { $found = $lnum; last; } $lnum++; } if ($found >= 0 && defined($value)) { # Update existing line $lref->[$found] = "$name = $value"; } elsif ($found >=0 && !defined($value)) { # Remove existing line splice(@$lref, $found, 1); } elsif ($found < 0 && defined($value)) { # Need to add new line push(@$lref, "$name = $value"); } &flush_file_lines($file); &unlock_file($file); ®ister_post_action(\&restart_php_fpm_server, $conf); return 1; } # save_php_fpm_ini_value(&domain, name, value, [admin?]) # Adds, updates or deletes an ini setting in the domain's pool file sub save_php_fpm_ini_value { my ($d, $name, $value, $admin) = @_; my (undef, $k) = &get_php_fpm_ini_value($d, $name); $k ||= ($admin ? "php_admin_value" : "php_value"); return &save_php_fpm_config_value($d, $k."[".$name."]", $value); } # increase_fpm_port(string) # Increase the number in a port string sub increase_fpm_port { my ($t) = @_; if ($t =~ /^(\d+)$/) { return $t + 1; } elsif ($t =~ /^(.*):(\d+)$/) { return $1.":".($2 + 1); } return undef; } # get_apache_mod_php_version() # If Apache has mod_phpX installed, return the version number sub get_apache_mod_php_version { return $apache_mod_php_version_cache if ($apache_mod_php_version_cache); &require_apache(); my $major = $apache::httpd_modules{'mod_php5'} ? 5 : $apache::httpd_modules{'mod_php7'} ? "7.0" : undef; return undef if (!$major); foreach my $php ("php$major", "php") { next if (!&has_command($php)); &clean_environment(); my $out = &backquote_command("$php -v 2>&1 </dev/null"); &reset_environment(); if ($out =~ /PHP\s+(\d+\.\d+)/) { $major = $1; last; } } $apache_mod_php_version_cache = $major; return $major; } # cleanup_php_sessions(&domain, dry-run) # Remove old PHP session files for some domain sub cleanup_php_sessions { my ($d, $dryrun) = @_; # Find the session files dir from php config my $etc = "$d->{'home'}/etc"; &foreign_require("phpini"); my $pconf = &phpini::get_config("$etc/php.ini"); my $tmp = &phpini::find_value("session.save_path", $pconf); $tmp ||= $d->{'home'}."/tmp"; # Look for session files that are too old my $days = $config{'php_session_age'} || 7; my $cutoff = time() - $days * 24 * 60 * 60; my @rv; opendir(DIR, $tmp) || return (); foreach my $f (readdir(DIR)) { next if ($f !~ /^sess_/); my @st = stat($tmp."/".$f); next if (!@st); if ($st[9] < $cutoff) { push(@rv, $tmp."/".$f); } } closedir(DIR); # Delete any found if (!$dryrun) { &unlink_file_as_domain_user($d, @rv); } return @rv; } # get_php_max_childred_allowed() # Get PHP-FPM recommended allowed number for sub-processes, # which is calculated as total available RAM devided by # 64 MiB (aprox. cunsumed by each PHP-FPM process) and # devided by 4 (as we assume that a maximum recommended # default which PHP can use is 20% of all available RAM # on the system). However, manually it will be possible # to rase the number to use all available RAM as defined # using `$max_php_fcgid_children` variable. Also on systems # with a lot of RAM limit maximum recommended to sensible 16. sub get_php_max_childred_allowed { return $main::get_real_memory_size_cache if (defined($main::get_real_memory_size_cache)); my $max; my $mem = &get_real_memory_size(); if ($mem) { my $sysram_mb = $mem / 1024 / 1024; $max = int(($sysram_mb / 64) / 5); if ($max > 16) { $max = 16; } } else { $max = $max_php_fcgid_children; } # Low memory systems should not # return values lower than 1 $max ||= 1; $main::get_real_memory_size_cache = $max; return int($max); } sub get_php_max_spare_servers { my ($defchildren) = @_; my $defmaxspare = $defchildren <= 1 ? $defchildren : $defchildren >= 4 ? int($defchildren / 2) : 2; return int($defmaxspare); } sub get_php_start_servers { my ($defchildren) = @_; my $min_spare_servers = 1; my $max_spare_servers = get_php_max_spare_servers($defchildren); my $start_servers = $min_spare_servers + ($max_spare_servers - $min_spare_servers) / 4; return int($start_servers) || 1; } # get_domain_php_error_log(&domain) # Returns the PHP error log for a domain, from it's php.ini file. Return a path # if set, an empty string if unset, or undef if it's not possible to set. sub get_domain_php_error_log { my ($d) = @_; my $mode = &get_domain_php_mode($d); my $phplog; if ($mode eq "fpm") { $phplog = &get_php_fpm_ini_value($d, "error_log") || ""; } elsif ($mode ne "none" && $mode ne "mod_php") { $phplog = ""; &foreign_require("phpini"); foreach my $i (&list_domain_php_inis($d)) { my $pconf = &phpini::get_config($i->[1]); $phplog = &phpini::find_value("error_log", $pconf); last if ($phplog); } } else { return undef; } if ($phplog && $phplog !~ /^\//) { $phplog = $d->{'home'}.'/'.$phplog; } return $phplog; } # save_domain_php_error_log(&domain, [logfile]) # Set or remove the PHP error log file for a domain in its php.ini file sub save_domain_php_error_log { my ($d, $phplog) = @_; $phplog = undef if (!$phplog); my $oldd = { %$d }; my $mode = &get_domain_php_mode($d); my $p = &domain_has_website($d); if ($mode eq "fpm") { &save_php_fpm_ini_value($d, "error_log", $phplog, 0); my $loge = &get_php_fpm_ini_value($d, "log_errors"); if (lc($loge) ne "on") { &save_php_fpm_ini_value($d, "log_errors", "On", 0); } } elsif ($mode ne "none" && $mode ne "mod_php") { &foreign_require("phpini"); foreach my $i (&list_domain_php_inis($d)) { &lock_file($i->[1]); my $pconf = &phpini::get_config($i->[1]); &phpini::save_directive($pconf, "error_log", $phplog); my $loge = &phpini::find_value("log_errors", $pconf); if (lc($loge) ne "on") { &phpini::save_directive($pconf, "log_errors", "On"); } &flush_file_lines($i->[1], undef, 1); &unlock_file($i->[1]); } if ($mode eq 'fcgid') { if ($p eq "web") { ®ister_post_action(\&restart_apache); } elsif ($p) { &plugin_call($p, "feature_restart_web_php", $d); } } } else { return "PHP error log cannot be set in $mode mode"; } $d->{'php_error_log'} = $phplog || ""; if ($phplog && !-r $phplog) { # Make sure the log file exists &open_tempfile_as_domain_user($d, PHPLOG, ">$phplog", 1, 1); &close_tempfile_as_domain_user($d, PHPLOG); } &push_all_print(); &set_all_null_print(); &modify_logrotate($d, $oldd); &pop_all_print(); return undef; } # can_php_error_log(&domain|mode) # Returns 1 if the PHP error log can be set for a domain or PHP mode sub can_php_error_log { my ($mode) = @_; $mode = &get_domain_php_mode($mode) if (ref($mode)); return $mode ne "none" && $mode ne "mod_php"; } # get_default_php_error_log(&domain) # Returns the default PHP log path for a domain, based on the template sub get_default_php_error_log { my ($d) = @_; my $tmpl = &get_template($d->{'template'}); my $path = $tmpl->{'php_log_path'} || "logs/php_log"; $path = &substitute_domain_template($path, $d); $path = $d->{'home'}.'/'.$path if ($path !~ /^\//); return $path; } # copyable_fpm_configs(&conf) # Returns only FPM configs that should be copied when changing version, # cloning or restoring a backup sub copyable_fpm_configs { my ($confs) = @_; my @rv; foreach my $pv (@$confs) { push(@rv, $pv) if ($pv->[0] =~ /^(php_value|php_admin_value|env)\[/ || $pv->[0] =~ /^pm\./); } return @rv; } 1;Private