mirror of
https://github.com/webmin/webmin.git
synced 2026-09-22 21:40:39 +01:00
This PR fixes incorrect line numbers when Apache directives contain nested blocks. It preserves comments during rewrites and correctly tracks every nested line. This prevents Virtualmin from corrupting custom Apache configurations. Fixes: https://github.com/virtualmin/virtualmin-gpl/issues/1153
2767 lines
72 KiB
Perl
Executable File
2767 lines
72 KiB
Perl
Executable File
# apache-lib.pl
|
|
# Common functions for apache configuration
|
|
|
|
BEGIN { push(@INC, ".."); };
|
|
use WebminCore;
|
|
$directive_type_count = 20;
|
|
|
|
our ($saved_conf_files);
|
|
|
|
if ($module_name ne 'htaccess') {
|
|
&init_config();
|
|
%access = &get_module_acl();
|
|
@access_types = $access{'types'} eq '*' ? (0 .. $directive_type_count)
|
|
: split(/\s+/, $access{'types'});
|
|
}
|
|
else {
|
|
@access_types = (0 .. $directive_type_count);
|
|
}
|
|
map { $access_types{$_}++ } @access_types;
|
|
$site_file = ($config{'webmin_apache'} || $module_config_directory)."/site";
|
|
$httpd_info_cache = $module_config_directory."/httpd-info";
|
|
$last_config_change_flag = $module_var_directory."/config-flag";
|
|
$last_restart_time_flag = $module_var_directory."/restart-flag";
|
|
|
|
# Check if a list of supported modules needs to be built. This is done
|
|
# if the Apache binary changes, when Webmin is upgraded, or once every five
|
|
# minutes if automatic rebuilding is enabled.
|
|
if ($module_name ne 'htaccess') {
|
|
&create_site_file();
|
|
}
|
|
&read_site_file();
|
|
|
|
$apache_docbase = $config{'apache_docbase'} ? $config{'apache_docbase'} :
|
|
$httpd_modules{'core'} >= 2.0 ?
|
|
"http://httpd.apache.org/docs-2.0/mod/" :
|
|
"http://httpd.apache.org/docs/mod/";
|
|
|
|
# create_site_file()
|
|
# If the Apache binary or Webmin version has changed, create the site
|
|
# file containing all known Apache modules
|
|
sub create_site_file
|
|
{
|
|
my %oldsite;
|
|
my $httpd = &find_httpd();
|
|
my @st = stat($httpd);
|
|
&read_file($site_file, \%oldsite);
|
|
my @sst = stat($site_file);
|
|
if ($oldsite{'path'} ne $httpd ||
|
|
$oldsite{'size'} != $st[7] ||
|
|
$oldsite{'webmin'} != &get_webmin_version() ||
|
|
$config{'auto_mods'} && $sst[9] < time()-5*60) {
|
|
# Need to build list of supported modules
|
|
my ($ver, $mods, $fullver) = &httpd_info($httpd);
|
|
if ($ver) {
|
|
my @allmods = &available_modules();
|
|
my @mods = map { "$_/$ver" }
|
|
&configurable_modules(\@allmods);
|
|
foreach my $m (@mods) {
|
|
if ($m =~ /(\S+)\/(\S+)/) {
|
|
$httpd_modules{$1} = $2;
|
|
}
|
|
}
|
|
# Call again now that known modules have been set, as
|
|
# sometimes there are dependencies due to LoadModule
|
|
# statements in an IfModule block
|
|
undef(@get_config_cache);
|
|
@allmods = &available_modules();
|
|
@mods = map { "$_/$ver" }
|
|
&configurable_modules(\@allmods);
|
|
my %site = ( 'size' => $st[7],
|
|
'path' => $httpd,
|
|
'modules' => join(' ', @mods),
|
|
'allmodules' => join(' ', @allmods),
|
|
'version' => $ver,
|
|
'fullversion' => $fullver,
|
|
'webmin' => &get_webmin_version() );
|
|
&lock_file($site_file);
|
|
&write_file($site_file, \%site);
|
|
chmod(0644, $site_file);
|
|
&unlock_file($site_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
# read_site_file()
|
|
# Read the site-specific setup file, then require in all the module-specific
|
|
# .pl files
|
|
sub read_site_file
|
|
{
|
|
if (&read_file($site_file, \%site)) {
|
|
foreach my $m (split(/\s+/, $site{'modules'})) {
|
|
if ($m =~ /(\S+)\/(\S+)/) {
|
|
$httpd_modules{$1} = $2;
|
|
}
|
|
}
|
|
foreach my $m (split(/\s+/, $site{'allmodules'})) {
|
|
$all_httpd_modules{$m} = $site{'version'};
|
|
}
|
|
foreach my $m (keys %httpd_modules) {
|
|
if (!-r "$module_root_directory/$m.pl") {
|
|
delete($httpd_modules{$m});
|
|
}
|
|
}
|
|
foreach my $f (split(/\s+/, $site{'htaccess'})) {
|
|
if (-r $f) { push(@htaccess_files, $f); }
|
|
}
|
|
foreach my $m (keys %httpd_modules) {
|
|
do "$m.pl";
|
|
}
|
|
foreach my $d (split(/\s+/, $site{'defines'})) {
|
|
$httpd_defines{$d}++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# parse_config_file(handle, lines, file, [recursive])
|
|
# Parses lines of text from some config file into a data structure. The
|
|
# return value is an array of references, one for each directive in the file.
|
|
# Each reference points to an associative array containing
|
|
# line - The line number this directive is at
|
|
# eline - The line number this directive ends at
|
|
# file - The file this directive is from
|
|
# type - 0 for a normal directive, 1 for a container directive
|
|
# name - The name of this directive
|
|
# value - Value (possibly with spaces)
|
|
# members - For type 1, a reference to the array of members
|
|
# indent - Number of spaces before the name
|
|
# comment - Full text for a comment stored as a dummy directive
|
|
sub parse_config_file
|
|
{
|
|
local($fh, @rv, $line, %dummy);
|
|
$fh = $_[0];
|
|
$dummy{'line'} = $dummy{'eline'} = $_[1]-1;
|
|
$dummy{'file'} = $_[2];
|
|
$dummy{'type'} = 0;
|
|
$dummy{'name'} = "dummy";
|
|
@rv = (\%dummy);
|
|
local %defs;
|
|
foreach my $d (&get_httpd_defines()) {
|
|
if ($d =~ /^(\S+)=(.*)$/) {
|
|
$defs{$1} = $2;
|
|
}
|
|
else {
|
|
$defs{$d} = '';
|
|
}
|
|
}
|
|
while($line = <$fh>) {
|
|
$line =~ s/\r|\n//g;
|
|
if ($line =~ /^(\s*)(#.*)$/) {
|
|
# Keep comments in the structure so block rewrites preserve them
|
|
local(%dir);
|
|
%dir = ('line', $_[1],
|
|
'eline', $_[1],
|
|
'file', $_[2],
|
|
'type', 0,
|
|
'name', 'dummy',
|
|
'comment', $2);
|
|
local $indent = $1;
|
|
$indent =~ s/\t/ /g;
|
|
$dir{'indent'} = length($indent);
|
|
push(@rv, \%dir);
|
|
$_[1]++;
|
|
}
|
|
elsif ($line =~ /^\s*<\/(\S+)\s*(.*)>/) {
|
|
# end of a container directive. This can only happen in a
|
|
# recursive call to this function
|
|
$_[1]++;
|
|
last if (lc($_[3]) eq lc($1));
|
|
}
|
|
elsif ($line =~ /^\s*<IfModule\s+(\!?)(\S+)\.c>/i ||
|
|
$line =~ /^\s*<IfModule\s+(\!?)(\S+)>/i) {
|
|
# start of an IfModule block. Read it, and if the module
|
|
# exists put the directives in this section.
|
|
local ($not, $mod) = ($1, $2);
|
|
local $oldline = $_[1];
|
|
$_[1]++;
|
|
local @dirs = &parse_config_file($fh, $_[1], $_[2], 'IfModule');
|
|
local $altmod = $mod;
|
|
$altmod =~ s/^(\S+)_module$/mod_$1/g;
|
|
local $mpmmod = $mod;
|
|
$mpmmod =~ s/^mpm_//; $mpmmod =~ s/_module$//;
|
|
if ($mod eq "prefork") {
|
|
# Special case for all the prefork aliases
|
|
($mod, $altmod, $mpmmod) = ("mod_".$mod."_module",
|
|
"mod_mpm_".$mod, $mod);
|
|
}
|
|
if (!$not && $httpd_modules{$mod} ||
|
|
$not && !$httpd_modules{$mod} ||
|
|
!$not && $httpd_modules{$altmod} ||
|
|
$not && !$httpd_modules{$altmod} ||
|
|
!$not && $httpd_modules{$mpmmod} ||
|
|
$not && !$httpd_modules{$mpmmod}
|
|
) {
|
|
# use the directives..
|
|
push(@rv, { 'line', $oldline,
|
|
'eline', $oldline,
|
|
'file', $_[2],
|
|
'name', "<IfModule $not$mod>" });
|
|
push(@rv, @dirs);
|
|
push(@rv, { 'line', $_[1]-1,
|
|
'eline', $_[1]-1,
|
|
'file', $_[2],
|
|
'name', "</IfModule>" });
|
|
}
|
|
}
|
|
elsif ($line =~ /^\s*<IfDefine\s+(\!?)(\S+)>/i) {
|
|
# start of an IfDefine block. Read it, and if the define
|
|
# exists put the directives in this section
|
|
local ($not, $def) = ($1, $2);
|
|
local $oldline = $_[1];
|
|
$_[1]++;
|
|
local @dirs = &parse_config_file($fh, $_[1], $_[2], 'IfDefine');
|
|
if (!$not && defined($defs{$def}) ||
|
|
$not && !defined($defs{$def})) {
|
|
# use the directives..
|
|
push(@rv, { 'line', $oldline,
|
|
'eline', $oldline,
|
|
'file', $_[2],
|
|
'name', "<IfDefine $not$def>" });
|
|
push(@rv, @dirs);
|
|
push(@rv, { 'line', $_[1]-1,
|
|
'eline', $_[1]-1,
|
|
'file', $_[2],
|
|
'name', "</IfDefine>" });
|
|
}
|
|
}
|
|
elsif ($line =~ /^\s*<IfVersion\s+(\!?)(\S*)\s*(\S+)>/i) {
|
|
# Start of an IfVersion block. Read it, and if the version
|
|
# matches put the directives in this section
|
|
local ($not, $op, $ver) = ($1, $2, $3);
|
|
local $oldline = $_[1];
|
|
$_[1]++;
|
|
local @dirs = &parse_config_file($fh, $_[1], $_[2], 'IfVersion');
|
|
$op ||= "=";
|
|
local $match = 0;
|
|
local $myver = $httpd_modules{'core'};
|
|
$myver =~ s/^(\d+)\.(\d)(\d+)$/$1.$2.$3/;
|
|
if ($op eq "=" || $op eq "==") {
|
|
if ($ver =~ /^\/(.*)\/$/) {
|
|
$match = 1 if ($myver =~ /$1/);
|
|
}
|
|
else {
|
|
$match = 1 if ($myver eq $ver);
|
|
}
|
|
}
|
|
elsif ($op eq ">") {
|
|
$match = 1 if ($myver > $ver);
|
|
}
|
|
elsif ($op eq ">=") {
|
|
$match = 1 if ($myver >= $ver);
|
|
}
|
|
elsif ($op eq "<") {
|
|
$match = 1 if ($myver < $ver);
|
|
}
|
|
elsif ($op eq "<=") {
|
|
$match = 1 if ($myver <= $ver);
|
|
}
|
|
elsif ($op eq "~") {
|
|
$match = 1 if ($myver =~ /$ver/);
|
|
}
|
|
$match = !$match if ($not);
|
|
if ($match) {
|
|
# use the directives..
|
|
push(@rv, { 'line', $oldline,
|
|
'eline', $oldline,
|
|
'file', $_[2],
|
|
'name', "<IfVersion $not$op $ver>" });
|
|
push(@rv, @dirs);
|
|
push(@rv, { 'line', $_[1]-1,
|
|
'eline', $_[1]-1,
|
|
'file', $_[2],
|
|
'name', "</IfVersion>" });
|
|
}
|
|
}
|
|
elsif ($line =~ /^(\s*)<(\S+)\s*(.*)>/) {
|
|
# start of a container directive. The first member is a dummy
|
|
# directive at the same line as the container
|
|
local(%dir, @members);
|
|
%dir = ('line', $_[1],
|
|
'file', $_[2],
|
|
'type', 1,
|
|
'name', $2,
|
|
'value', $3);
|
|
local $indent = $1;
|
|
$dir{'value'} =~ s/\s+$//g;
|
|
$dir{'words'} = &wsplit($dir{'value'});
|
|
$_[1]++;
|
|
@members = &parse_config_file($fh, $_[1], $_[2], $dir{'name'});
|
|
$dir{'members'} = \@members;
|
|
$dir{'eline'} = $_[1]-1;
|
|
$indent =~ s/\t/ /g;
|
|
$dir{'indent'} = length($indent);
|
|
push(@rv, \%dir);
|
|
}
|
|
elsif ($line =~ /^(\s*)(\S+)\s*(.*)$/) {
|
|
# normal directive
|
|
local(%dir);
|
|
%dir = ('line', $_[1],
|
|
'eline', $_[1],
|
|
'file', $_[2],
|
|
'type', 0,
|
|
'name', $2,
|
|
'value', $3);
|
|
local $indent = $1;
|
|
$indent =~ s/\t/ /g;
|
|
$dir{'indent'} = length($indent);
|
|
if ($dir{'value'} =~ s/\\$//g) {
|
|
# multi-line directive!
|
|
while($line = <$fh>) {
|
|
chop($line);
|
|
$cont = ($line =~ s/\\$//g);
|
|
$dir{'value'} .= $line;
|
|
$dir{'eline'} = ++$_[1];
|
|
if (!$cont) { last; }
|
|
}
|
|
}
|
|
$dir{'value'} =~ s/\s+$//g;
|
|
if ($dir{'value'} =~ /^(.*)\$\{([^\}]+)\}(.*)$/) {
|
|
# Contains a variable .. replace with define
|
|
local $v = $defs{$2};
|
|
if ($v) {
|
|
$dir{'value'} = $1.$v.$3;
|
|
}
|
|
}
|
|
$dir{'words'} = &wsplit($dir{'value'});
|
|
push(@rv, \%dir);
|
|
$_[1]++;
|
|
}
|
|
else {
|
|
# blank or comment line
|
|
$_[1]++;
|
|
}
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# wsplit(string)
|
|
# Splits a string like foo "foo \"bar\"" bazzz into an array of words
|
|
sub wsplit
|
|
{
|
|
local($s, @rv); $s = $_[0];
|
|
$s =~ s/\\\"/\0/g;
|
|
while($s =~ /^"([^"]*)"\s*(.*)$/ || $s =~ /^(\S+)\s*(.*)$/) {
|
|
$w = $1; $s = $2;
|
|
$w =~ s/\0/"/g; push(@rv, $w);
|
|
}
|
|
return \@rv;
|
|
}
|
|
|
|
# wjoin(word, word, ...)
|
|
sub wjoin
|
|
{
|
|
local(@rv, $w);
|
|
foreach $w (@_) {
|
|
if ($w =~ /^\S+$/) { push(@rv, $w); }
|
|
else { push(@rv, "\"$w\""); }
|
|
}
|
|
return join(' ', @rv);
|
|
}
|
|
|
|
# find_directive(name, &directives, [1stword])
|
|
# Returns the values of directives matching some name
|
|
sub find_directive
|
|
{
|
|
local (@vals, $ref);
|
|
foreach $ref (@{$_[1]}) {
|
|
if (lc($ref->{'name'}) eq lc($_[0])) {
|
|
push(@vals, $_[2] ? $ref->{'words'}->[0] : $ref->{'value'});
|
|
}
|
|
}
|
|
return wantarray ? @vals : !@vals ? undef : $vals[$#vals];
|
|
}
|
|
|
|
# find_directive_struct(name, &directives)
|
|
# Returns references to directives matching some name
|
|
sub find_directive_struct
|
|
{
|
|
local (@vals, $ref);
|
|
foreach $ref (@{$_[1]}) {
|
|
if (lc($ref->{'name'}) eq lc($_[0])) {
|
|
push(@vals, $ref);
|
|
}
|
|
}
|
|
return wantarray ? @vals : !@vals ? undef : $vals[$#vals];
|
|
}
|
|
|
|
# find_vdirective(name, &virtualdirectives, &directives, [1stword])
|
|
# Looks for some directive in a <VirtualHost> section, and then in the
|
|
# main section
|
|
sub find_vdirective
|
|
{
|
|
if ($_[1]) {
|
|
$rv = &find_directive($_[0], $_[1], $_[3]);
|
|
if ($rv) { return $rv; }
|
|
}
|
|
return &find_directive($_[0], $_[2], $_[3]);
|
|
}
|
|
|
|
# get_config()
|
|
# Returns the entire config structure
|
|
sub get_config
|
|
{
|
|
local($acc, $res, $lnum, $conf, @virt, $v, $mref, $inc);
|
|
if (@get_config_cache) {
|
|
return \@get_config_cache;
|
|
}
|
|
|
|
# read primary config file
|
|
($conf) = &find_httpd_conf();
|
|
return undef if (!$conf);
|
|
my %seenfiles;
|
|
@get_config_cache = &get_config_file($conf, \%seenfiles);
|
|
|
|
# Read main resource and access config files
|
|
$lnum = 0;
|
|
$res = &find_directive("ResourceConfig", \@get_config_cache);
|
|
if (!$res) { $res = $config{'srm_conf'}; }
|
|
if (!$res) { $res = "$config{'httpd_dir'}/conf/srm.conf"; }
|
|
if (!-r &translate_filename($res)) {
|
|
$res = "$config{'httpd_dir'}/etc/srm.conf";
|
|
}
|
|
push(@get_config_cache, &get_config_file($res, \%seenfiles));
|
|
|
|
$lnum = 0;
|
|
$acc = &find_directive("AccessConfig", \@get_config_cache);
|
|
if (!$acc) { $acc = $config{'access_conf'}; }
|
|
if (!$acc) { $acc = "$config{'httpd_dir'}/conf/access.conf"; }
|
|
if (!-r &translate_filename($acc)) {
|
|
$acc = "$config{'httpd_dir'}/etc/access.conf";
|
|
}
|
|
push(@get_config_cache, &get_config_file($acc, \%seenfiles));
|
|
|
|
# Read extra config files in VirtualHost sections
|
|
@virt = &find_directive_struct("VirtualHost", \@get_config_cache);
|
|
foreach $v (@virt) {
|
|
my %seenfiles;
|
|
$mref = $v->{'members'};
|
|
foreach $idn ("ResourceConfig", "AccessConfig", "Include", "IncludeOptional") {
|
|
foreach $inc (&find_directive_struct($idn, $mref)) {
|
|
local @incs = &expand_apache_include(
|
|
$inc->{'words'}->[0]);
|
|
foreach my $ginc (@incs) {
|
|
push(@$mref, &get_config_file($ginc,
|
|
\%seenfiles));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
my ($file, $seen) = @_;
|
|
|
|
# Convert sites-enabled to real path in sites-available
|
|
$file = &simplify_path(&resolve_links($file));
|
|
return ( ) if ($seen && $seen->{$file}++);
|
|
local @rv;
|
|
if (opendir(DIR, $file)) {
|
|
# Is a directory .. parse all files!
|
|
local @files = readdir(DIR);
|
|
closedir(DIR);
|
|
foreach my $f (sort { $a cmp $b } @files) {
|
|
next if ($f =~ /^\./);
|
|
push(@rv, &get_config_file("$file/$f", $seen));
|
|
}
|
|
}
|
|
else {
|
|
# Just a normal config file
|
|
local $lnum = 0;
|
|
&open_readfile(CONF, $file);
|
|
@rv = &parse_config_file(CONF, $lnum, $file);
|
|
close(CONF);
|
|
}
|
|
|
|
# Expand Include directives
|
|
foreach $inc (&find_directive_struct("Include", \@rv),
|
|
&find_directive_struct("IncludeOptional", \@rv)) {
|
|
local @incs = &expand_apache_include($inc->{'words'}->[0]);
|
|
foreach my $ginc (@incs) {
|
|
push(@rv, &get_config_file($ginc, $seen));
|
|
}
|
|
}
|
|
|
|
return @rv;
|
|
}
|
|
|
|
# expand_apache_include(dir)
|
|
# Given an include directive value, returns a list of matching files
|
|
sub expand_apache_include
|
|
{
|
|
local ($incdir) = @_;
|
|
if ($incdir !~ /^\//) { $incdir = "$config{'httpd_dir'}/$incdir"; }
|
|
if ($incdir =~ /^(.*)\[\^([^\]]+)\](.*)$/) {
|
|
# A glob like /etc/[^.#]*.conf , which cannot be handled
|
|
# by Perl's glob function!
|
|
local $before = $1;
|
|
local $after = $3;
|
|
local %reject = map { $_, 1 } split(//, $2);
|
|
$reject{'*'} = $reject{'?'} = $reject{'['} = $reject{']'} =
|
|
$reject{'/'} = $reject{'$'} = $reject{'('} = $reject{')'} =
|
|
$reject{'!'} = 1;
|
|
local $accept = join("", grep { !$reject{$_} } map { chr($_) } (32 .. 126));
|
|
$incdir = $before."[".$accept."]".$after;
|
|
}
|
|
return sort { $a cmp $b } glob($incdir);
|
|
}
|
|
|
|
# get_virtual_config(index|name)
|
|
# Returns the Apache config block with some index in the main config, or name
|
|
sub get_virtual_config
|
|
{
|
|
local ($name) = @_;
|
|
local $conf = &get_config();
|
|
local ($c, $v);
|
|
if (!$name) {
|
|
# Whole config
|
|
$c = $conf;
|
|
$v = undef;
|
|
}
|
|
elsif ($name =~ /^\d+$/) {
|
|
# By index
|
|
$c = $conf->[$name]->{'members'};
|
|
$v = $conf->[$name];
|
|
}
|
|
else {
|
|
# Find by name, in servername:port format
|
|
my ($sn, $sp) = split(/:/, $name);
|
|
VHOST: foreach my $virt (&find_directive_struct("VirtualHost", $conf)) {
|
|
local $vp = $virt->{'words'}->[0] =~ /:(\d+)$/ ? $1 : 80;
|
|
next if ($vp != $sp);
|
|
local $vn = &find_directive("ServerName", $virt->{'members'});
|
|
if (lc($vn) eq lc($sn) || lc($vn) eq lc("www.".$sn)) {
|
|
$c = $virt->{'members'};
|
|
$v = $virt;
|
|
last VHOST;
|
|
}
|
|
foreach my $n (&find_directive_struct("ServerAlias",
|
|
$virt->{'members'})) {
|
|
local @lcw = map { lc($_) } @{$n->{'words'}};
|
|
if (&indexof($sn, @lcw) >= 0 ||
|
|
&indexof("www.".$sn, @lcw) >= 0) {
|
|
$c = $virt->{'members'};
|
|
$v = $virt;
|
|
last VHOST;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return wantarray ? ($c, $v) : $c;
|
|
}
|
|
|
|
# get_htaccess_config(file)
|
|
# Parses a .htaccess format file into a list of config objects
|
|
sub get_htaccess_config
|
|
{
|
|
my ($file) = @_;
|
|
my ($lnum, @conf);
|
|
&open_readfile(HTACCESS, $file);
|
|
@conf = &parse_config_file(HTACCESS, $lnum, $file);
|
|
foreach my $c (@conf) {
|
|
$c->{'htaccess'} = 1;
|
|
}
|
|
close(HTACCESS);
|
|
return \@conf;
|
|
}
|
|
|
|
# save_directive(name, &values, &parent-directives, &config, [always-at-end])
|
|
# Updates the config file(s) and the directives structure with new values
|
|
# for the given directives.
|
|
# If a directive's value is merely being changed, then its value only needs
|
|
# to be updated in the directives array and in the file.
|
|
sub save_directive
|
|
{
|
|
local($i, @old, $lref, $change, $len, $v);
|
|
@old = &find_directive_struct($_[0], $_[2]);
|
|
local @files;
|
|
for($i=0; $i<@old || $i<@{$_[1]}; $i++) {
|
|
$v = $i<@{$_[1]} ? $_[1]->[$i] : undef;
|
|
if ($i >= @old) {
|
|
# a new directive is being added. If other directives of this
|
|
# type exist, add it after them. Otherwise, put it at the end of
|
|
# the first file in the section
|
|
if ($change && !$_[4]) {
|
|
# Have changed some old directive.. add this new one
|
|
# after it, and update change
|
|
local(%v, $j);
|
|
%v = ( "line", $change->{'line'}+1,
|
|
"eline", $change->{'line'}+1,
|
|
"file", $change->{'file'},
|
|
"indent", $change->{'indent'},
|
|
"type", 0,
|
|
"name", $_[0],
|
|
"value", $v,
|
|
"words", &wsplit($v) );
|
|
$j = &indexof($change, @{$_[2]})+1;
|
|
&renumber($_[3], $v{'line'}, $v{'file'}, 1);
|
|
splice(@{$_[2]}, $j, 0, \%v);
|
|
$lref = &read_file_lines($v{'file'});
|
|
push(@files, $v{'file'});
|
|
splice(@$lref, $v{'line'}, 0,
|
|
(" " x $v{'indent'})."$_[0] $v");
|
|
$change = \%v;
|
|
}
|
|
else {
|
|
# Adding a new directive to the end of the list
|
|
# in this section
|
|
local($f, %v, $j);
|
|
$f = $_[2]->[0]->{'file'};
|
|
for($j=0; $j < @{$_[2]} &&
|
|
$_[2]->[$j]->{'file'} eq $f; $j++) { }
|
|
$lref = &read_file_lines($f);
|
|
if ($_[2] eq $_[3]) {
|
|
# Top-level, so add to the end of the file
|
|
$l = scalar(@$lref) + 1;
|
|
}
|
|
else {
|
|
# Add after last directive in the same section
|
|
$l = $_[2]->[$j-1]->{'eline'}+1;
|
|
}
|
|
%v = ( "line", $l,
|
|
"eline", $l,
|
|
"file", $f,
|
|
"indent", $_[2]->[$j-1]->{'indent'},
|
|
"type", 0,
|
|
"name", $_[0],
|
|
"value", $v,
|
|
"words", &wsplit($v) );
|
|
&renumber($_[3], $l, $f, 1);
|
|
splice(@{$_[2]}, $j, 0, \%v);
|
|
push(@files, $f);
|
|
splice(@$lref, $l, 0,
|
|
(" " x $v{'indent'})."$_[0] $v");
|
|
}
|
|
}
|
|
elsif ($i >= @{$_[1]}) {
|
|
# a directive was deleted
|
|
$lref = &read_file_lines($old[$i]->{'file'});
|
|
push(@files, $old[$i]->{'file'});
|
|
$idx = &indexof($old[$i], @{$_[2]});
|
|
splice(@{$_[2]}, $idx, 1);
|
|
$len = $old[$i]->{'eline'} - $old[$i]->{'line'} + 1;
|
|
splice(@$lref, $old[$i]->{'line'}, $len);
|
|
&renumber($_[3], $old[$i]->{'line'}, $old[$i]->{'file'}, -$len);
|
|
}
|
|
else {
|
|
# just changing the value
|
|
$lref = &read_file_lines($old[$i]->{'file'});
|
|
push(@files, $old[$i]->{'file'});
|
|
$len = $old[$i]->{'eline'} - $old[$i]->{'line'} + 1;
|
|
&renumber($_[3], $old[$i]->{'eline'}+1,
|
|
$old[$i]->{'file'},1-$len);
|
|
$old[$i]->{'value'} = $v;
|
|
$old[$i]->{'words'} = &wsplit($v);
|
|
$old[$i]->{'eline'} = $old[$i]->{'line'};
|
|
splice(@$lref, $old[$i]->{'line'}, $len,
|
|
(" " x $old[$i]->{'indent'}).$_[0]." ".$v);
|
|
$change = $old[$i];
|
|
}
|
|
}
|
|
if (!$_[3]->[0]->{'htaccess'}) {
|
|
&update_last_config_change();
|
|
}
|
|
@files = &unique(@files);
|
|
push(@{$saved_conf_files}, @files);
|
|
return @files;
|
|
}
|
|
|
|
# save_directive_struct(&old-directive, &directive, &parent-directives,
|
|
# &config, [firstline-only])
|
|
# Updates, creates or removes only multi-line directive like a <virtualhost>
|
|
sub save_directive_struct
|
|
{
|
|
local ($olddir, $newdir, $pconf, $conf, $first) = @_;
|
|
return if (!$olddir && !$newdir); # Nothing to do
|
|
local $file = $olddir ? $olddir->{'file'} :
|
|
$newdir->{'file'} ? $newdir->{'file'} : $pconf->[0]->{'file'};
|
|
local $lref = &read_file_lines($file);
|
|
local $oldlen = $olddir ? $olddir->{'eline'}-$olddir->{'line'}+1 : undef;
|
|
local @newlines;
|
|
if ($newdir) {
|
|
my $isrc = $olddir ? $olddir :
|
|
@$pconf ? $pconf->[0] : undef;
|
|
if ($isrc) {
|
|
&recursive_set_indent($newdir, $isrc->{'indent'});
|
|
}
|
|
$newdir{'words'} = &wsplit($newdir{'value'});
|
|
@newlines = &directive_lines($newdir);
|
|
}
|
|
if ($olddir && $newdir) {
|
|
# Update in place
|
|
$newdir->{'words'} = &wsplit($newdir->{'value'});
|
|
if ($first) {
|
|
# Just changing first and last line, like virtualhost IP
|
|
$lref->[$olddir->{'line'}] = $newlines[0];
|
|
$lref->[$olddir->{'eline'}] = $newlines[$#newlines];
|
|
$olddir->{'name'} = $newdir->{'name'};
|
|
$olddir->{'value'} = $newdir->{'value'};
|
|
$olddir->{'words'} = $newdir->{'words'};
|
|
}
|
|
else {
|
|
# Re-writing whole block
|
|
&renumber($conf, $olddir->{'eline'}+1, $file,
|
|
scalar(@newlines)-$oldlen);
|
|
local $idx = &indexof($olddir, @$pconf);
|
|
$pconf->[$idx] = $newdir if ($idx >= 0);
|
|
$newdir->{'file'} = $olddir->{'file'};
|
|
$newdir->{'line'} = $olddir->{'line'};
|
|
$newdir->{'eline'} = $olddir->{'line'}+scalar(@newlines)-1;
|
|
splice(@$lref, $olddir->{'line'}, $oldlen, @newlines);
|
|
|
|
# Update sub-directive lines and files too
|
|
if ($newdir->{'type'}) {
|
|
&recursive_set_lines_files($newdir->{'members'},
|
|
$newdir->{'line'}+1,
|
|
$newdir->{'file'});
|
|
}
|
|
}
|
|
}
|
|
elsif ($olddir && !$newdir) {
|
|
# Remove
|
|
splice(@$lref, $olddir->{'line'}, $oldlen);
|
|
local $idx = &indexof($olddir, @$pconf);
|
|
splice(@$pconf, $idx, 1) if ($idx >= 0);
|
|
&renumber($conf, $olddir->{'line'}, $olddir->{'file'}, -$oldlen);
|
|
}
|
|
elsif (!$olddir && $newdir) {
|
|
# Add to file, at end of specific file or parent section
|
|
local ($addline, $addpos);
|
|
if ($newdir->{'file'}) {
|
|
$addline = scalar(@$lref);
|
|
$addpos = scalar(@$pconf);
|
|
}
|
|
else {
|
|
for($addpos=0; $addpos < scalar(@$pconf) &&
|
|
$pconf->[$addpos]->{'file'} eq $file;
|
|
$addpos++) {
|
|
# Find last parent directive in same file
|
|
}
|
|
$addpos--;
|
|
$addline = $pconf->[$addpos]->{'eline'}+1;
|
|
}
|
|
$newdir->{'words'} = &wsplit($newdir->{'value'});
|
|
$newdir->{'file'} = $file;
|
|
$newdir->{'line'} = $addline;
|
|
$newdir->{'eline'} = $addline + scalar(@newlines) - 1;
|
|
&renumber($conf, $addline, $file, scalar(@newlines));
|
|
splice(@$pconf, $addpos, 0, $newdir);
|
|
splice(@$lref, $addline, 0, @newlines);
|
|
|
|
# Update sub-directive lines and files too
|
|
if ($newdir->{'type'}) {
|
|
&recursive_set_lines_files($newdir->{'members'},
|
|
$newdir->{'line'}+1,
|
|
$newdir->{'file'});
|
|
}
|
|
}
|
|
if (!$conf->[0]->{'htaccess'}) {
|
|
&update_last_config_change();
|
|
}
|
|
}
|
|
|
|
# recursive_set_indent(&directive, indent)
|
|
# Set the indent to match another at the same level
|
|
sub recursive_set_indent
|
|
{
|
|
my ($dir, $indent) = @_;
|
|
$dir->{'indent'} = $indent if (!defined($dir->{'indent'}));
|
|
if ($dir->{'type'}) {
|
|
foreach my $m (@{$dir->{'members'}}) {
|
|
&recursive_set_indent($m, $indent+4);
|
|
}
|
|
}
|
|
}
|
|
|
|
# recursive_set_lines_files(&directives, first-line, file)
|
|
# Update the line numbers and filenames in a list of directives
|
|
sub recursive_set_lines_files
|
|
{
|
|
my ($dirs, $line, $file) = @_;
|
|
foreach my $dir (@$dirs) {
|
|
$dir->{'line'} = $line;
|
|
$dir->{'file'} = $file;
|
|
if ($dir->{'type'}) {
|
|
# Continue after every line used by nested members
|
|
$line = &recursive_set_lines_files($dir->{'members'},
|
|
$line+1, $file);
|
|
}
|
|
$dir->{'eline'} = $line;
|
|
$line++ if ($dir->{'name'} ne 'dummy' || defined($dir->{'comment'}));
|
|
}
|
|
return $line;
|
|
}
|
|
|
|
# delete_file_if_empty(file)
|
|
# If a virtual host file is now empty, delete it (and any link to it)
|
|
sub delete_file_if_empty
|
|
{
|
|
local ($file) = @_;
|
|
local $lref = &read_file_lines($file, 1);
|
|
foreach my $l (@$lref) {
|
|
return 0 if ($l =~ /\S/);
|
|
}
|
|
&unflush_file_lines($file);
|
|
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($_) &&
|
|
!&is_default_vhost($_) }
|
|
&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.
|
|
sub renumber
|
|
{
|
|
my ($conf, $line, $file, $offset) = @_;
|
|
return if (!$offset);
|
|
foreach my $d (@$conf) {
|
|
if ($d->{'file'} eq $file && $d->{'line'} >= $_[1]) {
|
|
$d->{'line'} += $offset;
|
|
}
|
|
if ($d->{'file'} eq $file && $d->{'eline'} >= $_[1]) {
|
|
$d->{'eline'} += $offset;
|
|
}
|
|
if ($d->{'type'}) {
|
|
&renumber($d->{'members'}, $line, $file, $offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
# server_root(path)
|
|
# Convert a relative path to being under the server root
|
|
sub server_root
|
|
{
|
|
my ($path) = @_;
|
|
if (!$path) {
|
|
return undef;
|
|
}
|
|
elsif ($path =~ /^\//) {
|
|
return $path;
|
|
}
|
|
else {
|
|
return "$config{'httpd_dir'}/$path";
|
|
}
|
|
}
|
|
|
|
sub dump_config
|
|
{
|
|
local($c, $mref);
|
|
print "<table border>\n";
|
|
print "<tr> <td>Name</td> <td>Value</td> <td>File</td> <td>Line</td> </tr>\n";
|
|
foreach $c (@_) {
|
|
printf "<tr> <td>%s</td> <td>%s</td><td>%s</td><td>%s</td> </tr>\n",
|
|
$c->{'name'}, $c->{'value'}, $c->{'file'}, $c->{'line'};
|
|
if ($c->{'type'}) {
|
|
print "<tr> <td colspan=4>\n";
|
|
$mref = $c->{'members'};
|
|
&dump_config(@$mref);
|
|
print "</td> </tr>\n";
|
|
}
|
|
}
|
|
print "</table>\n";
|
|
}
|
|
|
|
sub def
|
|
{
|
|
return $_[0] ? $_[0] : $_[1];
|
|
}
|
|
|
|
# make_directives(ref, version, module)
|
|
# Return directives suitable for this system and version
|
|
sub make_directives
|
|
{
|
|
my ($aref, $ver, $mod) = @_;
|
|
my @rv;
|
|
if ($ver =~ /^(1)\.(3)(\d+)$/) {
|
|
$ver = sprintf "%d.%d%2.2d", $1, $2, $3;
|
|
}
|
|
foreach my $d (@$aref) {
|
|
local(%dir);
|
|
$dir{'name'} = $d->[0];
|
|
$dir{'multiple'} = $d->[1];
|
|
$dir{'type'} = int($d->[2]);
|
|
$dir{'subtype'} = $d->[2] - $dir{'type'};
|
|
$dir{'module'} = $mod;
|
|
$dir{'version'} = $ver;
|
|
$dir{'priority'} = $d->[5];
|
|
foreach $c (split(/\s+/, $d->[3])) { $dir{$c}++; }
|
|
if (!$d->[4]) { push(@rv, \%dir); }
|
|
elsif ($d->[4] =~ /^-([\d\.]+)$/ && $ver < $1) { push(@rv, \%dir); }
|
|
elsif ($d->[4] =~ /^([\d\.]+)$/ && $ver >= $1) { push(@rv, \%dir); }
|
|
elsif ($d->[4] =~ /^([\d\.]+)-([\d\.]+)$/ && $ver >= $1 && $ver < $2)
|
|
{ push(@rv, \%dir); }
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
|
|
# editable_directives(type, context)
|
|
# Returns an array of references to associative arrays, one for each
|
|
# directive of the given type that can be used in the given context
|
|
sub editable_directives
|
|
{
|
|
local($m, $func, @rv, %done);
|
|
foreach $m (keys %httpd_modules) {
|
|
$func = $m."_directives";
|
|
if (defined(&$func)) {
|
|
push(@rv, &$func($httpd_modules{$m}));
|
|
}
|
|
}
|
|
@rv = grep { $_->{'type'} == $_[0] && $_->{$_[1]} &&
|
|
!$done{$_->{'name'}}++ } @rv;
|
|
@rv = grep { &can_edit_directive($_->{'name'}) } @rv;
|
|
@rv = sort { local $td = $a->{'subtype'} <=> $b->{'subtype'};
|
|
local $pd = $b->{'priority'} <=> $a->{'priority'};
|
|
local $md = $a->{'module'} cmp $b->{'module'};
|
|
$td ? $td : $pd ? $pd : $md ? $md : $a->{'name'} cmp $b->{'name'} }
|
|
@rv;
|
|
return @rv;
|
|
}
|
|
|
|
# can_edit_directive(name)
|
|
# Returns 1 if the Apache directive named can be edited by the current user
|
|
sub can_edit_directive
|
|
{
|
|
local ($name) = @_;
|
|
if ($access{'dirsmode'} == 0) {
|
|
return 1;
|
|
}
|
|
else {
|
|
local %dirs = map { lc($_), 1 } split(/\s+/, $access{'dirs'});
|
|
if ($access{'dirsmode'} == 1) {
|
|
return $dirs{lc($name)};
|
|
}
|
|
else {
|
|
return !$dirs{lc($name)};
|
|
}
|
|
}
|
|
}
|
|
|
|
# generate_inputs(&editors, &directives, [&skip])
|
|
# Displays a 2-column list of options, for use inside a table
|
|
sub generate_inputs
|
|
{
|
|
local($e, $sw, @args, @rv, $func, $lastsub);
|
|
foreach $e (@{$_[0]}) {
|
|
if (defined($lastsub) && $lastsub != $e->{'subtype'}) {
|
|
print &ui_table_hr();
|
|
}
|
|
$lastsub = $e->{'subtype'};
|
|
|
|
# Build arg list for the editing function. Each arg can be a single
|
|
# directive struct, or a reference to an array of structures.
|
|
$func = "edit";
|
|
undef(@args);
|
|
foreach $ed (split(/\s+/, $e->{'name'})) {
|
|
local(@vals);
|
|
$func .= "_$ed";
|
|
@vals = &find_directive_struct($ed, $_[1]);
|
|
if ($e->{'multiple'}) { push(@args, \@vals); }
|
|
elsif (!@vals) { push(@args, undef); }
|
|
else { push(@args, $vals[$#vals]); }
|
|
}
|
|
push(@args, $e);
|
|
|
|
# call the function
|
|
@rv = &$func(@args);
|
|
local $names;
|
|
if ($config{'show_names'} || $userconfig{'show_names'}) {
|
|
$names = " (";
|
|
foreach $ed (split(/\s+/, $e->{'name'})) {
|
|
# nodo50 v0.1 - Change 000004 - Open new window for Help in Apache module and mod_apachessl Help from http://www.apache-ssl.org and
|
|
# nodo50 v0.1 - Change 000004 - Abre nueva ventana para Ayuda del módulo Apache y para mod_apachessl busca la Ayuda en http://www.apache-ssl.org and
|
|
$names .= "<tt>".&ui_link( ($e->{'module'} eq 'mod_apachessl' ? 'http://www.apache-ssl.org/docs.html#'.$ed : $apache_docbase."/".$e->{'module'}.".html#".lc($ed)), $ed )."</tt> ";
|
|
#$names .= "<tt><a href='".$apache_docbase."/".$e->{'module'}.".html#".lc($ed)."'>".$ed."</a></tt> ";
|
|
# nodo50 v0.1 - Change 000004 - End
|
|
}
|
|
$names .= ")";
|
|
}
|
|
if ($rv[0] >= 2) {
|
|
# spans 2 columns..
|
|
if ($rv[0] == 3) {
|
|
# Takes up whole row
|
|
print &ui_table_row(undef, $rv[2], 4);
|
|
}
|
|
else {
|
|
# Has title on left
|
|
print &ui_table_row($rv[1], $rv[2], 3);
|
|
}
|
|
}
|
|
else {
|
|
# only spans one column
|
|
print &ui_table_row($rv[1], $rv[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
# parse_inputs(&editors, &directives, &config)
|
|
# Reads user choices from a form and update the directives and config files.
|
|
sub parse_inputs
|
|
{
|
|
# First call editor functions to get new values. Each function returns
|
|
# an array of references to arrays containing the new values for the directive.
|
|
&before_changing();
|
|
&lock_apache_files();
|
|
foreach $e (@{$_[0]}) {
|
|
@dirs = split(/\s+/, $e->{'name'});
|
|
$func = "save_".join('_', @dirs);
|
|
@rv = &$func($e);
|
|
for($i=0; $i<@dirs; $i++) {
|
|
push(@chname, $dirs[$i]);
|
|
push(@chval, $rv[$i]);
|
|
}
|
|
}
|
|
|
|
# Assuming everything went OK, update the configuration
|
|
for($i=0; $i<@chname; $i++) {
|
|
&save_directive($chname[$i], $chval[$i], $_[1], $_[2]);
|
|
}
|
|
&flush_file_lines();
|
|
&unlock_apache_files();
|
|
&after_changing();
|
|
&format_modifed_config_files();
|
|
}
|
|
|
|
# opt_input(value, name, default, size)
|
|
sub opt_input
|
|
{
|
|
return &ui_opt_textbox($_[1], $_[0], $_[3], $_[2]);
|
|
}
|
|
|
|
# parse_opt(name, regexp, error, [noquotes])
|
|
sub parse_opt
|
|
{
|
|
local($i, $re);
|
|
local $v = $in{$_[0]};
|
|
$v =~ /\r|\n|\0/ && &error($text{'enewline'});
|
|
if ($in{"$_[0]_def"}) { return ( [ ] ); }
|
|
for($i=1; $i<@_; $i+=2) {
|
|
$re = $_[$i];
|
|
if ($v !~ /$re/) { &error($_[$i+1]); }
|
|
}
|
|
return ( [ $v =~ /\s/ && !$_[3] ? "\"$v\"" : $v ] );
|
|
}
|
|
|
|
# choice_input(value, name, default, [choice]+)
|
|
# Each choice is a display,value pair
|
|
sub choice_input
|
|
{
|
|
my($i, $rv);
|
|
for($i=3; $i<@_; $i++) {
|
|
$_[$i] =~ /^([^,]*),(.*)$/;
|
|
$rv .= &ui_oneradio($_[1], $2, $1, lc($2) eq lc($_[0]) ||
|
|
!defined($_[0]) && lc($2) eq lc($_[2]))."\n";
|
|
}
|
|
return $rv;
|
|
}
|
|
|
|
# choice_input_vert(value, name, default, [choice]+)
|
|
# Each choice is a display,value pair
|
|
sub choice_input_vert
|
|
{
|
|
my($i, $rv);
|
|
for($i=3; $i<@_; $i++) {
|
|
$_[$i] =~ /^([^,]*),(.*)$/;
|
|
$rv .= &ui_oneradio($_[1], $2, $1, lc($2) eq lc($_[0]) ||
|
|
!defined($_[0]) && lc($2) eq lc($_[2]))."<br>\n";
|
|
}
|
|
return $rv;
|
|
}
|
|
|
|
# parse_choice(name, default)
|
|
sub parse_choice
|
|
{
|
|
if (lc($in{$_[0]}) eq lc($_[1])) {
|
|
return ( [ ] );
|
|
}
|
|
else {
|
|
$in{$_[0]} =~ /\r|\n|\0/ && &error($text{'enewline'});
|
|
return ( [ $in{$_[0]} ] );
|
|
}
|
|
}
|
|
|
|
# select_input(value, name, default, [choice]+)
|
|
sub select_input
|
|
{
|
|
my($i, @sel);
|
|
my $selv;
|
|
for($i=3; $i<@_; $i++) {
|
|
$_[$i] =~ /^([^,]*),(.*)$/;
|
|
if (lc($2) eq lc($_[0]) || !defined($_[0]) && lc($2) eq lc($_[2])) {
|
|
$selv = $2;
|
|
}
|
|
push(@sel, [ $2, $1 || " " ]);
|
|
}
|
|
return &ui_select($_[1], $selv, \@sel, 1);
|
|
}
|
|
|
|
# parse_choice(name, default)
|
|
sub parse_select
|
|
{
|
|
return &parse_choice(@_);
|
|
}
|
|
|
|
# handler_input(value, name)
|
|
sub handler_input
|
|
{
|
|
my($m, $func, @hl, @sel, $h);
|
|
my $conf = &get_config();
|
|
push(@hl, "");
|
|
foreach $m (keys %httpd_modules) {
|
|
$func = $m."_handlers";
|
|
if (defined(&$func)) {
|
|
push(@hl, &$func($conf, $httpd_modules{$m}));
|
|
}
|
|
}
|
|
if (&indexof($_[0], @hl) < 0) { push(@hl, $_[0]); }
|
|
foreach $h (&unique(@hl)) {
|
|
push(@sel, [$h, $h, ($h eq $_[0] ? "selected" : "")] );
|
|
}
|
|
push(@sel, ["None", "<".$text{'core_none'}.">", ($_[0] eq "None" ? "selected" : "")] );
|
|
return &ui_select($_[1], undef, \@sel, 1);
|
|
}
|
|
|
|
# parse_handler(name)
|
|
sub parse_handler
|
|
{
|
|
if ($in{$_[0]} eq "") {
|
|
return ( [ ] );
|
|
}
|
|
else {
|
|
$in{$_[0]} =~ /\r|\n|\0/ && &error($text{'enewline'});
|
|
return ( [ $in{$_[0]} ] );
|
|
}
|
|
}
|
|
|
|
# filters_input(&values, name)
|
|
sub filters_input
|
|
{
|
|
local($m, $func, @fl, $rv, $f);
|
|
local $conf = &get_config();
|
|
foreach $m (keys %httpd_modules) {
|
|
$func = $m."_filters";
|
|
if (defined(&$func)) {
|
|
push(@fl, &$func($conf, $httpd_modules{$m}));
|
|
}
|
|
}
|
|
foreach $f (@{$_[0]}) {
|
|
push(@fl, $f) if (&indexof($f, @fl) < 0);
|
|
}
|
|
foreach $f (&unique(@fl)) {
|
|
$rv .= &ui_checkbox($_[1], $f, $f, (&indexof($f, @{$_[0]}) < 0 ? 0 : 1 ) );
|
|
}
|
|
$rv ||= &ui_textbox($_[1], "", 20);
|
|
return $rv;
|
|
}
|
|
|
|
# parse_filters(name)
|
|
sub parse_filters
|
|
{
|
|
$in{$_[0]} =~ /\r|\n|\0/ && &error($text{'enewline'});
|
|
local @f = split(/\0/, $in{$_[0]});
|
|
return @f ? ( [ join(";", @f) ] ) : ( [ ] );
|
|
}
|
|
|
|
|
|
|
|
# virtual_name(struct, [forlog])
|
|
sub virtual_name
|
|
{
|
|
if ($_[0]) {
|
|
local $n = &find_directive("ServerName", $_[0]->{'members'});
|
|
if ($n) {
|
|
return &html_escape($_[0]->{'value'} =~ /:(\d+)$/ ? "$n:$1"
|
|
: $n);
|
|
}
|
|
else {
|
|
return &html_escape(
|
|
$_[0]->{'value'} =~ /^\[(\S+)\]$/ ? $1 :
|
|
$_[0]->{'value'} =~ /^\[(\S+)\]:(\d+)$/ ? "$1:$2" :
|
|
$_[0]->{'value'});
|
|
}
|
|
}
|
|
else { return $_[1] ? "*" : $text{'default_serv'}; }
|
|
}
|
|
|
|
# dir_name(struct)
|
|
# Given a <directory> or similar structure, return a human-readable description
|
|
sub dir_name
|
|
{
|
|
$_[0]->{'name'} =~ /^(Directory|Files|Location|Proxy)(Match)?$/;
|
|
my ($dfm, $mat) = ($1, $2);
|
|
if ($dfm eq "Proxy" && !$mat && $_[0]->{'words'}->[0] eq "*") {
|
|
# Proxy for all
|
|
return $text{'dir_proxyall'};
|
|
}
|
|
elsif ($mat) {
|
|
# Match-type directive
|
|
return "$dfm regexp <tt>".&html_escape($_[0]->{'words'}->[0])."</tt>";
|
|
}
|
|
elsif ($_[0]->{'words'}->[0] eq "~") {
|
|
# Regular expression
|
|
return "$dfm regexp <tt>".&html_escape($_[0]->{'words'}->[1])."</tt>";
|
|
}
|
|
else {
|
|
# Exact match
|
|
return "$dfm <tt>".&html_escape($_[0]->{'words'}->[0])."</tt>";
|
|
}
|
|
}
|
|
|
|
# list_user_file(file, &user, &pass)
|
|
sub list_user_file
|
|
{
|
|
local($_);
|
|
&open_readfile(USERS, $_[0]);
|
|
while(<USERS>) {
|
|
/^(\S+):(\S+)/;
|
|
push(@{$_[1]}, $1); $_[2]->{$1} = $2;
|
|
}
|
|
close(USERS);
|
|
}
|
|
|
|
|
|
# config_icons(context, program)
|
|
# Displays up to 18 icons, one for each type of configuration directive, for
|
|
# some context (global, virtual, directory or htaccess)
|
|
sub config_icons
|
|
{
|
|
local ($ctx, $prog) = @_;
|
|
local($m, $func, $e, %etype, $i, $c);
|
|
foreach $m (sort { $a cmp $b } (keys %httpd_modules)) {
|
|
$func = $m."_directives";
|
|
if (defined(&$func)) {
|
|
foreach $e (&$func($httpd_modules{$m})) {
|
|
if ($e->{$ctx}) { $etype{$e->{'type'}}++; }
|
|
}
|
|
}
|
|
}
|
|
local (@titles, @links, @icons);
|
|
for($i=0; $text{"type_$i"}; $i++) {
|
|
if ($etype{$i} && $access_types{$i}) {
|
|
push(@links, $prog."type=$i");
|
|
push(@titles, $text{"type_$i"});
|
|
push(@icons, "images/type_icon_$i.gif");
|
|
}
|
|
}
|
|
for($i=2; $i<@_; $i++) {
|
|
if ($_[$i]) {
|
|
push(@links, $_[$i]->{'link'});
|
|
push(@titles, $_[$i]->{'name'});
|
|
push(@icons, $_[$i]->{'icon'});
|
|
}
|
|
}
|
|
&icons_table(\@links, \@titles, \@icons, 5);
|
|
print "<p>\n";
|
|
}
|
|
|
|
# restart_button()
|
|
# Returns HTML for a link to put in the top-right corner of every page
|
|
sub restart_button
|
|
{
|
|
local $args = "redir=".&urlize(&this_url());
|
|
local @rv;
|
|
if (&is_apache_running()) {
|
|
if ($access{'stop'}) {
|
|
push(@rv, &ui_link("stop.cgi?$args", $text{'apache_stop'}) );
|
|
}
|
|
if ($access{'apply'}) {
|
|
my $n = &needs_config_restart();
|
|
if ($n) {
|
|
push(@rv, &ui_link("restart.cgi?$args&newconfig=1",
|
|
"<b>$text{'apache_apply'}</b>") );
|
|
}
|
|
else {
|
|
push(@rv, &ui_link("restart.cgi?$args", $text{'apache_apply'}) );
|
|
}
|
|
}
|
|
}
|
|
elsif ($access{'stop'}) {
|
|
push(@rv, &ui_link("start.cgi?$args", $text{'apache_start'}) );
|
|
}
|
|
return join("<br>\n", @rv);
|
|
}
|
|
|
|
# this_url()
|
|
# Returns the URL in the apache directory of the current script
|
|
sub this_url
|
|
{
|
|
local($url);
|
|
$url = $ENV{'SCRIPT_NAME'};
|
|
if ($ENV{'QUERY_STRING'} ne "") { $url .= "?$ENV{'QUERY_STRING'}"; }
|
|
return $url;
|
|
}
|
|
|
|
# find_all_directives(config, name)
|
|
# Recursively finds all directives of some type
|
|
sub find_all_directives
|
|
{
|
|
local(@rv, $d);
|
|
foreach $d (@{$_[0]}) {
|
|
if ($d->{'name'} eq $_[1]) { push(@rv, $d); }
|
|
if ($d->{'type'} == 1) {
|
|
push(@rv, &find_all_directives($d->{'members'}, $_[1]));
|
|
}
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# httpd_info(executable)
|
|
# Returns the httpd version and modules array
|
|
sub httpd_info
|
|
{
|
|
local ($cmd) = @_;
|
|
$cmd = &has_command($cmd);
|
|
local @st = stat($cmd);
|
|
local %cache;
|
|
&read_file_cached($httpd_info_cache, \%cache);
|
|
if ($cache{'cmd'} eq $cmd && $cache{'time'} == $st[9]) {
|
|
# Cache looks up to date
|
|
return ($cache{'version'}, [ split(/\s+/, $cache{'mods'}) ],
|
|
$cache{'fullversion'});
|
|
}
|
|
local(@mods, $verstr, $ver, $minor, $fullver);
|
|
$verstr = &backquote_command(quotemeta($cmd)." -v 2>&1");
|
|
if ($config{'httpd_version'} =~ /(\d+)\.([\d\.]+)/) {
|
|
$fullver = $config{'httpd_version'};
|
|
$ver = $1;
|
|
$minor = $2;
|
|
$minor =~ s/\.//g;
|
|
$ver .= ".$minor";
|
|
}
|
|
elsif ($verstr =~ /Apache(\S*)\/(\d+)\.([\d\.]+)/) {
|
|
# standard apache
|
|
$fullver = "$2.$3";
|
|
$ver = $2;
|
|
$minor = $3;
|
|
$minor =~ s/\.//g;
|
|
$ver .= ".$minor";
|
|
}
|
|
elsif ($verstr =~ /HP\s*Apache-based\s*Web\s*Server(\S*)\/(\d+)\.([\d\.]+)/) {
|
|
# HP's apache
|
|
$fullver = "$2.$3";
|
|
$ver = $2;
|
|
$minor = $3;
|
|
$minor =~ s/\.//g;
|
|
$ver .= ".$minor";
|
|
}
|
|
elsif ($verstr =~ /Red\s*Hat\s+Secure\/2\.0/i) {
|
|
# redhat secure server 2.0
|
|
$fullver = $ver = 1.31;
|
|
}
|
|
elsif ($verstr =~ /Red\s*Hat\s+Secure\/3\.0/i) {
|
|
# redhat secure server 3.0
|
|
$fullver = $ver = 1.39;
|
|
}
|
|
elsif (&has_command("rpm") &&
|
|
&backquote_command("rpm -q apache 2>&1") =~ /^apache-(\d+)\.([\d\.]+)/) {
|
|
# got version from the RPM
|
|
$fullver = $ver = $1;
|
|
$minor = $2;
|
|
$minor =~ s/\.//g;
|
|
$ver .= ".$minor";
|
|
}
|
|
else {
|
|
# couldn't get version
|
|
return (0, undef);
|
|
}
|
|
if ($ver < 1.2) {
|
|
# apache 1.1 has no -l option! Use the standard list
|
|
@mods = ("core", "mod_mime", "mod_access", "mod_auth", "mod_include",
|
|
"mod_negotiation", "mod_dir", "mod_cgi", "mod_userdir",
|
|
"mod_alias", "mod_env", "mod_log_common");
|
|
}
|
|
else {
|
|
# ask apache for the module list
|
|
@mods = ("core");
|
|
&open_execute_command(APACHE, "\"$_[0]\" -l 2>/dev/null", 1);
|
|
while(<APACHE>) {
|
|
if (/(\S+)\.c/) { push(@mods, $1); }
|
|
}
|
|
close(APACHE);
|
|
if ($?) {
|
|
# httpd crashed! Use last known good set of modules
|
|
local %oldsite;
|
|
&read_file($site_file, \%oldsite);
|
|
if ($oldsite{'modules'}) {
|
|
@mods = split(/\s+/, $oldsite{'modules'});
|
|
}
|
|
}
|
|
@mods = &unique(@mods);
|
|
}
|
|
$cache{'cmd'} = $cmd;
|
|
$cache{'time'} = $st[9];
|
|
$cache{'version'} = $ver;
|
|
$cache{'fullversion'} = $fullver;
|
|
$cache{'mods'} = join(" ", @mods);
|
|
&write_file($httpd_info_cache, \%cache);
|
|
return ($ver, \@mods, $fullver);
|
|
}
|
|
|
|
# print_line(directive, text, indent, link)
|
|
sub print_line
|
|
{
|
|
local $line = $_[0]->{'line'} + 1;
|
|
local $lstr = "$_[0]->{'file'} ($line)";
|
|
local $txt = join("", @{$_[1]});
|
|
local $left = 85 - length($lstr) - $_[2];
|
|
if (length($txt) > $left) {
|
|
$txt = substr($txt, 0, $left)." ..";
|
|
}
|
|
local $txtlen = length($txt);
|
|
$txt = &html_escape($txt);
|
|
print " " x $_[2];
|
|
if ($_[3]) {
|
|
print &ui_link($_[3], $txt);
|
|
}
|
|
else { print $txt; }
|
|
print " " x (90 - $txtlen - $_[2] - length($lstr));
|
|
print $lstr,"\n";
|
|
}
|
|
|
|
# can_edit_virt(struct)
|
|
sub can_edit_virt
|
|
{
|
|
return 1 if ($access{'virts'} eq '*');
|
|
local %vcan = map { $_, 1 } split(/\s+/, $access{'virts'});
|
|
local ($can) = grep { $vcan{$_} } &virt_acl_name($_[0]);
|
|
return $can ? 1 : 0;
|
|
}
|
|
|
|
# virt_acl_name(struct)
|
|
# Give a virtual host, returns a list of names that could be used in the ACL
|
|
# to refer to it
|
|
sub virt_acl_name
|
|
{
|
|
return ( "__default__" ) if (!$_[0]);
|
|
local $n = &find_directive("ServerName", $_[0]->{'members'});
|
|
local @rv;
|
|
local $p;
|
|
if ($_[0]->{'value'} =~ /(:\d+)/) { $p = $1; }
|
|
if ($n) {
|
|
push(@rv, $n.$p);
|
|
}
|
|
else {
|
|
push(@rv, $_[0]->{'value'});
|
|
}
|
|
foreach $n (&find_directive_struct("ServerAlias", $_[0]->{'members'})) {
|
|
local $a;
|
|
foreach $a (@{$n->{'words'}}) {
|
|
push(@rv, $a.$p);
|
|
}
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# allowed_auth_file(file)
|
|
sub allowed_auth_file
|
|
{
|
|
local $_;
|
|
return 1 if ($access{'dir'} eq '/');
|
|
return 0 if ($_[0] =~ /\.\./);
|
|
local $f = &server_root($_[0]);
|
|
return 0 if (-l $f && !&allowed_auth_file(readlink($f)));
|
|
local $l = length($access{'dir'});
|
|
return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
|
|
}
|
|
|
|
# directory_exists(file)
|
|
# Returns 1 if the directory in some path exists
|
|
sub directory_exists
|
|
{
|
|
local $path = &server_root($_[0]);
|
|
if ($path =~ /^(\S*\/)([^\/]+)$/) {
|
|
return -d $1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# allowed_doc_dir(dir)
|
|
# Returns 1 if some directory is under the allowed root for alias targets
|
|
sub allowed_doc_dir
|
|
{
|
|
return $access{'aliasdir'} eq '/' ||
|
|
$_[0] !~ /^\// || # Relative path, like for <Files>
|
|
&is_under_directory($access{'aliasdir'}, $_[0]);
|
|
}
|
|
|
|
sub lock_apache_files
|
|
{
|
|
local $conf = &get_config();
|
|
local $f;
|
|
@main::locked_apache_files = &unique(map { $_->{'file'} } @$conf);
|
|
foreach $f (@main::locked_apache_files) {
|
|
&lock_file($f);
|
|
}
|
|
}
|
|
|
|
sub unlock_apache_files
|
|
{
|
|
local $conf = &get_config();
|
|
local $f;
|
|
foreach $f (@main::locked_apache_files) {
|
|
&unlock_file($f);
|
|
}
|
|
@main::locked_apache_files = ( );
|
|
}
|
|
|
|
# directive_lines(directive, ...)
|
|
# Convery a list of Apache directives into a list of lines
|
|
sub directive_lines
|
|
{
|
|
my @rv;
|
|
foreach my $d (@_) {
|
|
if ($d->{'name'} eq 'dummy') {
|
|
if (defined($d->{'comment'})) {
|
|
my $indent = (" " x $d->{'indent'});
|
|
push(@rv, $indent.$d->{'comment'});
|
|
}
|
|
next;
|
|
}
|
|
my $indent = (" " x $d->{'indent'});
|
|
if ($d->{'type'}) {
|
|
push(@rv, $indent."<$d->{'name'} $d->{'value'}>");
|
|
push(@rv, &directive_lines(@{$d->{'members'}}));
|
|
push(@rv, $indent."</$d->{'name'}>");
|
|
}
|
|
else {
|
|
push(@rv, $indent."$d->{'name'} $d->{'value'}");
|
|
}
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# test_config()
|
|
# If possible, test the current configuration and return an error message,
|
|
# or undef.
|
|
sub test_config
|
|
{
|
|
if ($httpd_modules{'core'} >= 1.301) {
|
|
# Test the configuration with the available command
|
|
local $cmd;
|
|
if ($config{'test_apachectl'} &&
|
|
-x &translate_filename($config{'apachectl_path'})) {
|
|
# Test with apachectl
|
|
$cmd = "\"$config{'apachectl_path'}\" configtest";
|
|
}
|
|
else {
|
|
# Test with httpd
|
|
local $httpd = &find_httpd();
|
|
$cmd = "\"$httpd\" -d \"$config{'httpd_dir'}\" -t";
|
|
if ($config{'httpd_conf'}) {
|
|
$cmd .= " -f \"$config{'httpd_conf'}\"";
|
|
}
|
|
foreach $d (&get_httpd_defines()) {
|
|
$cmd .= " -D$d";
|
|
}
|
|
}
|
|
local $out = &backquote_command("$cmd 2>&1");
|
|
if ($out && $out !~ /(syntax|Checking).*\s+ok/i) {
|
|
return $out;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# before_changing()
|
|
# If testing all changes, backup the config files so they can be reverted
|
|
# if necessary.
|
|
sub before_changing
|
|
{
|
|
my @extra = grep { $_ } @_;
|
|
if ($config{'test_always'} || $access{'test_always'}) {
|
|
local $conf = &get_config();
|
|
local @files = &unique((map { $_->{'file'} } @$conf), @extra);
|
|
local $/ = undef;
|
|
local $f;
|
|
foreach $f (@files) {
|
|
if (&open_readfile(BEFORE, $f)) {
|
|
$before_changing{$f} = <BEFORE>;
|
|
close(BEFORE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# after_changing()
|
|
# If testing all changes, test now and revert the configs and show an error
|
|
# message if a problem was found.
|
|
sub after_changing
|
|
{
|
|
if ($config{'test_always'} || $access{'test_always'}) {
|
|
local $err = &test_config();
|
|
if ($err) {
|
|
# Something failed .. revert all files
|
|
&rollback_apache_config();
|
|
&error(&text('eafter', "<pre>$err</pre>"));
|
|
}
|
|
}
|
|
}
|
|
|
|
# rollback_apache_config()
|
|
# Copy back all config files from their originals
|
|
sub rollback_apache_config
|
|
{
|
|
local $f;
|
|
foreach $f (keys %before_changing) {
|
|
&open_tempfile(AFTER, ">$f");
|
|
&print_tempfile(AFTER, $before_changing{$f});
|
|
&close_tempfile(AFTER);
|
|
}
|
|
}
|
|
|
|
# find_httpd_conf()
|
|
# Returns the path to the http.conf file, and the last place looked
|
|
# (without any translation).
|
|
sub find_httpd_conf
|
|
{
|
|
local $conf = $config{'httpd_conf'};
|
|
return ( -f &translate_filename($conf) ? $conf : undef, $conf ) if ($conf);
|
|
$conf = "$config{'httpd_dir'}/conf/httpd.conf";
|
|
$conf = "$config{'httpd_dir'}/conf/httpd2.conf"
|
|
if (!-f &translate_filename($conf));
|
|
$conf = "$config{'httpd_dir'}/etc/httpd.conf"
|
|
if (!-f &translate_filename($conf));
|
|
$conf = "$config{'httpd_dir'}/etc/httpd2.conf"
|
|
if (!-f &translate_filename($conf));
|
|
$conf = undef if (!-f &translate_filename($conf));
|
|
return ( $conf, "$config{'httpd_dir'}/conf/httpd.conf" );
|
|
}
|
|
|
|
# find_httpd()
|
|
# Returns the path to the httpd executable, by appending '2' if necessary
|
|
sub find_httpd
|
|
{
|
|
return $config{'httpd_path'}
|
|
if (-x &translate_filename($config{'httpd_path'}) &&
|
|
!-d &translate_filename($config{'httpd_path'}));
|
|
return $config{'httpd_path'}.'2'
|
|
if (-x &translate_filename($config{'httpd_path'}.'2') &&
|
|
!-d &translate_filename($config{'httpd_path'}.'2'));
|
|
return undef;
|
|
}
|
|
|
|
# get_pid_file()
|
|
# Returns the path to the PID file (without any translation)
|
|
sub get_pid_file
|
|
{
|
|
return $config{'pid_file'} if ($config{'pid_file'});
|
|
local $conf = &get_config();
|
|
local $pidfilestr = &find_directive_struct("PidFile", $conf);
|
|
local $pidfile = $pidfilestr ? $pidfilestr->{'words'}->[0]
|
|
: "logs/httpd.pid";
|
|
return &server_root($pidfile);
|
|
}
|
|
|
|
# restart_apache()
|
|
# Re-starts Apache, and returns undef on success or an error message on failure
|
|
sub restart_apache
|
|
{
|
|
local $pidfile = &get_pid_file();
|
|
if ($config{'apply_cmd'} eq 'restart') {
|
|
# Call stop and start functions
|
|
local $err = &stop_apache();
|
|
return $err if ($err);
|
|
local $stopped = &wait_for_apache_stop();
|
|
local $err = &start_apache();
|
|
return $err if ($err);
|
|
}
|
|
elsif ($config{'apply_cmd'}) {
|
|
# Use the configured start command
|
|
&clean_environment();
|
|
local $out = &backquote_logged("$config{'apply_cmd'} 2>&1");
|
|
&reset_environment();
|
|
&wait_for_graceful() if ($config{'apply_cmd'} =~ /graceful/);
|
|
if ($?) {
|
|
return "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
}
|
|
elsif (-x &translate_filename($config{'apachectl_path'})) {
|
|
# Use apachectl to restart
|
|
if ($httpd_modules{'core'} >= 2) {
|
|
# Do a graceful restart
|
|
&clean_environment();
|
|
local $out = &backquote_logged(
|
|
"$config{'apachectl_path'} graceful 2>&1");
|
|
&reset_environment();
|
|
&wait_for_graceful();
|
|
if ($?) {
|
|
return "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
}
|
|
else {
|
|
&clean_environment();
|
|
local $out = &backquote_logged(
|
|
"$config{'apachectl_path'} restart 2>&1");
|
|
&reset_environment();
|
|
if ($out !~ /httpd restarted/) {
|
|
return "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# send SIGHUP directly
|
|
&open_readfile(PID, $pidfile) || return &text('restart_epid', $pidfile);
|
|
<PID> =~ /(\d+)/ || return &text('restart_epid2', $pidfile);
|
|
close(PID);
|
|
&kill_logged('HUP', $1) || return &text('restart_esig', $1);
|
|
&wait_for_graceful();
|
|
}
|
|
&restart_last_restart_time();
|
|
return undef;
|
|
}
|
|
|
|
# wait_for_graceful([timeout])
|
|
# Wait for some time for Apache to complete a graceful restart
|
|
sub wait_for_graceful
|
|
{
|
|
local $timeout = $_[0] || 10;
|
|
local $errorlog = &get_error_log();
|
|
return -1 if (!$errorlog || !-r $errorlog);
|
|
local @st = stat($errorlog);
|
|
my $start = time();
|
|
while(time() - $start < $timeout) {
|
|
sleep(1);
|
|
open(ERRORLOG, "<".$errorlog);
|
|
seek(ERRORLOG, $st[7], 0);
|
|
local $/ = undef;
|
|
local $rest = <ERRORLOG>;
|
|
close(ERRORLOG);
|
|
if ($rest =~ /resuming\s+normal\s+operations/i) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# stop_apache()
|
|
# Attempts to stop the running Apache process, and returns undef on success or
|
|
# an error message on failure
|
|
sub stop_apache
|
|
{
|
|
local $out;
|
|
if ($config{'stop_cmd'}) {
|
|
# use the configured stop command
|
|
$out = &backquote_logged("($config{'stop_cmd'}) 2>&1");
|
|
if ($?) {
|
|
return "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
}
|
|
elsif (-x $config{'apachectl_path'}) {
|
|
# use the apachectl program
|
|
$out = &backquote_logged("($config{'apachectl_path'} stop) 2>&1");
|
|
if ($httpd_modules{'core'} >= 2 ? $? : $out !~ /httpd stopped/) {
|
|
return "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
}
|
|
else {
|
|
# kill the process
|
|
$pidfile = &get_pid_file();
|
|
open(PID, "<".$pidfile) || return &text('stop_epid', $pidfile);
|
|
<PID> =~ /(\d+)/ || return &text('stop_epid2', $pidfile);
|
|
close(PID);
|
|
&kill_logged('TERM', $1) || return &text('stop_esig', $1);
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# start_apache()
|
|
# Attempts to start Apache, and returns undef on success or an error message
|
|
# upon failure.
|
|
sub start_apache
|
|
{
|
|
local ($out, $cmd);
|
|
&clean_environment();
|
|
if ($config{'start_cmd'}) {
|
|
# use the configured start command
|
|
if ($config{'stop_cmd'}) {
|
|
# execute the stop command to clear lock files
|
|
&system_logged("($config{'stop_cmd'}) >/dev/null 2>&1");
|
|
}
|
|
$out = &backquote_logged("($config{'start_cmd'}) 2>&1");
|
|
&reset_environment();
|
|
if ($?) {
|
|
return "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
}
|
|
elsif (-x $config{'apachectl_path'}) {
|
|
# use the apachectl program
|
|
$cmd = "$config{'apachectl_path'} start";
|
|
$out = &backquote_logged("($cmd) 2>&1");
|
|
&reset_environment();
|
|
}
|
|
else {
|
|
# start manually
|
|
local $httpd = &find_httpd();
|
|
$cmd = "$httpd -d $config{'httpd_dir'}";
|
|
if ($config{'httpd_conf'}) {
|
|
$cmd .= " -f $config{'httpd_conf'}";
|
|
}
|
|
foreach $d (&get_httpd_defines()) {
|
|
$cmd .= " -D$d";
|
|
}
|
|
local $temp = &transname();
|
|
local $rv = &system_logged("( $cmd ) >$temp 2>&1 </dev/null");
|
|
$out = &read_file_contents($temp);
|
|
unlink($temp);
|
|
&reset_environment();
|
|
}
|
|
|
|
# Check if Apache may have failed to start
|
|
local $slept;
|
|
if ($out =~ /\S/ && $out !~ /httpd\s+started/i) {
|
|
sleep(3);
|
|
if (!&is_apache_running()) {
|
|
return "<pre>".&html_escape($cmd)." :\n".
|
|
&html_escape($out)."</pre>";
|
|
}
|
|
$slept = 1;
|
|
}
|
|
|
|
# check if startup was successful. Later apache version return no
|
|
# error code, but instead fail to start and write the reason to
|
|
# the error log file!
|
|
sleep(3) if (!$slept);
|
|
local $conf = &get_config();
|
|
if (!&is_apache_running()) {
|
|
# Not running.. find out why
|
|
local $errorlogstr = &find_directive_struct("ErrorLog", $conf);
|
|
local $errorlog = $errorlogstr ? $errorlogstr->{'words'}->[0]
|
|
: "logs/error_log";
|
|
if ($out =~ /\S/) {
|
|
return "$text{'start_eafter'} : <pre>$out</pre>";
|
|
}
|
|
elsif ($errorlog eq 'syslog' || $errorlog =~ /^\|/) {
|
|
return $text{'start_eunknown'};
|
|
}
|
|
else {
|
|
$errorlog = &server_root($errorlog);
|
|
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
|
|
return "$text{'start_eafter'} : <pre>$out</pre>";
|
|
}
|
|
}
|
|
&restart_last_restart_time();
|
|
return undef;
|
|
}
|
|
|
|
# get_error_log()
|
|
# Returns the path to the global error log, if possible
|
|
sub get_error_log
|
|
{
|
|
local $conf = &get_config();
|
|
local $errorlogstr = &find_directive_struct("ErrorLog", $conf);
|
|
local $errorlog = $errorlogstr ? $errorlogstr->{'words'}->[0]
|
|
: "logs/error_log";
|
|
$errorlog = &server_root($errorlog);
|
|
return $errorlog;
|
|
}
|
|
|
|
sub is_apache_running
|
|
{
|
|
if ($gconfig{'os_type'} eq 'windows') {
|
|
# No such thing as a PID file on Windows
|
|
local ($pid) = &find_byname("Apache.exe");
|
|
return $pid;
|
|
}
|
|
else {
|
|
# Check PID file
|
|
local $pidfile = &get_pid_file();
|
|
return &check_pid_file($pidfile);
|
|
}
|
|
}
|
|
|
|
# wait_for_apache_stop([secs])
|
|
# Wait 30 (by default) seconds for Apache to stop. Returns 1 if OK, 0 if not
|
|
sub wait_for_apache_stop
|
|
{
|
|
local $secs = $_[0] || 30;
|
|
for(my $i=0; $i<$secs; $i++) {
|
|
return 1 if (!&is_apache_running());
|
|
sleep(1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# configurable_modules([&all-mods])
|
|
# Returns a list of Apaches that are compiled in or dynamically loaded, and
|
|
# supported by Webmin.
|
|
sub configurable_modules
|
|
{
|
|
my ($allmods) = @_;
|
|
$allmods ||= [ &available_modules() ];
|
|
return grep { -r "$module_root_directory/$_.pl" } @$allmods;
|
|
}
|
|
|
|
# available_modules()
|
|
# Returns a list of Apaches that are compiled in or dynamically loaded
|
|
sub available_modules
|
|
{
|
|
my ($ver, $mods) = &httpd_info(&find_httpd());
|
|
my @rv;
|
|
|
|
# Add compiled-in modules
|
|
push(@rv, @$mods);
|
|
|
|
# Add dynamically loaded modules
|
|
my $conf = &get_config();
|
|
foreach my $l (&find_directive_struct("LoadModule", $conf)) {
|
|
if ($l->{'words'}->[1] =~ /(mod_\S+)\.(so|dll)/) {
|
|
push(@rv, $1);
|
|
}
|
|
elsif ($l->{'words'}->[1] =~ /libssl\.so/) {
|
|
push(@rv, "mod_apachessl");
|
|
}
|
|
elsif ($l->{'words'}->[1] =~ /lib([^\/\s]+)\.(so|dll)/) {
|
|
push(@rv, "mod_$1");
|
|
}
|
|
}
|
|
|
|
# Add dynamically loaded modules
|
|
if ($config{'apachectl_path'}) {
|
|
&open_execute_command(APACHE,
|
|
"$config{'apachectl_path'} -M 2>/dev/null", 1);
|
|
while(<APACHE>) {
|
|
if (/(\S+)_module/) {
|
|
push(@rv, "mod_${1}");
|
|
}
|
|
}
|
|
close(APACHE);
|
|
}
|
|
|
|
return &unique(@rv);
|
|
}
|
|
|
|
# get_httpd_defines(automatic-only)
|
|
# Returns a list of defines that need to be passed to Apache
|
|
sub get_httpd_defines
|
|
{
|
|
local ($auto) = @_;
|
|
if (@get_httpd_defines_cache) {
|
|
return @get_httpd_defines_cache;
|
|
}
|
|
local @rv;
|
|
if (!$auto) {
|
|
push(@rv, keys %httpd_defines);
|
|
}
|
|
if ($config{'defines_file'}) {
|
|
# Add defines from an environment file, which can be in
|
|
# the format :
|
|
# OPTIONS='-Dfoo -Dbar'
|
|
# or regular name=value format
|
|
local %def;
|
|
&read_env_file($config{'defines_file'}, \%def);
|
|
if ($config{'defines_name'} && $def{$config{'defines_name'}}) {
|
|
# Looking for var like OPTIONS='-Dfoo -Dbar'
|
|
local $var = $def{$config{'defines_name'}};
|
|
foreach my $v (split(/\s+/, $var)) {
|
|
if ($v =~ /^-[Dd](\S+)$/) {
|
|
push(@rv, $1);
|
|
}
|
|
else {
|
|
push(@rv, $v);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# Looking for regular name=value directives.
|
|
# Remove $SUFFIX variable seen on debian that is computed
|
|
# dynamically, but is usually empty.
|
|
foreach my $k (keys %def) {
|
|
$def{$k} =~ s/\$SUFFIX//g;
|
|
push(@rv, $k."=".$def{$k});
|
|
}
|
|
}
|
|
}
|
|
foreach my $md (split(/\t+/, $config{'defines_mods'})) {
|
|
# Add HAVE_ defines from modules
|
|
opendir(DIR, $md);
|
|
while(my $m = readdir(DIR)) {
|
|
if ($m =~ /^(mod_|lib)(.*).so$/i) {
|
|
push(@rv, "HAVE_".uc($2));
|
|
}
|
|
}
|
|
closedir(DIR);
|
|
}
|
|
foreach my $d (split(/\s+/, $config{'defines'})) {
|
|
push(@rv, $d);
|
|
}
|
|
@get_httpd_defines_cache = @rv;
|
|
return @rv;
|
|
}
|
|
|
|
# create_webfile_link(file)
|
|
# Creates a link in the debian-style link directory for a new website's file
|
|
sub create_webfile_link
|
|
{
|
|
local ($file) = @_;
|
|
my $linkdir = &vhost_enabled_dir();
|
|
if ($linkdir) {
|
|
local $short = $file;
|
|
$short =~ s/^.*\///;
|
|
local $linksrc = "$linkdir/$short";
|
|
&lock_file($linksrc);
|
|
symlink($file, $linksrc);
|
|
&unlock_file($linksrc);
|
|
}
|
|
}
|
|
|
|
# delete_webfile_link(file)
|
|
# If the given path is in a directory like /etc/apache2/sites-available, delete
|
|
# the link to it from /etc/apache2/sites-enabled
|
|
sub delete_webfile_link
|
|
{
|
|
local ($file) = @_;
|
|
$file = &simplify_path(&resolve_links($file));
|
|
my $linkdir = &vhost_enabled_dir();
|
|
if ($linkdir && opendir(LINKDIR, $linkdir)) {
|
|
foreach my $f (readdir(LINKDIR)) {
|
|
if ($f ne "." && $f ne "..") {
|
|
my $link = $linkdir."/".$f;
|
|
next if (!-l $link);
|
|
if (&simplify_path(&resolve_links($link)) eq $file) {
|
|
&unlink_logged($link);
|
|
}
|
|
}
|
|
}
|
|
closedir(LINKDIR);
|
|
}
|
|
}
|
|
|
|
# can_configure_apache_modules()
|
|
# Returns 1 if the distro has a way of selecting enabled Apache modules
|
|
sub can_configure_apache_modules
|
|
{
|
|
if ($gconfig{'os_type'} eq 'debian-linux') {
|
|
# Debian and Ubuntu use an /etc/apacheN/mods-enabled dir
|
|
return -d "$config{'httpd_dir'}/mods-enabled" &&
|
|
-d "$config{'httpd_dir'}/mods-available";
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# list_configured_apache_modules()
|
|
# Returns a list of all Apache modules. Each is a hash containing a mod and
|
|
# enabled, disabled and available flags.
|
|
sub list_configured_apache_modules
|
|
{
|
|
if ($gconfig{'os_type'} eq 'debian-linux') {
|
|
# Find enabled modules
|
|
local @rv;
|
|
local $edir = "$config{'httpd_dir'}/mods-enabled";
|
|
opendir(EDIR, $edir);
|
|
foreach my $f (readdir(EDIR)) {
|
|
if ($f =~ /^(\S+)\.load$/) {
|
|
push(@rv, { 'mod' => $1, 'enabled' => 1 });
|
|
}
|
|
}
|
|
closedir(EDIR);
|
|
|
|
# Add available modules
|
|
local $adir = "$config{'httpd_dir'}/mods-available";
|
|
opendir(ADIR, $adir);
|
|
foreach my $f (readdir(ADIR)) {
|
|
if ($f =~ /^(\S+)\.load$/) {
|
|
local ($got) = grep { $_->{'mod'} eq $1 } @rv;
|
|
if (!$got) {
|
|
push(@rv, { 'mod' => $1, 'disabled' => 1 });
|
|
}
|
|
}
|
|
}
|
|
closedir(ADIR);
|
|
|
|
# XXX modules from apt-get
|
|
|
|
return sort { $a->{'mod'} cmp $b->{'mod'} } @rv;
|
|
}
|
|
else {
|
|
# Not supported
|
|
return ( );
|
|
}
|
|
}
|
|
|
|
# add_configured_apache_module(module)
|
|
# Updates the Apache configuration to use some module. Returns undef on success,
|
|
# or an error message on failure.
|
|
sub add_configured_apache_module
|
|
{
|
|
local ($mod) = @_;
|
|
if ($gconfig{'os_type'} eq 'debian-linux') {
|
|
# XXX download from apt-get ?
|
|
|
|
# Enable with a2enmod if installed
|
|
if (&has_command("a2enmod")) {
|
|
local $out = &backquote_logged(
|
|
"a2enmod ".quotemeta($mod)." 2>&1");
|
|
return $? ? $out : undef;
|
|
}
|
|
else {
|
|
# Fall back to creating links
|
|
local $adir = "$config{'httpd_dir'}/mods-available";
|
|
local $edir = "$config{'httpd_dir'}/mods-enabled";
|
|
opendir(ADIR, $adir);
|
|
foreach my $f (readdir(ADIR)) {
|
|
if ($f =~ /^\Q$mod->{'mod'}\E\./) {
|
|
&symlink_logged("$adir/$f", "$edir/$f") ||
|
|
return $!;
|
|
}
|
|
}
|
|
closedir(ADIR);
|
|
return undef;
|
|
}
|
|
}
|
|
else {
|
|
return "Operating system does not support Apache modules";
|
|
}
|
|
}
|
|
|
|
# remove_configured_apache_module(module)
|
|
# Updates the Apache configuration to stop using some module. Returns undef
|
|
# on success, or an error message on failure.
|
|
sub remove_configured_apache_module
|
|
{
|
|
local ($mod) = @_;
|
|
if ($gconfig{'os_type'} eq 'debian-linux') {
|
|
# Disable with a2dismod if installed
|
|
if (&has_command("a2dismod")) {
|
|
local $out = &backquote_logged(
|
|
"a2dismod ".quotemeta($mod)." 2>&1");
|
|
return $? ? $out : undef;
|
|
}
|
|
else {
|
|
# Fall back to removing links
|
|
local $edir = "$config{'httpd_dir'}/mods-enabled";
|
|
opendir(EDIR, $edir);
|
|
foreach my $f (readdir(EDIR)) {
|
|
if ($f =~ /^\Q$mod->{'mod'}\E\./) {
|
|
&unlink_logged("$edir/$f");
|
|
}
|
|
}
|
|
closedir(EDIR);
|
|
return undef;
|
|
}
|
|
}
|
|
else {
|
|
return "Operating system does not support Apache modules";
|
|
}
|
|
}
|
|
|
|
# is_virtualmin_domain(&virt)
|
|
# Returns the domain hash if some virtualhost is managed by Virtualmin
|
|
sub is_virtualmin_domain
|
|
{
|
|
local ($virt) = @_;
|
|
return 0 if ($config{'allow_virtualmin'});
|
|
local $n = &find_directive("ServerName", $virt->{'members'});
|
|
return undef if (!$n);
|
|
return undef if (!&foreign_check("virtual-server"));
|
|
&foreign_require("virtual-server");
|
|
local $d = &virtual_server::get_domain_by("dom", $n);
|
|
return $d if ($d);
|
|
$n =~ s/^www\.//i;
|
|
local $d = &virtual_server::get_domain_by("dom", $n);
|
|
return $d;
|
|
}
|
|
|
|
# update_last_config_change()
|
|
# Updates the flag file indicating when the config was changed
|
|
sub update_last_config_change
|
|
{
|
|
&open_tempfile(FLAG, ">$last_config_change_flag", 0, 1);
|
|
&close_tempfile(FLAG);
|
|
}
|
|
|
|
# restart_last_restart_time()
|
|
# Updates the flag file indicating when the config was changed
|
|
sub restart_last_restart_time
|
|
{
|
|
&open_tempfile(FLAG, ">$last_restart_time_flag", 0, 1);
|
|
&close_tempfile(FLAG);
|
|
}
|
|
|
|
# needs_config_restart()
|
|
# Returns 1 if a restart is needed for sure after a config change
|
|
sub needs_config_restart
|
|
{
|
|
my @cst = stat($last_config_change_flag);
|
|
my @rst = stat($last_restart_time_flag);
|
|
if (@cst && @rst && $cst[9] > $rst[9]) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# format_config(conf-lines-ref, [indent])
|
|
# Formats Apache config lines with default
|
|
# four spaces of indent for each block
|
|
sub format_config
|
|
{
|
|
my ($conf_lref, $indent) = @_;
|
|
|
|
# Prevent formatting if not allowed in config
|
|
return if (!&format_config_allowed());
|
|
|
|
# Default single indent equals 4 spaces
|
|
$indent ||= 4;
|
|
$indent = " " x $indent;
|
|
|
|
# At first check if Apache blocks are ballanced
|
|
my $conf_block_opening;
|
|
my $conf_block_closing;
|
|
foreach my $l (@{$conf_lref}) {
|
|
|
|
# If line doesn't start with # disregard of trailing spaces
|
|
if ($l !~ /^\s*#/) {
|
|
|
|
# This is a new block, count it
|
|
if ($l =~ /(<[a-zA-Z]+).*>/) {
|
|
$conf_block_opening++;
|
|
}
|
|
|
|
# This is a new closing block, count it
|
|
if ($l =~ /(<\/[a-zA-Z]+).*>/) {
|
|
$conf_block_closing++;
|
|
}
|
|
}
|
|
}
|
|
|
|
# If the number of closing and opening blocks
|
|
# is the same then generate proper indents
|
|
if ($conf_block_opening == $conf_block_closing) {
|
|
|
|
my $conf_lvl = 0;
|
|
foreach my $l (@{$conf_lref}) {
|
|
my $indent_current = $indent x $conf_lvl;
|
|
|
|
# If line doesn't start with # disregard of trailing spaces
|
|
if ($l !~ /^\s*#/) {
|
|
|
|
# Indent up next line if a new block
|
|
if ($l =~ /(<[a-zA-Z]+).*>/) {
|
|
$conf_lvl++;
|
|
}
|
|
|
|
# Indent down next line if a closing block
|
|
if ($l =~ /(<\/[a-zA-Z]+).*>/) {
|
|
$conf_lvl--;
|
|
|
|
# Change current indent right now as it is a closing block
|
|
$indent_current = $indent x $conf_lvl;
|
|
}
|
|
}
|
|
|
|
# Replace beginning spaces with needed indent
|
|
$l =~ s/^\s*/$indent_current/
|
|
if($l);
|
|
}
|
|
}
|
|
}
|
|
|
|
# format_config(filename, [indent])
|
|
# Formats Apache config given file
|
|
sub format_config_file
|
|
{
|
|
my ($file, $indent) = @_;
|
|
|
|
# Prevent formatting if not allowed in config
|
|
return if (!&format_config_allowed());
|
|
|
|
# If file was deleted prevent recreating an empty file
|
|
return if (!-r $file);
|
|
|
|
# Lock file
|
|
&lock_file($file);
|
|
|
|
# Open file
|
|
my $conf_lref = &read_file_lines($file);
|
|
|
|
# Format
|
|
&format_config($conf_lref, $indent);
|
|
|
|
# Write file
|
|
&flush_file_lines($file);
|
|
|
|
# Unlock file
|
|
&unlock_file($file);
|
|
}
|
|
|
|
# format_modifed_configs([test-config])
|
|
# Formats all modifed Apache configs during the call
|
|
sub format_modifed_config_files
|
|
{
|
|
my ($force_config_test) = @_;
|
|
if($saved_conf_files) {
|
|
if (&format_config_allowed()) {
|
|
# Test config first if not already
|
|
# tested and don't format on error
|
|
if ($force_config_test) {
|
|
if ($config{'test_manual'} ||
|
|
$config{'test_always'}) {
|
|
my $conf_err = &test_config();
|
|
if ($conf_err) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
# Format indents for each file individually
|
|
foreach my $saved_conf_file (&unique(@{$saved_conf_files})) {
|
|
&format_config_file($saved_conf_file, $config{'format_config_indent'});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# format_config_allowed()
|
|
# Checks if formatting config is allowed
|
|
sub format_config_allowed
|
|
{
|
|
return $config{'format_config'};
|
|
}
|
|
|
|
# clear_apache_modules_cache()
|
|
# If new Apache modules were enabled, force re-gen of the site file
|
|
# that contains the modules cache
|
|
sub clear_apache_modules_cache
|
|
{
|
|
&unlink_file($site_file);
|
|
&create_site_file();
|
|
&read_site_file();
|
|
}
|
|
|
|
1;
|