mirror of
https://github.com/webmin/webmin.git
synced 2026-06-04 20:30:22 +01:00
Add Debian-style sites-available file management to Apache
* Note: Bring the Apache module to parity with the Nginx module's Debian sites-available/sites-enabled handling: list disabled vhost files alongside active ones, toggle their state via symlink with apachectl configtest rollback, and delete VirtualHost blocks from inactive files. When Virtualmin manages a vhost, defer enable/disable to Virtualmin's own forms instead of touching the symlink directly. https://forum.virtualmin.com/t/enable-disable-toggle-buttons-in-ngnix-module/137238/4?u=ilia
This commit is contained in:
@@ -436,6 +436,13 @@ foreach $v (@virt) {
|
||||
return \@get_config_cache;
|
||||
}
|
||||
|
||||
# flush_config_cache()
|
||||
# Delete all in-memory config caches
|
||||
sub flush_config_cache
|
||||
{
|
||||
undef(@get_config_cache);
|
||||
}
|
||||
|
||||
# get_config_file(filename, [&seen-files])
|
||||
# Returns a list of config hash refs from some file
|
||||
sub get_config_file
|
||||
@@ -788,6 +795,427 @@ unlink($file);
|
||||
&delete_webfile_link($file);
|
||||
}
|
||||
|
||||
# can_manage_vhost_files()
|
||||
# Returns 1 if this system uses Debian-style available/enabled site dirs
|
||||
sub can_manage_vhost_files
|
||||
{
|
||||
return 0 if ($gconfig{'os_type'} ne 'debian-linux');
|
||||
my $avail = &vhost_available_dir();
|
||||
my $enabled = &vhost_enabled_dir();
|
||||
return $avail && -d $avail && $enabled && -d $enabled &&
|
||||
&simplify_path(&resolve_links($avail)) ne
|
||||
&simplify_path(&resolve_links($enabled));
|
||||
}
|
||||
|
||||
# vhost_available_dir()
|
||||
# Returns the configured directory of available Apache virtual host files
|
||||
sub vhost_available_dir
|
||||
{
|
||||
return $config{'virt_file'} ? &server_root($config{'virt_file'}) : undef;
|
||||
}
|
||||
|
||||
# vhost_enabled_dir()
|
||||
# Returns the configured directory of enabled Apache virtual host symlinks
|
||||
sub vhost_enabled_dir
|
||||
{
|
||||
return $config{'link_dir'} ? &server_root($config{'link_dir'}) : undef;
|
||||
}
|
||||
|
||||
# get_vhost_available_files()
|
||||
# Returns real config files from the directory used for new virtual hosts
|
||||
sub get_vhost_available_files
|
||||
{
|
||||
my @rv;
|
||||
return @rv if (!&can_manage_vhost_files());
|
||||
my $avail = &vhost_available_dir();
|
||||
opendir(AVAIL, $avail) || return @rv;
|
||||
foreach my $f (sort { lc($a) cmp lc($b) } readdir(AVAIL)) {
|
||||
next if ($f eq "." || $f eq "..");
|
||||
my $file = $avail."/".$f;
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
next if (!$rfile || !-f $rfile || !-r $rfile);
|
||||
push(@rv, $rfile);
|
||||
}
|
||||
closedir(AVAIL);
|
||||
return &unique(@rv);
|
||||
}
|
||||
|
||||
# find_virtuals_in_file(file)
|
||||
# Returns VirtualHost blocks parsed from one config file
|
||||
sub find_virtuals_in_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
return ( ) if (!-r $rfile);
|
||||
my @conf = &get_config_file($rfile);
|
||||
return grep { $_->{'file'} eq $rfile }
|
||||
&find_directive_struct("VirtualHost", \@conf);
|
||||
}
|
||||
|
||||
# is_default_vhost(&virt)
|
||||
# Returns 1 if a VirtualHost looks like a default/catch-all host
|
||||
sub is_default_vhost
|
||||
{
|
||||
my ($virt) = @_;
|
||||
return 1 if (!$virt);
|
||||
return 1 if ($virt->{'value'} =~ /_default_/i);
|
||||
return 1 if (!&find_directive("ServerName", $virt->{'members'}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
# can_manage_vhost_file(file)
|
||||
# Returns 1 if all virtual hosts in a file are manageable by this user
|
||||
sub can_manage_vhost_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
return 0 if (!$rfile || !-f $rfile || !-r $rfile);
|
||||
my @virts = &find_virtuals_in_file($rfile);
|
||||
return 0 if (!@virts);
|
||||
foreach my $virt (@virts) {
|
||||
return 0 if (&is_default_vhost($virt));
|
||||
return 0 if (!&can_edit_virt($virt));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# can_manage_vhost_state_file(file)
|
||||
# Returns 1 if a virtual host file can have its enabled state managed here
|
||||
sub can_manage_vhost_state_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
return 0 if (!$rfile || !-f $rfile);
|
||||
my %available = map { $_, 1 } &get_vhost_available_files();
|
||||
return 0 if (!$available{$rfile});
|
||||
return &can_manage_vhost_file($rfile);
|
||||
}
|
||||
|
||||
# get_virtual_list_rows(&config)
|
||||
# Returns row hashes for the virtual-host list, preserving sites-available order
|
||||
sub get_virtual_list_rows
|
||||
{
|
||||
my ($conf) = @_;
|
||||
my @active = grep { &can_edit_virt($_) }
|
||||
&find_directive_struct("VirtualHost", $conf);
|
||||
if (&can_manage_vhost_files()) {
|
||||
my @rows;
|
||||
my %active_by_file;
|
||||
foreach my $v (@active) {
|
||||
my $file = &simplify_path(&resolve_links($v->{'file'}));
|
||||
$file ||= $v->{'file'};
|
||||
push(@{$active_by_file{$file}}, $v);
|
||||
}
|
||||
my %done_virt;
|
||||
foreach my $file (&get_vhost_available_files()) {
|
||||
my @filevirts = @{$active_by_file{$file} || [ ]};
|
||||
my $active = @filevirts ? 1 : 0;
|
||||
if (!@filevirts) {
|
||||
@filevirts = grep { &can_edit_virt($_) }
|
||||
&find_virtuals_in_file($file);
|
||||
}
|
||||
foreach my $v (@filevirts) {
|
||||
push(@rows, { 'virt' => $v,
|
||||
'active' => $active,
|
||||
'file' => $file });
|
||||
$done_virt{$v}++;
|
||||
}
|
||||
}
|
||||
foreach my $v (@active) {
|
||||
next if ($done_virt{$v});
|
||||
push(@rows, { 'virt' => $v,
|
||||
'active' => 1,
|
||||
'file' => $v->{'file'} });
|
||||
}
|
||||
return @rows;
|
||||
}
|
||||
return map { { 'virt' => $_, 'active' => 1, 'file' => $_->{'file'} } }
|
||||
@active;
|
||||
}
|
||||
|
||||
# vhost_file_link(file)
|
||||
# Returns the enabled symlink path for a virtual host file
|
||||
sub vhost_file_link
|
||||
{
|
||||
my ($file) = @_;
|
||||
return undef if (!&can_manage_vhost_files());
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
my $avail = &vhost_available_dir();
|
||||
my $short;
|
||||
if (opendir(AVAIL, $avail)) {
|
||||
foreach my $f (sort { lc($a) cmp lc($b) } readdir(AVAIL)) {
|
||||
next if ($f eq "." || $f eq "..");
|
||||
my $afile = $avail."/".$f;
|
||||
my $rafile = &simplify_path(&resolve_links($afile));
|
||||
if ($rafile && $rafile eq $rfile) {
|
||||
$short = $f;
|
||||
last;
|
||||
}
|
||||
}
|
||||
closedir(AVAIL);
|
||||
}
|
||||
$short ||= $rfile;
|
||||
$short =~ s/^.*\///;
|
||||
return &vhost_enabled_dir()."/".$short;
|
||||
}
|
||||
|
||||
# vhost_file_links(file)
|
||||
# Returns enabled symlinks for a virtual host file
|
||||
sub vhost_file_links
|
||||
{
|
||||
my ($file) = @_;
|
||||
my @rv;
|
||||
return @rv if (!&can_manage_vhost_files());
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
my $enabled = &vhost_enabled_dir();
|
||||
opendir(LINKDIR, $enabled) || return @rv;
|
||||
foreach my $f (readdir(LINKDIR)) {
|
||||
next if ($f eq "." || $f eq "..");
|
||||
my $link = $enabled."/".$f;
|
||||
next if (!-l $link);
|
||||
my $rlink = &simplify_path(&resolve_links($link));
|
||||
if ($rlink && $rlink eq $rfile) {
|
||||
push(@rv, $link);
|
||||
}
|
||||
}
|
||||
closedir(LINKDIR);
|
||||
return @rv;
|
||||
}
|
||||
|
||||
# vhost_file_enabled(file)
|
||||
# Returns 1 if a virtual host file has an enabled symlink
|
||||
sub vhost_file_enabled
|
||||
{
|
||||
my ($file) = @_;
|
||||
return scalar(&vhost_file_links($file)) ? 1 : 0;
|
||||
}
|
||||
|
||||
# enable_vhost_file(file)
|
||||
# Enables a virtual host file and rolls back if apache configtest fails
|
||||
sub enable_vhost_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
return $text{'enable_efile'} if (!&can_manage_vhost_state_file($rfile));
|
||||
my $verr = &virtualmin_vhost_file_state_error($rfile, "enable");
|
||||
return $verr if ($verr);
|
||||
my $link = &vhost_file_link($rfile);
|
||||
$link || return $text{'enable_elinkdir'};
|
||||
return undef if (&vhost_file_enabled($rfile));
|
||||
if (-e $link || -l $link) {
|
||||
return &text('enable_elinkexists', "<tt>".&html_escape($link)."</tt>");
|
||||
}
|
||||
&symlink_logged($rfile, $link) ||
|
||||
return &text('enable_elink', "<tt>".&html_escape($link)."</tt>",
|
||||
"<tt>".&html_escape($!)."</tt>");
|
||||
my $err = &test_config();
|
||||
if ($err) {
|
||||
&unlink_logged($link);
|
||||
return &text('enable_etest', "<tt>".&html_escape($err)."</tt>");
|
||||
}
|
||||
&flush_config_cache();
|
||||
&update_last_config_change();
|
||||
return undef;
|
||||
}
|
||||
|
||||
# disable_vhost_file(file)
|
||||
# Disables a virtual host file and rolls back if apache configtest fails
|
||||
sub disable_vhost_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
return $text{'enable_efile'} if (!&can_manage_vhost_state_file($rfile));
|
||||
my $verr = &virtualmin_vhost_file_state_error($rfile, "disable");
|
||||
return $verr if ($verr);
|
||||
my @links = &vhost_file_links($file);
|
||||
return undef if (!@links);
|
||||
my @restore = map { [ $_, readlink($_) ] } @links;
|
||||
my @removed;
|
||||
foreach my $link (@links) {
|
||||
if (!&unlink_logged($link)) {
|
||||
foreach my $r (@removed) {
|
||||
&symlink_logged($r->[1], $r->[0])
|
||||
if (defined($r->[1]) && !-e $r->[0] && !-l $r->[0]);
|
||||
}
|
||||
return &text('enable_eunlink',
|
||||
"<tt>".&html_escape($link)."</tt>",
|
||||
"<tt>".&html_escape($!)."</tt>");
|
||||
}
|
||||
my ($restore) = grep { $_->[0] eq $link } @restore;
|
||||
push(@removed, $restore) if ($restore);
|
||||
}
|
||||
my $err = &test_config();
|
||||
if ($err) {
|
||||
foreach my $r (@restore) {
|
||||
&symlink_logged($r->[1], $r->[0])
|
||||
if (defined($r->[1]) && !-e $r->[0] && !-l $r->[0]);
|
||||
}
|
||||
return &text('enable_etest', "<tt>".&html_escape($err)."</tt>");
|
||||
}
|
||||
&flush_config_cache();
|
||||
&update_last_config_change();
|
||||
return undef;
|
||||
}
|
||||
|
||||
# virtualmin_available()
|
||||
# Returns 1 if Virtualmin is installed and supported on this system
|
||||
sub virtualmin_available
|
||||
{
|
||||
return $main::apache_virtualmin_available
|
||||
if (defined($main::apache_virtualmin_available));
|
||||
$main::apache_virtualmin_available = &foreign_check("virtual-server");
|
||||
return $main::apache_virtualmin_available;
|
||||
}
|
||||
|
||||
# virtualmin_domain_by_name(name)
|
||||
# Returns a Virtualmin domain object by domain name, if one exists
|
||||
sub virtualmin_domain_by_name
|
||||
{
|
||||
my ($name) = @_;
|
||||
return undef if (!&virtualmin_available());
|
||||
return $main::apache_virtualmin_domain_by_name_cache{$name}
|
||||
if (exists($main::apache_virtualmin_domain_by_name_cache{$name}));
|
||||
&foreign_require("virtual-server");
|
||||
my $d = &virtual_server::get_domain_by("dom", $name);
|
||||
$main::apache_virtualmin_domain_by_name_cache{$name} = $d;
|
||||
return $d;
|
||||
}
|
||||
|
||||
# virtual_names(&virt)
|
||||
# Returns all hostnames from ServerName and ServerAlias directives
|
||||
sub virtual_names
|
||||
{
|
||||
my ($virt) = @_;
|
||||
my @rv;
|
||||
my $sn = &find_directive("ServerName", $virt->{'members'});
|
||||
push(@rv, $sn) if ($sn);
|
||||
foreach my $sa (&find_directive_struct("ServerAlias", $virt->{'members'})) {
|
||||
push(@rv, @{$sa->{'words'} || [ ]});
|
||||
if (!@{$sa->{'words'} || [ ]} && $sa->{'value'}) {
|
||||
push(@rv, $sa->{'value'});
|
||||
}
|
||||
}
|
||||
return grep { $_ && $_ ne "*" } &unique(@rv);
|
||||
}
|
||||
|
||||
# virtualmin_domain_for_vhost_file(file)
|
||||
# Returns the Virtualmin domain object for a virtual host file, if any
|
||||
sub virtualmin_domain_for_vhost_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
return undef if (!&virtualmin_available());
|
||||
my $rfile = &simplify_path(&resolve_links($file));
|
||||
$rfile ||= $file;
|
||||
return $main::apache_virtualmin_domain_for_file_cache{$rfile}
|
||||
if (exists($main::apache_virtualmin_domain_for_file_cache{$rfile}));
|
||||
foreach my $virt (&find_virtuals_in_file($file)) {
|
||||
next if (!&can_edit_virt($virt));
|
||||
foreach my $name (&virtual_names($virt)) {
|
||||
my $d = &virtualmin_domain_by_name($name);
|
||||
if (!$d && $name =~ /^www\.(\S+)/i) {
|
||||
$d = &virtualmin_domain_by_name($1);
|
||||
}
|
||||
if ($d) {
|
||||
$main::apache_virtualmin_domain_for_file_cache{$rfile} = $d;
|
||||
return $d;
|
||||
}
|
||||
}
|
||||
}
|
||||
$main::apache_virtualmin_domain_for_file_cache{$rfile} = undef;
|
||||
return undef;
|
||||
}
|
||||
|
||||
# vhost_file_state(file)
|
||||
# Returns the effective enabled state for a virtual host file
|
||||
sub vhost_file_state
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $d = &virtualmin_domain_for_vhost_file($file);
|
||||
if ($d) {
|
||||
return { 'enabled' => $d->{'disabled'} ? 0 : 1,
|
||||
'source' => 'virtualmin',
|
||||
'domain' => $d };
|
||||
}
|
||||
return { 'enabled' => &vhost_file_enabled($file) ? 1 : 0,
|
||||
'source' => 'apache' };
|
||||
}
|
||||
|
||||
# vhost_file_toggle_action(file)
|
||||
# Returns the action needed to toggle a virtual host file's effective state
|
||||
sub vhost_file_toggle_action
|
||||
{
|
||||
my ($file) = @_;
|
||||
return &vhost_file_state($file)->{'enabled'} ? "disable" : "enable";
|
||||
}
|
||||
|
||||
# virtualmin_domain_state_link(&domain, enabled?)
|
||||
# Returns a link to the Virtualmin state change form for some domain
|
||||
sub virtualmin_domain_state_link
|
||||
{
|
||||
my ($d, $enabled) = @_;
|
||||
my $page = $enabled ? "disable_domain.cgi" : "enable_domain.cgi";
|
||||
my $label = $enabled ? $text{'enable_virtualmin_disable_label'} :
|
||||
$text{'enable_virtualmin_enable_label'};
|
||||
my $url = "../virtual-server/".$page."?dom=".&urlize($d->{'id'});
|
||||
return &ui_link("e_escape($url), "\"".$label."\"");
|
||||
}
|
||||
|
||||
# virtualmin_vhost_file_state_error(file, action)
|
||||
# Returns an error if a Virtualmin-owned site is being enabled or disabled here
|
||||
sub virtualmin_vhost_file_state_error
|
||||
{
|
||||
my ($file, $action) = @_;
|
||||
return undef if ($action ne "enable" && $action ne "disable");
|
||||
my $state_info = &vhost_file_state($file);
|
||||
return undef if ($state_info->{'source'} ne "virtualmin");
|
||||
my $d = $state_info->{'domain'};
|
||||
return undef if (!$d);
|
||||
my $state = lc($state_info->{'enabled'} ? $text{'index_enabled'} :
|
||||
$text{'index_disabled'});
|
||||
my $dom = "<tt>".&html_escape($d->{'dom'})."</tt>";
|
||||
my $link = &virtualmin_domain_state_link($d, $state_info->{'enabled'});
|
||||
return $state_info->{'enabled'} ?
|
||||
&text('enable_evirtualmin_disable', $dom, $state, $link) :
|
||||
&text('enable_evirtualmin_enable', $dom, $state, $link);
|
||||
}
|
||||
|
||||
# delete_virtuals_from_file(file, &virtualhosts...)
|
||||
# Deletes VirtualHost blocks from one file and removes the file if empty
|
||||
sub delete_virtuals_from_file
|
||||
{
|
||||
my ($file, @virts) = @_;
|
||||
return 0 if (!@virts);
|
||||
my $lref = &read_file_lines($file);
|
||||
foreach my $virt (sort { $b->{'line'} <=> $a->{'line'} } @virts) {
|
||||
my $len = $virt->{'eline'} - $virt->{'line'} + 1;
|
||||
splice(@$lref, $virt->{'line'}, $len);
|
||||
}
|
||||
my $empty = 1;
|
||||
foreach my $line (@$lref) {
|
||||
if ($line =~ /\S/) {
|
||||
$empty = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
&flush_file_lines($file);
|
||||
if ($empty) {
|
||||
foreach my $link (&vhost_file_links($file)) {
|
||||
&unlink_logged($link);
|
||||
}
|
||||
&unlink_logged($file);
|
||||
}
|
||||
&flush_config_cache();
|
||||
&update_last_config_change();
|
||||
return scalar(@virts);
|
||||
}
|
||||
|
||||
# renumber(&config, line, file, offset)
|
||||
# Recursively changes the line number of all directives from some file
|
||||
# beyond the given line.
|
||||
@@ -1985,16 +2413,16 @@ if ($config{'link_dir'}) {
|
||||
sub delete_webfile_link
|
||||
{
|
||||
local ($file) = @_;
|
||||
$file = &simplify_path(&resolve_links($file));
|
||||
if ($config{'link_dir'}) {
|
||||
local $short = $file;
|
||||
$short =~ s/^.*\///;
|
||||
opendir(LINKDIR, $config{'link_dir'});
|
||||
foreach my $f (readdir(LINKDIR)) {
|
||||
if ($f ne "." && $f ne ".." &&
|
||||
(&simplify_path(
|
||||
&resolve_links($config{'link_dir'}."/".$f)) eq $file ||
|
||||
$short eq $f)) {
|
||||
&unlink_logged($config{'link_dir'}."/".$f);
|
||||
if ($f ne "." && $f ne "..") {
|
||||
my $link = $config{'link_dir'}."/".$f;
|
||||
next if (!-l $link);
|
||||
if (&simplify_path(&resolve_links($link)) eq $file) {
|
||||
&unlink_logged($link);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(LINKDIR);
|
||||
|
||||
@@ -3,19 +3,90 @@
|
||||
|
||||
require './apache-lib.pl';
|
||||
&ReadParse();
|
||||
&error_setup($text{'delete_err'});
|
||||
@d = split(/\0/, $in{'d'});
|
||||
$file_action = $in{'toggle'} ? "toggle" : undef;
|
||||
&error_setup($file_action ? $text{'enable_err'} : $text{'delete_err'});
|
||||
$access{'vaddr'} || &error($text{'delete_ecannot'});
|
||||
$conf = &get_config();
|
||||
@d = split(/\0/, $in{'d'});
|
||||
$can_vhost_files = &can_manage_vhost_files();
|
||||
@d || &error($text{'delete_enone'});
|
||||
|
||||
if ($file_action) {
|
||||
&can_manage_vhost_files() || &error($text{'enable_elinkdir'});
|
||||
foreach $d (@d) {
|
||||
if ($d =~ /^file\t([^\t]+)/) {
|
||||
$file = $1;
|
||||
}
|
||||
elsif ($d !~ /^file\t/) {
|
||||
($vmembers, $vconf) = &get_virtual_config($d);
|
||||
next if (!$vconf || !&can_edit_virt($vconf));
|
||||
$file = $vconf->{'file'};
|
||||
}
|
||||
else {
|
||||
next;
|
||||
}
|
||||
$rfile = $file ? &simplify_path(&resolve_links($file)) : undef;
|
||||
$files{$rfile}++ if ($rfile && -f $rfile &&
|
||||
&can_manage_vhost_state_file($rfile));
|
||||
}
|
||||
@files = keys %files;
|
||||
@files || &error($text{'enable_enone'});
|
||||
foreach $file (@files) {
|
||||
$action = &vhost_file_toggle_action($file);
|
||||
$err = &virtualmin_vhost_file_state_error($file, $action);
|
||||
$err && &error($err);
|
||||
$file_actions{$file} = $action;
|
||||
}
|
||||
foreach $file (@files) {
|
||||
$err = $file_actions{$file} eq "enable" ?
|
||||
&enable_vhost_file($file) :
|
||||
&disable_vhost_file($file);
|
||||
$err && &error($err);
|
||||
}
|
||||
&webmin_log($file_action, "vhostfile", scalar(@files));
|
||||
&redirect("");
|
||||
exit;
|
||||
}
|
||||
if (!$in{'delete'}) {
|
||||
&error($text{'delete_eaction'});
|
||||
}
|
||||
|
||||
# Get them all
|
||||
foreach $d (@d) {
|
||||
if ($d =~ /^file\t([^\t]+)\t(\d+)$/) {
|
||||
push(@{$file_lines{$1}}, $2);
|
||||
next;
|
||||
}
|
||||
elsif ($d =~ /^file\t/) {
|
||||
next;
|
||||
}
|
||||
($vmembers, $vconf) = &get_virtual_config($d);
|
||||
$vconf || &error($text{'delete_egone'});
|
||||
&can_edit_virt($vconf) || &error(&text('delete_ecannot2',
|
||||
&virtual_name($vconf)));
|
||||
$can_vhost_files && &is_default_vhost($vconf) &&
|
||||
&error($text{'delete_edefault'});
|
||||
push(@virts, $vconf);
|
||||
}
|
||||
if (%file_lines) {
|
||||
foreach $file (keys %file_lines) {
|
||||
$rfile = &simplify_path(&resolve_links($file));
|
||||
next if (!$rfile || !-f $rfile ||
|
||||
!&can_manage_vhost_state_file($rfile));
|
||||
@fvirts = &find_virtuals_in_file($rfile);
|
||||
foreach $line (@{$file_lines{$file}}) {
|
||||
($vconf) = grep { $_->{'line'} == $line } @fvirts;
|
||||
$vconf || &error($text{'delete_egone'});
|
||||
&can_edit_virt($vconf) ||
|
||||
&error(&text('delete_ecannot2',
|
||||
&virtual_name($vconf)));
|
||||
&is_default_vhost($vconf) &&
|
||||
&error($text{'delete_edefault'});
|
||||
push(@{$file_virts{$rfile}}, $vconf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@virts || %file_virts || &error($text{'delete_enone'});
|
||||
|
||||
# Delete their structures
|
||||
&before_changing();
|
||||
@@ -28,6 +99,11 @@ foreach $vconf (@virts) {
|
||||
&unlock_all_files();
|
||||
&update_last_config_change();
|
||||
&after_changing();
|
||||
&webmin_log("virts", "delete", scalar(@virts));
|
||||
foreach $file (keys %file_virts) {
|
||||
&lock_file($file);
|
||||
$deleted += &delete_virtuals_from_file($file, @{$file_virts{$file}});
|
||||
&unlock_file($file);
|
||||
}
|
||||
$deleted += scalar(@virts);
|
||||
&webmin_log("virts", "delete", $deleted);
|
||||
&redirect("");
|
||||
|
||||
|
||||
103
apache/index.cgi
103
apache/index.cgi
@@ -102,6 +102,10 @@ if (&can_edit_virt()) {
|
||||
push(@vproxy, undef);
|
||||
$sn ||= &get_system_hostname();
|
||||
push(@vurl, $defport ? "http://$sn:$defport/" : "http://$sn/");
|
||||
push(@vfile, undef);
|
||||
push(@vstatus, "");
|
||||
push(@vsel, undef);
|
||||
push(@vfilemanage, 0);
|
||||
$showing_default++;
|
||||
}
|
||||
|
||||
@@ -128,16 +132,23 @@ elsif ($httpd_modules{'core'} >= 1.2) {
|
||||
$ba = &find_directive("ServerName", $conf);
|
||||
$nv{&to_ipaddress($ba ? $ba : &get_system_hostname())}++;
|
||||
}
|
||||
@virt = grep { &can_edit_virt($_) } @virt;
|
||||
$can_vhost_files = &can_manage_vhost_files();
|
||||
@vrows = &get_virtual_list_rows($conf);
|
||||
if ($config{'show_order'} == 1) {
|
||||
# sort by server name
|
||||
@virt = sort { &server_name_sort($a) cmp &server_name_sort($b) } @virt;
|
||||
@vrows = sort { &server_name_sort($a->{'virt'}) cmp
|
||||
&server_name_sort($b->{'virt'}) } @vrows;
|
||||
}
|
||||
elsif ($config{'show_order'} == 2) {
|
||||
# sort by IP address
|
||||
@virt = sort { &server_ip_sort($a) cmp &server_ip_sort($b) } @virt;
|
||||
@vrows = sort { &server_ip_sort($a->{'virt'}) cmp
|
||||
&server_ip_sort($b->{'virt'}) } @vrows;
|
||||
}
|
||||
foreach $v (@virt) {
|
||||
@virt = map { $_->{'virt'} } grep { $_->{'active'} } @vrows;
|
||||
%available_vhost_file = map { $_, 1 } &get_vhost_available_files()
|
||||
if ($can_vhost_files);
|
||||
foreach $r (@vrows) {
|
||||
$v = $r->{'virt'};
|
||||
$vm = $v->{'members'};
|
||||
if ($v->{'words'}->[0] =~ /^\[(\S+)\]:(\d+)$/) {
|
||||
# IPv6 address and port
|
||||
@@ -163,7 +174,7 @@ foreach $v (@virt) {
|
||||
$idx = &indexof($v, @$conf);
|
||||
push(@vidx, $idx);
|
||||
push(@vname, $text{'index_virt'});
|
||||
push(@vlink, "virt_index.cgi?virt=$idx");
|
||||
push(@vlink, $r->{'active'} ? "virt_index.cgi?virt=$idx" : undef);
|
||||
$sname = &find_directive("ServerName", $vm);
|
||||
local $daddr = $addr eq "_default_" ||
|
||||
($addr eq "*" && $httpd_modules{'core'} < 1.2);
|
||||
@@ -225,10 +236,34 @@ foreach $v (@virt) {
|
||||
}
|
||||
$sp = undef if ($sp == 80 && $prot eq "http" ||
|
||||
$sp == 443 && $prot eq "https");
|
||||
push(@vurl, $sp ? "$prot://$sn:$sp/" : "$prot://$sn/");
|
||||
push(@vurl, $r->{'active'} ?
|
||||
($sp ? "$prot://$sn:$sp/" : "$prot://$sn/") : undef);
|
||||
local $rfile = $r->{'file'} ? &simplify_path(&resolve_links($r->{'file'}))
|
||||
: undef;
|
||||
push(@vfile, $rfile);
|
||||
local $status = "";
|
||||
if ($can_vhost_files && $rfile && $available_vhost_file{$rfile}) {
|
||||
local $enabled = &vhost_file_state($rfile)->{'enabled'};
|
||||
$status = $enabled ? $text{'index_enabled'} :
|
||||
$text{'index_disabled'};
|
||||
}
|
||||
push(@vstatus, $status);
|
||||
local $file_manage = $can_vhost_files && $rfile &&
|
||||
$available_vhost_file{$rfile} &&
|
||||
&can_manage_vhost_state_file($rfile);
|
||||
push(@vfilemanage, $file_manage ? 1 : 0);
|
||||
local $sel;
|
||||
if ($r->{'active'} && (!$can_vhost_files || !&is_default_vhost($v))) {
|
||||
$sel = $idx;
|
||||
}
|
||||
elsif (!$r->{'active'} && $can_vhost_files && $rfile &&
|
||||
$available_vhost_file{$rfile} && $file_manage) {
|
||||
$sel = "file\t".$rfile."\t".$v->{'line'};
|
||||
}
|
||||
push(@vsel, $sel);
|
||||
}
|
||||
|
||||
if (@vlink == 1 && !$access{'global'} && $access{'virts'} ne "*" &&
|
||||
if (@vlink == 1 && $vlink[0] && !$access{'global'} && $access{'virts'} ne "*" &&
|
||||
!$access{'create'} && $access{'noconfig'}) {
|
||||
# Can only manage one vhost, so go direct to it
|
||||
&redirect($vlink[0]);
|
||||
@@ -297,7 +332,9 @@ if ($access{'global'}) {
|
||||
# work out select links
|
||||
print &ui_tabs_start_tab("mode", "list");
|
||||
#print $text{'index_desclist'},"<p>\n";
|
||||
$showdel = $access{'vaddr'} && ($vidx[0] || $vidx[1]);
|
||||
$showdel = $access{'vaddr'} &&
|
||||
grep { defined($_) && $_ ne "" } @vsel;
|
||||
$showtoggle = $can_vhost_files && grep { $_ } @vfilemanage;
|
||||
@links = ( );
|
||||
if ($showdel) {
|
||||
push(@links, &select_all_link("d"),
|
||||
@@ -326,8 +363,10 @@ if ($config{'max_servers'} && @vname > $config{'max_servers'}) {
|
||||
}
|
||||
elsif ($config{'show_list'} && scalar(@vname)) {
|
||||
# as list for people with lots of servers
|
||||
$list_form = "vhosts_form";
|
||||
if ($showdel) {
|
||||
print &ui_form_start("delete_vservs.cgi", "post");
|
||||
print &ui_form_start("delete_vservs.cgi", "post", undef,
|
||||
"id='$list_form'");
|
||||
}
|
||||
print &ui_links_row(\@links);
|
||||
print &ui_columns_start([
|
||||
@@ -337,19 +376,23 @@ elsif ($config{'show_list'} && scalar(@vname)) {
|
||||
$text{'index_port'},
|
||||
$text{'index_name'},
|
||||
$text{'index_root'},
|
||||
$can_vhost_files ? ( $text{'index_status'} ) : ( ),
|
||||
$text{'index_url'} ], 100);
|
||||
for($i=0; $i<@vname; $i++) {
|
||||
local @cols;
|
||||
push(@cols, &ui_link($vlink[$i], $vname[$i]) );
|
||||
push(@cols, $vlink[$i] ? &ui_link($vlink[$i], $vname[$i]) :
|
||||
$vname[$i] );
|
||||
push(@cols, &html_escape($vaddr[$i]));
|
||||
push(@cols, &html_escape($vport[$i]));
|
||||
push(@cols, $vserv[$i] || $text{'index_auto'});
|
||||
push(@cols, &html_escape($vproxy[$i]) ||
|
||||
&html_escape($vroot[$i]));
|
||||
push(@cols, &ui_link($vurl[$i], $text{'index_view'}) );
|
||||
if ($showdel && $vidx[$i]) {
|
||||
push(@cols, $vstatus[$i]) if ($can_vhost_files);
|
||||
push(@cols, $vurl[$i] ? &ui_link($vurl[$i], $text{'index_view'}) :
|
||||
"" );
|
||||
if ($showdel && defined($vsel[$i]) && $vsel[$i] ne "") {
|
||||
print &ui_checked_columns_row(\@cols, undef,
|
||||
"d", $vidx[$i]);
|
||||
"d", $vsel[$i]);
|
||||
}
|
||||
elsif ($showdel) {
|
||||
print &ui_columns_row([ "", @cols ]);
|
||||
@@ -361,13 +404,23 @@ elsif ($config{'show_list'} && scalar(@vname)) {
|
||||
print &ui_columns_end();
|
||||
print &ui_links_row(\@links);
|
||||
if ($showdel) {
|
||||
print &ui_form_end([ [ "delete", $text{'index_delete'} ] ]);
|
||||
if ($showtoggle) {
|
||||
print &ui_form_end_side_by_side($list_form,
|
||||
[ [ "delete", $text{'index_delete'} ] ],
|
||||
[ [ "toggle", $text{'index_toggle'}, undef,
|
||||
undef, "form=\"$list_form\"" ] ]);
|
||||
}
|
||||
else {
|
||||
print &ui_form_end([ [ "delete", $text{'index_delete'} ] ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
# as icons for niceness
|
||||
$list_form = "vhosts_form";
|
||||
if ($showdel) {
|
||||
print &ui_form_start("delete_vservs.cgi", "post");
|
||||
print &ui_form_start("delete_vservs.cgi", "post", undef,
|
||||
"id='$list_form'");
|
||||
}
|
||||
print &ui_links_row(\@links);
|
||||
print "<table width=100% cellpadding=5>\n";
|
||||
@@ -376,8 +429,9 @@ else {
|
||||
print '<div class="row icons-row inline-row">';
|
||||
&generate_icon("images/virt.gif", $vname[$i], $vlink[$i],
|
||||
undef, undef, undef,
|
||||
$vidx[$i] && $access{'vaddr'} ?
|
||||
&ui_checkbox("d", $vidx[$i]) : "");
|
||||
defined($vsel[$i]) && $vsel[$i] ne "" &&
|
||||
$access{'vaddr'} ?
|
||||
&ui_checkbox("d", $vsel[$i]) : "");
|
||||
print "</div>\n";
|
||||
print "</td> <td valign=top>\n";
|
||||
print "$vdesc[$i]<br>\n";
|
||||
@@ -397,12 +451,24 @@ else {
|
||||
print "<b>$text{'index_root'}</b> ",
|
||||
&html_escape($vroot[$i]),"</td> </tr>\n";
|
||||
}
|
||||
if ($can_vhost_files && $vstatus[$i]) {
|
||||
print "<tr><td colspan=2><b>$text{'index_status'}</b> ",
|
||||
$vstatus[$i],"</td></tr>\n";
|
||||
}
|
||||
print "</table></td> </tr>\n";
|
||||
}
|
||||
print "</table>\n";
|
||||
print &ui_links_row(\@links);
|
||||
if ($showdel) {
|
||||
print &ui_form_end([ [ "delete", $text{'index_delete'} ] ]);
|
||||
if ($showtoggle) {
|
||||
print &ui_form_end_side_by_side($list_form,
|
||||
[ [ "delete", $text{'index_delete'} ] ],
|
||||
[ [ "toggle", $text{'index_toggle'}, undef,
|
||||
undef, "form=\"$list_form\"" ] ]);
|
||||
}
|
||||
else {
|
||||
print &ui_form_end([ [ "delete", $text{'index_delete'} ] ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
print &ui_tabs_end_tab();
|
||||
@@ -492,4 +558,3 @@ return $addr eq '_default_' || $addr eq '*' ? undef :
|
||||
$addr =~ /^\[(\S+)\]$/ && &check_ip6address($1) ? $1 :
|
||||
&to_ipaddress($addr);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ index_listen=Listen on address (if needed)
|
||||
index_port=Port
|
||||
index_name=Server Name
|
||||
index_root=Document Root
|
||||
index_status=State
|
||||
index_enabled=Enabled
|
||||
index_disabled=Disabled
|
||||
index_url=URL
|
||||
index_view=Open..
|
||||
index_adddir=Allow access to this directory
|
||||
@@ -57,6 +60,7 @@ index_fmode1=Virtual servers file $1
|
||||
index_fmode1d=New file under virtual servers directory $1
|
||||
index_fmode2=Selected file..
|
||||
index_delete=Delete Selected Servers
|
||||
index_toggle=Toggle State
|
||||
|
||||
cvirt_ecannot=You are not allowed to create a virtual server
|
||||
cvirt_err=Failed to create virtual server
|
||||
@@ -1032,6 +1036,7 @@ log_stop=Stopped webserver
|
||||
log_apply=Applied changes
|
||||
log_manual=Manually edited configuration file $1
|
||||
log_virts_delete=Deleted $1 virtual servers
|
||||
log_toggle_vhostfile=Toggled state of $1 virtual host files
|
||||
|
||||
search_title=Find Servers
|
||||
search_notfound=No matching virtual servers found
|
||||
@@ -1148,6 +1153,22 @@ delete_err=Failed to delete virtual servers
|
||||
delete_enone=None selected
|
||||
delete_ecannot=You are not allowed to delete servers
|
||||
delete_ecannot2=You are not allowed to edit the server $1
|
||||
delete_eaction=No action was selected
|
||||
delete_egone=The selected virtual server no longer exists
|
||||
delete_edefault=The default virtual server cannot be deleted
|
||||
|
||||
enable_err=Failed to change virtual host file state
|
||||
enable_enone=No manageable virtual host files were selected
|
||||
enable_efile=Virtual host file does not exist or cannot be managed
|
||||
enable_elinkdir=No enabled virtual host links directory is configured
|
||||
enable_elink=Failed to create symbolic link $1 : $2
|
||||
enable_eunlink=Failed to remove symbolic link $1 : $2
|
||||
enable_elinkexists=The symbolic link $1 already exists
|
||||
enable_etest=Apache configuration test failed after changing the virtual host file state : $1
|
||||
enable_evirtualmin_disable=This Apache virtual host is managed by Virtualmin virtual server $1, which is currently $2. Site disabling should be done in Virtualmin using $3.
|
||||
enable_evirtualmin_enable=This Apache virtual host is managed by Virtualmin virtual server $1, which is currently $2. Site enabling should be done in Virtualmin using $3.
|
||||
enable_virtualmin_disable_label=Disable and Delete ⇾ Disable Virtual Server
|
||||
enable_virtualmin_enable_label=Disable and Delete ⇾ Enable Virtual Server
|
||||
|
||||
syslog_desc=Apache error log
|
||||
|
||||
|
||||
344
apache/t/vhost-files.t
Normal file
344
apache/t/vhost-files.t
Normal file
@@ -0,0 +1,344 @@
|
||||
#!/usr/bin/perl
|
||||
# Tests for Debian-style Apache sites-available/sites-enabled handling.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use File::Basename qw(dirname);
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use Cwd qw(abs_path);
|
||||
|
||||
my $root = abs_path(File::Spec->catdir(dirname(__FILE__), '..', '..'));
|
||||
my $tmp = abs_path(tempdir(CLEANUP => 1));
|
||||
my $webmin_config = File::Spec->catdir($tmp, 'webmin-config');
|
||||
my $webmin_var = File::Spec->catdir($tmp, 'webmin-var');
|
||||
my $apache_root = File::Spec->catdir($tmp, 'apache2');
|
||||
my $available = File::Spec->catdir($apache_root, 'sites-available');
|
||||
my $enabled = File::Spec->catdir($apache_root, 'sites-enabled');
|
||||
my $apache_conf = File::Spec->catfile($apache_root, 'apache2.conf');
|
||||
|
||||
make_path($webmin_config, $webmin_var, "$webmin_config/apache",
|
||||
"$webmin_var/apache", $apache_root, $available, $enabled);
|
||||
|
||||
sub write_text
|
||||
{
|
||||
my ($file, $text) = @_;
|
||||
open(my $fh, '>', $file) || die "Failed to write $file: $!";
|
||||
print $fh $text;
|
||||
close($fh) || die "Failed to close $file: $!";
|
||||
}
|
||||
|
||||
sub vhost_conf
|
||||
{
|
||||
my ($name, $rootdir) = @_;
|
||||
my $name_line = defined($name) ? " ServerName $name\n" : "";
|
||||
return "<VirtualHost *:80>\n".
|
||||
$name_line.
|
||||
" DocumentRoot $rootdir\n".
|
||||
"</VirtualHost>\n";
|
||||
}
|
||||
|
||||
my $default = File::Spec->catfile($available, '000-default.conf');
|
||||
my $alpha = File::Spec->catfile($available, 'alpha.conf');
|
||||
my $beta = File::Spec->catfile($available, 'beta.conf');
|
||||
my $charlie = File::Spec->catfile($available, 'charlie.conf');
|
||||
|
||||
write_text($default, vhost_conf(undef, '/srv/default'));
|
||||
write_text($alpha, vhost_conf('alpha.example', '/srv/alpha'));
|
||||
write_text($beta, vhost_conf('beta.example', '/srv/beta'));
|
||||
write_text($charlie, vhost_conf('charlie.example', '/srv/charlie'));
|
||||
write_text($apache_conf,
|
||||
"ServerRoot \"$apache_root\"\n".
|
||||
"Listen 80\n".
|
||||
"IncludeOptional $enabled/*.conf\n");
|
||||
|
||||
symlink($default, File::Spec->catfile($enabled, '000-default.conf')) ||
|
||||
die "Failed to symlink default: $!";
|
||||
symlink($alpha, File::Spec->catfile($enabled, 'alpha.conf')) ||
|
||||
die "Failed to symlink alpha: $!";
|
||||
symlink($charlie, File::Spec->catfile($enabled, 'charlie.conf')) ||
|
||||
die "Failed to symlink charlie: $!";
|
||||
|
||||
write_text(File::Spec->catfile($webmin_config, 'config'),
|
||||
"os_type=debian-linux\n".
|
||||
"os_version=12\n".
|
||||
"real_os_type=Debian Linux\n".
|
||||
"real_os_version=12\n");
|
||||
write_text(File::Spec->catfile($webmin_config, 'miniserv.conf'),
|
||||
"root=$root\n");
|
||||
write_text(File::Spec->catfile($webmin_config, 'apache', 'config'),
|
||||
"httpd_dir=$apache_root\n".
|
||||
"httpd_path=/bin/true\n".
|
||||
"httpd_conf=$apache_conf\n".
|
||||
"apachectl_path=/bin/true\n".
|
||||
"httpd_version=2.4.57\n".
|
||||
"test_apachectl=0\n".
|
||||
"test_config=1\n".
|
||||
"virt_file=$available\n".
|
||||
"link_dir=$enabled\n");
|
||||
|
||||
$ENV{'WEBMIN_CONFIG'} = $webmin_config;
|
||||
$ENV{'WEBMIN_VAR'} = $webmin_var;
|
||||
$ENV{'FOREIGN_MODULE_NAME'} = 'apache';
|
||||
$ENV{'FOREIGN_ROOT_DIRECTORY'} = $root;
|
||||
$ENV{'REMOTE_USER'} = 'root';
|
||||
|
||||
unshift(@INC, $root);
|
||||
require File::Spec->catfile($root, 'apache', 'apache-lib.pl');
|
||||
|
||||
{
|
||||
no warnings 'once';
|
||||
$main::text{'enable_elinkdir'} = 'No enabled virtual host links directory is configured';
|
||||
$main::text{'enable_efile'} = 'Virtual host file does not exist or cannot be managed';
|
||||
$main::text{'enable_elink'} = 'Failed to create symbolic link $1 : $2';
|
||||
$main::text{'enable_eunlink'} = 'Failed to remove symbolic link $1 : $2';
|
||||
$main::text{'enable_elinkexists'} = 'The symbolic link $1 already exists';
|
||||
$main::text{'enable_etest'} = 'Apache configuration test failed after changing the virtual host file state : $1';
|
||||
$main::text{'enable_evirtualmin_disable'} = 'This Apache virtual host is managed by Virtualmin virtual server $1, which is currently $2. Site disabling should be done in Virtualmin using $3.';
|
||||
$main::text{'enable_evirtualmin_enable'} = 'This Apache virtual host is managed by Virtualmin virtual server $1, which is currently $2. Site enabling should be done in Virtualmin using $3.';
|
||||
$main::text{'enable_virtualmin_disable_label'} = 'Disable and Delete ⇾ Disable Virtual Server';
|
||||
$main::text{'enable_virtualmin_enable_label'} = 'Disable and Delete ⇾ Enable Virtual Server';
|
||||
$main::text{'index_enabled'} = 'Enabled';
|
||||
$main::text{'index_disabled'} = 'Disabled';
|
||||
}
|
||||
|
||||
sub apache_config
|
||||
{
|
||||
main::flush_config_cache();
|
||||
my $conf = main::get_config();
|
||||
ok($conf, 'test apache config can be parsed');
|
||||
return $conf;
|
||||
}
|
||||
|
||||
sub row_names
|
||||
{
|
||||
return [ map {
|
||||
scalar(main::find_directive('ServerName', $_->{'virt'}->{'members'})) || ''
|
||||
} @_ ];
|
||||
}
|
||||
|
||||
sub row_states
|
||||
{
|
||||
return [ map { $_->{'active'} ? 'enabled' : 'disabled' } @_ ];
|
||||
}
|
||||
|
||||
subtest 'sites-available files are manageable and ordered' => sub {
|
||||
ok(main::can_manage_vhost_files(),
|
||||
'sites-available/enabled dirs are manageable');
|
||||
is_deeply(
|
||||
[ main::get_vhost_available_files() ],
|
||||
[ $default, $alpha, $beta, $charlie ],
|
||||
'available files are listed in stable filename order',
|
||||
);
|
||||
|
||||
my @rows = main::get_virtual_list_rows(apache_config());
|
||||
is_deeply(row_names(@rows),
|
||||
[ '', 'alpha.example', 'beta.example', 'charlie.example' ],
|
||||
'disabled rows stay in sites-available order');
|
||||
is_deeply(row_states(@rows),
|
||||
[ 'enabled', 'enabled', 'disabled', 'enabled' ],
|
||||
'row active state follows sites-enabled symlinks');
|
||||
ok(!main::can_manage_vhost_file($default),
|
||||
'default virtual host file is not file-state manageable');
|
||||
};
|
||||
|
||||
subtest 'disable removes only the enabled symlink' => sub {
|
||||
no warnings 'once';
|
||||
unlink($main::last_config_change_flag);
|
||||
unlink($main::last_restart_time_flag);
|
||||
main::restart_last_restart_time();
|
||||
my $old = time() - 10;
|
||||
utime($old, $old, $main::last_restart_time_flag);
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::test_config = sub { return undef; };
|
||||
is(main::disable_vhost_file($alpha), undef, 'disable succeeds');
|
||||
}
|
||||
ok(main::needs_config_restart(),
|
||||
'disable marks config as needing apply');
|
||||
|
||||
ok(-f $alpha, 'disable leaves the sites-available file in place');
|
||||
ok(!-e File::Spec->catfile($enabled, 'alpha.conf'),
|
||||
'disable removes the sites-enabled symlink');
|
||||
|
||||
my @rows = main::get_virtual_list_rows(apache_config());
|
||||
is_deeply(row_names(@rows),
|
||||
[ '', 'alpha.example', 'beta.example', 'charlie.example' ],
|
||||
'disabled row remains in the same list position');
|
||||
is_deeply(row_states(@rows),
|
||||
[ 'enabled', 'disabled', 'disabled', 'enabled' ],
|
||||
'disabled row status is updated');
|
||||
};
|
||||
|
||||
subtest 'enable creates a symlink without touching the source file' => sub {
|
||||
no warnings 'once';
|
||||
unlink($main::last_config_change_flag);
|
||||
unlink($main::last_restart_time_flag);
|
||||
main::restart_last_restart_time();
|
||||
my $old = time() - 10;
|
||||
utime($old, $old, $main::last_restart_time_flag);
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::test_config = sub { return undef; };
|
||||
is(main::enable_vhost_file($beta), undef, 'enable succeeds');
|
||||
}
|
||||
ok(main::needs_config_restart(),
|
||||
'enable marks config as needing apply');
|
||||
|
||||
my $link = File::Spec->catfile($enabled, 'beta.conf');
|
||||
ok(-f $beta, 'enable leaves the sites-available file in place');
|
||||
ok(-l $link, 'enable creates the sites-enabled symlink');
|
||||
is(readlink($link), $beta, 'enabled symlink points to the available file');
|
||||
ok(main::vhost_file_enabled($beta), 'vhost_file_enabled sees the symlink');
|
||||
};
|
||||
|
||||
subtest 'same-name symlink to another target is not disabled' => sub {
|
||||
my $otherdir = File::Spec->catdir($tmp, 'other-sites');
|
||||
my $other = File::Spec->catfile($otherdir, 'charlie.conf');
|
||||
my $link = File::Spec->catfile($enabled, 'charlie.conf');
|
||||
make_path($otherdir);
|
||||
write_text($other, vhost_conf('other.example', '/srv/other'));
|
||||
unlink($link) || die "Failed to remove charlie link: $!";
|
||||
symlink($other, $link) || die "Failed to symlink other charlie: $!";
|
||||
|
||||
ok(!main::vhost_file_enabled($charlie),
|
||||
'same-name symlink to another file is not considered enabled');
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::test_config = sub { return undef; };
|
||||
is(main::disable_vhost_file($charlie), undef, 'disable is a no-op');
|
||||
}
|
||||
ok(-l $link, 'same-name symlink to another target is preserved');
|
||||
is(readlink($link), $other, 'preserved symlink target is unchanged');
|
||||
};
|
||||
|
||||
subtest 'file-level actions require access to every virtual host in the file' => sub {
|
||||
my $mixed = File::Spec->catfile($available, 'mixed.conf');
|
||||
write_text($mixed,
|
||||
vhost_conf('alpha.example', '/srv/mixed-alpha').
|
||||
vhost_conf('hidden.example', '/srv/mixed-hidden'));
|
||||
|
||||
{
|
||||
no warnings 'once';
|
||||
local $main::access{'virts'} = 'alpha.example:80';
|
||||
ok(!main::can_manage_vhost_file($mixed),
|
||||
'mixed-access file cannot be managed by a restricted user');
|
||||
}
|
||||
ok(main::can_manage_vhost_file($mixed),
|
||||
'shared file can be managed when all contained vhosts are allowed');
|
||||
};
|
||||
|
||||
subtest 'state helpers enforce allowed files and ACLs directly' => sub {
|
||||
my $outside = File::Spec->catfile($tmp, 'outside.conf');
|
||||
write_text($outside, vhost_conf('outside.example', '/srv/outside'));
|
||||
is(main::enable_vhost_file($outside),
|
||||
'Virtual host file does not exist or cannot be managed',
|
||||
'enable rejects files outside sites-available');
|
||||
|
||||
my $mixed = File::Spec->catfile($available, 'state-mixed.conf');
|
||||
write_text($mixed,
|
||||
vhost_conf('alpha.example', '/srv/state-alpha').
|
||||
vhost_conf('hidden.example', '/srv/state-hidden'));
|
||||
{
|
||||
no warnings 'once';
|
||||
local $main::access{'virts'} = 'alpha.example:80';
|
||||
is(main::enable_vhost_file($mixed),
|
||||
'Virtual host file does not exist or cannot be managed',
|
||||
'enable rejects mixed-access files without relying on caller validation');
|
||||
}
|
||||
};
|
||||
|
||||
subtest 'apache configtest failure rolls back link changes' => sub {
|
||||
my $delta = File::Spec->catfile($available, 'delta.conf');
|
||||
my $delta_link = File::Spec->catfile($enabled, 'delta.conf');
|
||||
write_text($delta, vhost_conf('delta.example', '/srv/delta'));
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::test_config = sub { return 'bad config'; };
|
||||
like(main::enable_vhost_file($delta), qr/bad config/,
|
||||
'failed enable reports apache configtest output');
|
||||
}
|
||||
ok(!-e $delta_link, 'failed enable removes the new symlink');
|
||||
|
||||
symlink($delta, $delta_link) || die "Failed to symlink delta: $!";
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::test_config = sub { return 'bad config'; };
|
||||
like(main::disable_vhost_file($delta), qr/bad config/,
|
||||
'failed disable reports apache configtest output');
|
||||
}
|
||||
ok(-l $delta_link, 'failed disable restores the removed symlink');
|
||||
is(readlink($delta_link), $delta, 'restored symlink target is unchanged');
|
||||
};
|
||||
|
||||
subtest 'Virtualmin-managed virtual host files cannot be toggled directly' => sub {
|
||||
my $enabled_domain = File::Spec->catfile($available, 'vm-enabled.conf');
|
||||
my $disabled_domain = File::Spec->catfile($available, 'vm-disabled.conf');
|
||||
write_text($enabled_domain,
|
||||
vhost_conf('www.vm-enabled.example', '/srv/vm-enabled'));
|
||||
write_text($disabled_domain,
|
||||
vhost_conf('vm-disabled.example', '/srv/vm-disabled'));
|
||||
|
||||
{
|
||||
no warnings qw(redefine once);
|
||||
local %main::apache_virtualmin_domain_for_file_cache;
|
||||
local %main::apache_virtualmin_domain_by_name_cache;
|
||||
local *main::virtualmin_available = sub { return 1; };
|
||||
local *main::virtualmin_domain_by_name = sub {
|
||||
my ($name) = @_;
|
||||
return $name eq 'vm-enabled.example' ?
|
||||
{ 'dom' => $name, 'id' => '12345',
|
||||
'disabled' => '' } :
|
||||
$name eq 'vm-disabled.example' ?
|
||||
{ 'dom' => $name, 'id' => '67890',
|
||||
'disabled' => 'web' } :
|
||||
undef;
|
||||
};
|
||||
|
||||
my $disable_err =
|
||||
main::virtualmin_vhost_file_state_error($enabled_domain,
|
||||
'disable');
|
||||
my $enabled_state = main::vhost_file_state($enabled_domain);
|
||||
is($enabled_state->{'source'}, 'virtualmin',
|
||||
'Virtualmin is the effective state source for managed files');
|
||||
ok($enabled_state->{'enabled'},
|
||||
'Virtualmin enabled domain is reported as enabled');
|
||||
is(main::vhost_file_toggle_action($enabled_domain), 'disable',
|
||||
'toggle action follows the Virtualmin enabled state');
|
||||
like($disable_err, qr/currently enabled/,
|
||||
'Virtualmin state is included for enabled domains');
|
||||
like($disable_err, qr/Disable Virtual Server/,
|
||||
'disabling directs users to Virtualmin disable action');
|
||||
like($disable_err,
|
||||
qr{virtual-server/disable_domain\.cgi\?dom=12345},
|
||||
'disabling links to the Virtualmin disable form');
|
||||
|
||||
my $enable_err =
|
||||
main::virtualmin_vhost_file_state_error($disabled_domain,
|
||||
'enable');
|
||||
my $disabled_state = main::vhost_file_state($disabled_domain);
|
||||
is($disabled_state->{'source'}, 'virtualmin',
|
||||
'Virtualmin remains the state source for disabled domains');
|
||||
ok(!$disabled_state->{'enabled'},
|
||||
'Virtualmin disabled domain is reported as disabled');
|
||||
is(main::vhost_file_toggle_action($disabled_domain), 'enable',
|
||||
'toggle action follows the Virtualmin disabled state');
|
||||
like($enable_err, qr/currently disabled/,
|
||||
'Virtualmin state is included for disabled domains');
|
||||
like($enable_err, qr/Enable Virtual Server/,
|
||||
'enabling directs users to Virtualmin enable action');
|
||||
like($enable_err,
|
||||
qr{virtual-server/enable_domain\.cgi\?dom=67890},
|
||||
'enabling links to the Virtualmin enable form');
|
||||
|
||||
is(main::virtualmin_vhost_file_state_error($alpha, 'disable'),
|
||||
undef, 'non-Virtualmin virtual host files can still be toggled');
|
||||
}
|
||||
};
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user