php IHDR w Q )Ba pHYs sRGB gAMA a IDATxMk\U s&uo,mD )Xw+e?tw.oWp;QHZnw`gaiJ9̟灙a=nl[ ʨ G;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ y H@E7j 1j+OFRg}ܫ;@Ea~ j`u'o> j- $_q?qS XzG'ay
files >> /usr/libexec/webmin/virtual-server/ |
files >> //usr/libexec/webmin/virtual-server/php-lib.pl |
# 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) { local @actions = &apache::find_directive("Action", $vconf); local $pdir = &public_html_dir($d); local ($dir) = grep { $_->{'words'}->[0] eq $pdir || $_->{'words'}->[0] eq $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'; } } } foreach my $a (@actions) { if ($a =~ /^application\/x-httpd-php.\s+\/cgi-bin\/php\S+\.cgi/) { return 'cgi'; } } } return 'mod_php'; } # save_domain_php_mode(&domain, mode, [port], [new-domain]) # Changes the method a virtual web server uses to run PHP. 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'}); # Work out source php.ini files local (%srcini, %subs_ini); local @vers = &list_available_php_versions($d, $mode); @vers || &error("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); } local $defver = $vers[0]->[0]; 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 || &error("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 || &error("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", "phpini-lib.pl"); 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"); } } # Link ~/etc/php.ini to the per-version ini file &create_php_ini_link($d, $mode); # Call plugin-specific function to perform webserver setup if ($p ne 'web') { return &plugin_call($p, "feature_save_web_php_mode", $d, $mode, $port, $newdom); } &require_apache(); # Create wrapper scripts if ($mode ne "mod_php") { &create_php_wrappers($d, $mode); } # 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 || &error("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+/ } @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"); } } 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"); } else { 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 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"); } } &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 ($apache::httpd_modules{'mod_php4'} || $apache::httpd_modules{'mod_php5'}) { local @admin = &apache::find_directive("php_admin_value", $vconf); @admin = grep { !/^engine\s+/ } @admin; if ($mode ne "mod_php" && !$config{'allow_modphp'}) { 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", "phpini-lib.pl"); 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", "software-lib.pl"); 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(); } ®ister_post_action(\&restart_apache); $pfound || &error("Apache virtual host was not found"); } # 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; 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"; if ($max) { &apache::save_directive($dirname, [ $max+1 ], $vconf, $conf); } else { &apache::save_directive($dirname, [ 9999 ], $vconf, $conf); } &flush_file_lines($virt->{'file'}); } ®ister_post_action(\&restart_apache); $pfound || &error("Apache virtual host was not found"); } # 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 sub set_php_max_execution_time { local ($d, $max) = @_; &foreign_require("phpini", "phpini-lib.pl"); 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); } } # 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", "phpini-lib.pl"); 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); } } } # supported_php_modes([&domain]) # Returns a list of PHP execution modes possible for a domain sub supported_php_modes { local ($d) = @_; local $p = &domain_has_website($d); if ($p ne 'web') { return &plugin_call($p, "feature_web_supported_php_modes", $d); } &require_apache(); local @rv; if ($apache::httpd_modules{'mod_php4'} || $apache::httpd_modules{'mod_php5'}) { # Check for Apache PHP module push(@rv, "mod_php"); } if ($d) { # Check for domain's cgi-bin directory local ($pvirt, $pconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'}); if ($pconf) { local @sa = grep { /^\/cgi-bin\s/ } &apache::find_directive("ScriptAlias", $pconf); push(@rv, "cgi"); } } else { # Assume all domains have CGI push(@rv, "cgi"); } if ($apache::httpd_modules{'mod_fcgid'}) { # Check for Apache fcgi module push(@rv, "fcgid"); } return @rv; } # list_available_php_versions([&domain], [forcemode]) # Returns a list of PHP versions and their executables installed on the system, # for use by a domain sub list_available_php_versions { local ($d, $mode) = @_; local @rv; &require_apache(); if ($d) { # If the domain is using mod_php, we can only use one version $mode ||= &get_domain_php_mode($d); if ($mode eq "mod_php") { if ($apache::httpd_modules{'mod_php4'}) { return ([ 4, undef ]); } elsif ($apache::httpd_modules{'mod_php5'}) { return ([ 5, undef ]); } else { return ( ); } } } else { # If no domain is given, included mod_php versions if active if ($apache::httpd_modules{'mod_php4'}) { push(@rv, [ 4, undef ]); } elsif ($apache::httpd_modules{'mod_php5'}) { push(@rv, [ 5, undef ]); } } # For CGI and fCGId modes, check which wrappers could exist foreach my $v (@all_possible_php_versions) { local $phpn; if ($gconfig{'os_type'} eq 'solaris') { # On Solaris with CSW packages, php-cgi is in a directory named # after the PHP version $phpn = &has_command("/opt/csw/php$v/bin/php-cgi"); } $phpn ||= &has_command("php$v-cgi") || &has_command("php$v"); local $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 $phpn ||= &has_command("php$nodotv-cgi") || &has_command("php-cgi$nodotv") || &has_command("/opt/rh/php$nodotv/root/usr/bin/php-cgi") || &has_command("/opt/atomic/php$nodotv/root/usr/bin/php") || &has_command("/opt/rh/php$nodotv/bin/php-cgi") || &has_command("php$nodotv") || &has_command("/opt/rh/php$nodotv/root/usr/bin/php"); &has_command("/opt/rh/php$nodotv/bin/php") || &has_command(glob("/opt/phpfarm/inst/bin/php-cgi-$v.*")); } $vercmds{$v} = $phpn if ($phpn); } local $php = &has_command("php-cgi") || &has_command("php"); 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. &clean_environment(); local $out = &backquote_command("$php -v 2>&1 </dev/null"); &reset_environment(); if ($out =~ /PHP\s+(\d+)\./ && !$vercmds{$1}) { $vercmds{$1} = $php; } } # Return results as list return map { [ $_, $vercmds{$_} ] } sort { $a <=> $b } (keys %vercmds); } # 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. sub get_php_version { local ($cmd, $d) = @_; if ($cmd !~ /^\//) { local ($phpn) = grep { $_->[0] == $cmd } &list_available_php_versions($d); return undef if (!$phpn); $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\.]+)/) { return $1; } return undef; } # 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 mod local @avail = &list_available_php_versions($d, $mode); if (@avail) { return ( { 'dir' => &public_html_dir($d), 'version' => $avail[0]->[0], 'mode' => $mode } ); } else { 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 }); } } } } return @rv; } # save_domain_php_directory(&domain, dir, phpversion) # 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 1 if the # directory version was set OK, 0 if not (because the virtualhost couldn't # be found, or the PHP mode was wrong) sub save_domain_php_directory { local ($d, $dir, $ver) = @_; 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 0 if ($mode eq "mod_php"); local @ports = ( $d->{'web_port'}, $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) ); local $any = 0; local %allvers = map { $_, 1 } @all_possible_php_versions; local $pfound = 0; foreach my $p (@ports) { local $conf = &apache::get_config(); local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p); next if (!$virt); $pfound++; # 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[45]/ } @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"); &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() : ""; local @lines = ( "<Directory $dir>", "Options +Indexes +IncludesNOEXEC +SymLinksifOwnerMatch +ExecCGI", "allow from all", "AllowOverride All".$olist, @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 0 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); # Copy in php.ini file for version if missing my @inifiles = &find_domain_php_ini_files($d); my ($iniver) = grep { $_->[0] eq $ver } @inifiles; if (!$iniver) { &save_domain_php_mode($d, $mode); } ®ister_post_action(\&restart_apache); $pfound || &error("Apache virtual host was not found"); return 1; } # 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; } # cleanup_php_cgi_processes() # Finds and kills and php-cgi, php4-cgi and php5-cgi processes which are # orphans (owned by init). This can happen if they are not killed when Apache # is restarted. sub cleanup_php_cgi_processes { if (&foreign_check("proc") && $config{'web'}) { &foreign_require("proc", "proc-lib.pl"); local @procs = &proc::list_processes(); local @cgis = grep { $_->{'args'} =~ /^\S+php(4|5|)\-cgi/ && $_->{'ppid'} == 1 } @procs; foreach my $p (@cgis) { kill('KILL', $p->{'pid'}); } return scalar(@cgis); } return -1; } # list_domain_php_inis(&domain, [force-mode]) # Returns a list of php.ini files used by a domain, and their PHP versions 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 ]); } } if (!@inis) { local $ifile = "$d->{'home'}/etc/php.ini"; if (-r $ifile) { push(@inis, [ undef, $ifile ]); } } 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; foreach my $i ("/opt/rh/php$nodotv/root/etc/php.ini", "/opt/rh/php$nodotv/lib/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$ver/cgi/php.ini", "/etc/php$nodotv/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", "phpini-lib.pl"); local $gconf = &phpini::get_config($inifile); local $sock = &phpini::find_value("mysql.default_socket", $gconf); return $sock; } # 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 { local ($d) = @_; local $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"; } local ($ver) = &list_available_php_versions($d, "fcgid"); return -2 if (!$ver); local $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; } # 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"; } 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; } # 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 >/dev/null"); local @errs; foreach my $l (split(/\r?\n/, $out)) { if ($l =~ /PHP\s+Fatal\s+error:\s*(.*)/) { my $msg = $1; $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") { # 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" && &foreign_check("phpini")) { &foreign_require("phpini", "phpini-lib.pl"); &$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_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", "phpini-lib.pl"); 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); } } } # 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) = @_; $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]; local $etc = "$d->{'home'}/etc"; if ($hdir) { &unlink_file_as_domain_user($d, "$etc/php.ini"); &symlink_file_as_domain_user($d, "php".$hdir->{'version'}."/php.ini", "$etc/php.ini"); } } } 1;y~or5J={Eeu磝Qk ᯘG{?+]ן?wM3X^歌>{7پK>on\jy Rg/=fOroNVv~Y+ NGuÝHWyw[eQʨSb> >}Gmx[o[<{Ϯ_qFvM IENDB`