Files
webmin/nginx/nginx-lib.pl

1809 lines
50 KiB
Perl

# Common functions for Nginx config file
use strict;
use warnings;
no warnings 'recursion';
use Socket;
BEGIN { push(@INC, ".."); };
eval "use WebminCore;";
&init_config();
our %access = &get_module_acl();
our ($get_config_cache, $get_config_parent_cache, %list_directives_cache,
@list_modules_cache, @open_config_files);
our (%config, %text, %in, $module_root_directory);
&set_nginx_config_defaults();
my @lock_all_config_files_cache;
# set_nginx_config_defaults()
# Fill in sensible defaults if module config has not been initialized yet
sub set_nginx_config_defaults
{
my $conf = &detect_nginx_config_file();
if ($conf && (!$config{'nginx_config'} || !-r $config{'nginx_config'})) {
$config{'nginx_config'} = $conf;
}
my $cmd = &detect_nginx_command();
if ($cmd && (!$config{'nginx_cmd'} || !&has_command($config{'nginx_cmd'}))) {
$config{'nginx_cmd'} = $cmd;
}
if (!$config{'start_cmd'} || !$config{'stop_cmd'} || !$config{'apply_cmd'}) {
if (&has_command("systemctl")) {
$config{'start_cmd'} ||= "systemctl start nginx";
$config{'stop_cmd'} ||= "systemctl stop nginx";
$config{'apply_cmd'} ||= "systemctl reload nginx";
}
elsif (&has_command("service")) {
$config{'start_cmd'} ||= "service nginx start";
$config{'stop_cmd'} ||= "service nginx stop";
$config{'apply_cmd'} ||= "service nginx reload";
}
elsif ($config{'nginx_cmd'}) {
$config{'start_cmd'} ||= $config{'nginx_cmd'};
$config{'stop_cmd'} ||= $config{'nginx_cmd'}." -s stop";
$config{'apply_cmd'} ||= $config{'nginx_cmd'}." -s reload";
}
}
}
# detect_nginx_config_file()
# Returns the first readable standard Nginx config file
sub detect_nginx_config_file
{
foreach my $file ($config{'nginx_config'},
"/etc/nginx/nginx.conf",
"/usr/local/nginx/conf/nginx.conf",
"/opt/nginx/conf/nginx.conf") {
return $file if ($file && -r $file);
}
return undef;
}
# detect_nginx_command()
# Returns the first executable Nginx command in a standard path or PATH
sub detect_nginx_command
{
return $config{'nginx_cmd'}
if ($config{'nginx_cmd'} && &has_command($config{'nginx_cmd'}));
foreach my $cmd ("nginx", "/usr/sbin/nginx", "/usr/local/nginx/sbin/nginx",
"/opt/nginx/sbin/nginx") {
my $found = &has_command($cmd);
return $found if ($found);
}
return undef;
}
# get_config()
# Parses the Nginx config file into an array ref
sub get_config
{
if (!$get_config_cache) {
$get_config_cache = &read_config_file($config{'nginx_config'});
}
return $get_config_cache;
}
# get_config_parent()
# Returns an object that represents the whole config file
sub get_config_parent
{
if (!$get_config_parent_cache) {
$get_config_parent_cache = { 'members' => &get_config(),
'type' => 1,
'file' => $config{'nginx_config'},
'indent' => -1,
'line' => 0,
'eline' => 0 };
foreach my $c (@{$get_config_parent_cache->{'members'}}) {
if ($c->{'file'} eq $get_config_parent_cache->{'file'} &&
$c->{'eline'} > $get_config_parent_cache->{'eline'}) {
$get_config_parent_cache->{'eline'} = $c->{'eline'}+1;
}
}
}
return $get_config_parent_cache;
}
# flush_config_cache()
# Delete all in-memory config caches
sub flush_config_cache
{
undef($get_config_parent_cache);
undef($get_config_cache);
}
# remove_hash_comment(line)
# Returns the line with comments removed
sub remove_hash_comment
{
my ($l) = @_;
if ($l =~ /".*#.*"/) {
# Comment inside quotes, so only remove any comment outside quotes
$l =~ s/#[^"]*$//;
}
else {
# Remove all comments
$l =~ s/#.*$//;
}
return $l;
}
# read_config_file(file, [preserve-includes])
# Returns an array ref of nginx config objects
sub read_config_file
{
my ($file, $noinc) = @_;
my $link = &resolve_links($file);
$link || &error("Dangling link $file");
$file = $link;
my @rv = ( );
my $addto = \@rv;
my @stack = ( );
my $lnum = 0;
my $fh = "CFILE".int(rand(1000000));
&open_readfile($fh, $file) || return [];
my @lines = <$fh>;
close($fh);
while(@lines) {
my $l = shift(@lines);
$l = &remove_hash_comment($l);
my ($indent_str) = $l =~ /^(\s*)/;
my $slnum = $lnum;
# If line doesn't end with { } or ; , it must be continued on the
# next line
while($l =~ /\S/ && $l !~ /[\{\}\;]\s*$/ && @lines) {
my $nl = shift(@lines);
if ($nl =~ /\S/) {
$nl = &remove_hash_comment($nl);
$l .= " ".$nl;
}
$lnum++;
}
if ($l =~ /^\s*if\s*\((.*)\)\s*\{\s*$/) {
# Start of an if statement
my $ns = { 'name' => 'if',
'type' => 2,
'indent' => scalar(@stack),
'indent_str' => $indent_str,
'file' => $file,
'line' => $slnum,
'eline' => $lnum,
'members' => [ ] };
my $value = $1;
&set_split_words($ns, " ".$value);
push(@stack, $addto);
push(@$addto, $ns);
$addto = $ns->{'members'};
}
elsif ($l =~ /^\s*(\S+)(\s*.*)\{\s*$/) {
# Start of a section
my $ns = { 'name' => $1,
'type' => 1,
'indent' => scalar(@stack),
'indent_str' => $indent_str,
'file' => $file,
'line' => $slnum,
'eline' => $lnum,
'members' => [ ] };
my $value = $2;
&set_split_words($ns, $value);
push(@stack, $addto);
push(@$addto, $ns);
$addto = $ns->{'members'};
}
elsif ($l =~ /^\s*}/ && @stack) {
# End of a section
$addto = pop(@stack);
$addto->[@$addto-1]->{'eline'} = $lnum;
}
elsif ($l =~ /^\s*(\S+)((\s+("([^"]*)"|'([^']*)'|[^ ;]+))*)\s*;/) {
# Found a directive
my ($name, $value) = ($1, $2);
my @words = &split_words($value);
if ($name eq "include" && !$noinc) {
# Include a file or glob
if ($words[0] !~ /^\//) {
my $filedir = $file;
$filedir =~ s/\/[^\/]+$//;
$words[0] = $filedir."/".$value;
}
foreach my $ifile (glob($words[0])) {
my $inc = &read_config_file($ifile);
push(@$addto, @$inc);
}
}
else {
# Some directive in the current section
my ($sep_str) = $l =~ /^\s*\S+(\s+)/;
my $dir = { 'name' => $name,
'value' => $words[0],
'words' => \@words,
'type' => 0,
'indent' => scalar(@stack),
'indent_str' => $indent_str,
'sep_str' => $sep_str,
'file' => $file,
'line' => $slnum,
'eline' => $lnum };
push(@$addto, $dir);
if (@stack) {
my $lastaddto = $stack[$#stack];
$lastaddto->[@$lastaddto - 1]->{'eline'} = $lnum;
}
}
}
elsif ($l =~ /\S/) {
$l =~ s/\r|\n//g;
print STDERR "Invalid Nginx config line $l at $lnum in $file\n";
}
$lnum++;
}
return \@rv;
}
# extra_dirs_to_directives(directives)
# Parses tab-separated extra directives from module config into Nginx objects
sub extra_dirs_to_directives
{
my ($extra_dirs) = @_;
return ( ) if (!$extra_dirs || $extra_dirs eq "none");
my $temp = &transname();
my $fh = "EXTRA";
&open_tempfile($fh, ">$temp", 0, 1);
&print_tempfile($fh, join("\n", split(/\t+/, $extra_dirs))."\n");
&close_tempfile($fh);
my $econf = &read_config_file($temp, 1);
&clear_directive_lines(@$econf);
&unlink_file($temp);
return @$econf;
}
# clear_directive_lines(&directive, ...)
# Removes file and line metadata from parsed directives
sub clear_directive_lines
{
foreach my $e (@_) {
delete($e->{'file'});
delete($e->{'line'});
delete($e->{'eline'});
if ($e->{'type'}) {
&clear_directive_lines(@{$e->{'members'}});
}
}
}
# split_words(string)
# Convert a string of bare or quoted words into a list
sub split_words
{
my ($value) = @_;
my @words;
while($value =~ s/^\s+"([^"]+)"// ||
$value =~ s/^\s+'([^']+)'// ||
$value =~ s/^\s+(\S+)//) {
push(@words, $1);
}
return @words;
}
# set_split_words(&directive, string)
# Set the words and value fields based on a string
sub set_split_words
{
my ($ns, $value) = @_;
my @s = &split_words($value);
$ns->{'words'} = \@s;
$ns->{'value'} = @s ? $s[0] : undef;
}
# get_add_to_file(name)
# Returns the file to add new servers to, if any
sub get_add_to_file
{
my ($name) = @_;
if (!$config{'add_to'}) {
return undef;
}
elsif (-d $config{'add_to'}) {
$name =~ s/[^a-zA-Z0-9\.\_\-]//g;
if ($name) {
return $config{'add_to'}."/".$name.".conf";
}
}
else {
return $config{'add_to'};
}
return undef;
}
# find(name, [&config|&parent])
# Returns the object or objects with some name in the given config
sub find
{
my ($name, $conf) = @_;
$conf ||= &get_config();
if (ref($conf) eq 'HASH') {
$conf = $conf->{'members'};
}
my @rv;
foreach my $c (@$conf) {
if (lc($c->{'name'}) eq $name) {
push(@rv, $c);
}
}
return wantarray ? @rv : $rv[0];
}
# find_value(name, [config])
# Returns the value of the object or objects with some name in the given config
sub find_value
{
my ($name, $conf) = @_;
my @rv = map { my @w = @{$_->{'words'}};
(@w ? $w[0] : undef) || $_->{'value'} } &find($name, $conf);
return wantarray ? @rv : $rv[0];
}
# find_recursive(name, [&config|&parent])
# Returns all objects under some parent with the given name
sub find_recursive
{
my ($name, $conf) = @_;
$conf ||= &get_config();
if (ref($conf) eq 'HASH') {
$conf = $conf->{'members'};
}
my @rv;
foreach my $c (@$conf) {
if (lc($c->{'name'}) eq $name) {
push(@rv, $c);
}
if ($c->{'type'}) {
push(@rv, &find_recursive($name, $c));
}
}
return wantarray ? @rv : $rv[0];
}
# save_directive(&parent, name|&oldobjects, &newvalues|&newobjects, [&before])
# Updates the values of some named directive
sub save_directive
{
my ($parent, $name_or_oldstructs, $values, $before) = @_;
$values = [ $values ] if (!ref($values));
my $oldstructs = ref($name_or_oldstructs) ? $name_or_oldstructs :
[ &find($name_or_oldstructs, $parent) ];
my $name = !ref($name_or_oldstructs) ? $name_or_oldstructs :
@$name_or_oldstructs ? $name_or_oldstructs->[0]->{'name'} : undef;
my $newstructs = [ map { &value_to_struct($name, $_) } @$values ];
for(my $i=0; $i<@$newstructs || $i<@$oldstructs; $i++) {
my $o = $i<@$oldstructs ? $oldstructs->[$i] : undef;
my $n = $i<@$newstructs ? $newstructs->[$i] : undef;
my $file = $o ? $o->{'file'} :
$n && $n->{'file'} ? $n->{'file'} : $parent->{'file'};
my $lref = &read_file_lines($file);
push(@open_config_files, $file);
if ($i<@$newstructs && $i<@$oldstructs) {
# Updating some directive
my $olen = $o->{'eline'} - $o->{'line'} + 1;
my $oldline = $lref->[$o->{'line'}];
my $indent = &directive_indent($o, $parent, $lref);
$n->{'indent_str'} = $indent
if (!defined($n->{'indent_str'}));
$n->{'indent'} = $o->{'indent'}
if (defined($o->{'indent'}));
$n->{'sep_str'} = &directive_value_separator($n, $oldline)
if (!defined($n->{'sep_str'}));
my @lines = &make_directive_lines($n, $indent, $parent, $lref);
$o->{'name'} = $n->{'name'};
$o->{'value'} = $n->{'words'}->[0];
$o->{'words'} = $n->{'words'};
$o->{'indent'} = $n->{'indent'} if (defined($n->{'indent'}));
$o->{'indent_str'} = $n->{'indent_str'};
$o->{'sep_str'} = $n->{'sep_str'};
splice(@$lref, $o->{'line'}, $olen, @lines);
if ($olen != scalar(@lines)) {
# Renumber directives
&renumber($file, $o->{'line'}, $olen - scalar(@lines));
$o->{'eline'} = $o->{'line'} + scalar(@lines) - 1;
}
}
elsif ($i<@$newstructs) {
# Adding a directive
my @lines;
$n->{'value'} = $n->{'words'}->[0];
if ($n->{'file'}) {
# New file, add at start
my $indent = defined($n->{'indent_str'}) ?
$n->{'indent_str'} : "";
$n->{'indent_str'} = $indent
if (!defined($n->{'indent_str'}));
$n->{'indent'} = 0 if (!defined($n->{'indent'}));
@lines = &make_directive_lines($n, $indent, $parent, $lref);
$n->{'line'} = 0;
$n->{'eline'} = scalar(@lines) - 1;
&recursive_set_file($n, $n->{'file'}, $n->{'line'});
unshift(@{$parent->{'members'}}, $n);
}
elsif ($before) {
# Insert into parent before some other directive
my $indent = &directive_indent($before, $parent, $lref);
$n->{'indent_str'} = $indent
if (!defined($n->{'indent_str'}));
$n->{'indent'} = $parent->{'indent'} + 1
if (!defined($n->{'indent'}) &&
defined($parent->{'indent'}));
@lines = &make_directive_lines($n, $indent, $parent, $lref);
$n->{'line'} = $before->{'line'};
$n->{'eline'} = $n->{'line'} + scalar(@lines) - 1;
&recursive_set_file($n, $file, $n->{'line'});
&renumber($file, $n->{'line'}-1, scalar(@lines));
my $idx = &indexof($before, @{$parent->{'members'}});
if ($idx >= 0) {
splice(@{$parent->{'members'}}, $idx, 0, $n);
}
else {
push(@{$parent->{'members'}}, $n);
}
}
else {
# Insert into parent at end
my $indent = &new_directive_indent($parent, $lref);
$n->{'indent_str'} = $indent
if (!defined($n->{'indent_str'}));
$n->{'indent'} = $parent->{'indent'} + 1
if (!defined($n->{'indent'}) &&
defined($parent->{'indent'}));
@lines = &make_directive_lines($n, $indent, $parent, $lref);
$n->{'line'} = $parent->{'eline'};
$n->{'eline'} = $n->{'line'} + scalar(@lines) - 1;
&recursive_set_file($n, $file, $n->{'line'});
&renumber($file, $parent->{'eline'}-1, scalar(@lines));
push(@{$parent->{'members'}}, $n);
}
splice(@$lref, $n->{'line'}, 0, @lines);
}
elsif ($i<@$oldstructs) {
# Removing a directive
my $olen = $o->{'eline'} - $o->{'line'} + 1;
splice(@$lref, $o->{'line'}, $olen);
my $idx = &indexof($o, @{$parent->{'members'}});
if ($idx >= 0) {
splice(@{$parent->{'members'}}, $idx, 1);
}
&renumber($file, $o->{'line'}, -$olen);
}
}
}
# renumber(filename, line, offset, [&parent])
# Adjusts the line number of any directive after the one given by the offset
sub renumber
{
my ($file, $line, $offset, $object) = @_;
$object ||= &get_config_parent();
if ($object->{'file'} eq $file) {
$object->{'line'} += $offset if ($object->{'line'} > $line);
$object->{'eline'} += $offset if ($object->{'eline'} > $line);
}
if ($object->{'type'}) {
foreach my $m (@{$object->{'members'}}) {
&renumber($file, $line, $offset, $m);
}
}
}
# recursive_set_file(&parent, filename, start-line)
# Sets the file on some object and all children
sub recursive_set_file
{
my ($parent, $file, $line) = @_;
$parent->{'file'} ||= $file;
$parent->{'line'} ||= $line;
$parent->{'eline'} ||= $parent->{'line'};
if ($parent->{'type'}) {
my $n = 1;
foreach my $dir (@{$parent->{'members'}}) {
&recursive_set_file($dir, $file, $parent->{'line'} + $n);
$n += ($dir->{'eline'} - $dir->{'line'} + 1);
}
$parent->{'eline'} = $parent->{'line'} + $n;
}
}
# flush_config_file_lines([&parent])
# Flush all lines in the current config
sub flush_config_file_lines
{
my ($parent) = @_;
foreach my $f (&unique(@open_config_files)) {
&flush_file_lines($f);
}
@open_config_files = ( );
}
# lock_all_config_files([&parent])
# Locks all files used in the current config
sub lock_all_config_files
{
my ($parent) = @_;
@lock_all_config_files_cache = &get_all_config_files($parent);
foreach my $f (@lock_all_config_files_cache) {
&lock_file($f);
}
}
# unlock_all_config_files([&parent])
# Un-locks all files used in the current config
sub unlock_all_config_files
{
my ($parent) = @_;
foreach my $f (reverse(@lock_all_config_files_cache)) {
&unlock_file($f);
}
@lock_all_config_files_cache = ();
}
# get_all_config_files([&parent])
# Returns all files in the given config object
sub get_all_config_files
{
my ($parent) = @_;
$parent ||= &get_config_parent();
my @rv = ( $parent->{'file'} );
if ($parent->{'type'}) {
foreach my $c (@{$parent->{'members'}}) {
push(@rv, &get_all_config_files($c));
}
}
return &unique(@rv);
}
# directive_indent(&directive, &parent, &file-lines)
# Returns the exact whitespace prefix to use when writing a directive
sub directive_indent
{
my ($dir, $parent, $lref) = @_;
return $dir->{'indent_str'} if (defined($dir->{'indent_str'}));
if ($lref && defined($dir->{'line'}) && defined($lref->[$dir->{'line'}]) &&
$lref->[$dir->{'line'}] =~ /^(\s*)/) {
return $1;
}
return &new_directive_indent($parent, $lref) if ($parent);
return &indent_string($dir->{'indent'}, $lref)
if (defined($dir->{'indent'}));
return "";
}
# new_directive_indent(&parent, &file-lines)
# Returns an exact whitespace prefix for a new child directive
sub new_directive_indent
{
my ($parent, $lref) = @_;
return "" if (!$parent);
return "" if (defined($parent->{'indent'}) && $parent->{'indent'} < 0);
foreach my $m (@{$parent->{'members'} || []}) {
next if ($parent->{'file'} && $m->{'file'} &&
$parent->{'file'} ne $m->{'file'});
return $m->{'indent_str'} if (defined($m->{'indent_str'}));
}
my $pindent = defined($parent->{'indent_str'}) ? $parent->{'indent_str'} :
defined($parent->{'indent'}) ? &indent_string($parent->{'indent'}, $lref) : "";
return $pindent.&child_indent_step($parent, $lref);
}
# child_indent_step(&parent, &file-lines)
# Returns one indentation level used below a parent block
sub child_indent_step
{
my ($parent, $lref) = @_;
my $pindent = defined($parent->{'indent_str'}) ? $parent->{'indent_str'} :
defined($parent->{'indent'}) ? &indent_string($parent->{'indent'}, $lref) : "";
foreach my $m (@{$parent->{'members'} || []}) {
next if ($parent->{'file'} && $m->{'file'} &&
$parent->{'file'} ne $m->{'file'});
my $mindent = $m->{'indent_str'};
if (defined($mindent) && index($mindent, $pindent) == 0 &&
length($mindent) > length($pindent)) {
return substr($mindent, length($pindent));
}
}
return &config_indent_step($lref);
}
# config_indent_step(&file-lines)
# Returns a one-level indent already used in the current file
sub config_indent_step
{
my ($lref) = @_;
my %indents;
if ($lref) {
foreach my $l (@$lref) {
next if (!defined($l) || $l !~ /^(\s+)\S/);
$indents{$1}++;
}
}
return (sort { length($a) <=> length($b) ||
$indents{$b} <=> $indents{$a} } keys %indents)[0]
if (%indents);
return &default_indent_step();
}
# default_indent_step()
# Returns one generated indentation level for new blocks
sub default_indent_step
{
return " ";
}
# make_directive_lines(&directive, indent, [&parent], [&file-lines])
# Returns text for some directive
sub make_directive_lines
{
my ($dir, $indent, $parent, $lref) = @_;
my $indent_str = &indent_string($indent, $lref);
$dir->{'indent_str'} = $indent_str if (!defined($dir->{'indent_str'}));
my @rv;
my @w = @{$dir->{'words'}};
if ($dir->{'type'}) {
# Multi-line
if ($dir->{'name'} eq 'if') {
push(@rv, $indent_str.$dir->{'name'}.' ('.&join_words(@w).') {');
}
else {
push(@rv, $indent_str.$dir->{'name'}.
(@w ? " ".&join_words(@w) : "")." {");
}
my $step = &child_indent_step($dir, $lref);
foreach my $m (@{$dir->{'members'}}) {
my $mindent = &directive_indent($m, $dir, $lref);
$mindent = $indent_str.$step if (!defined($mindent));
push(@rv, &make_directive_lines($m, $mindent, $dir, $lref));
}
push(@rv, $indent_str."}");
}
else {
# Single line
my $sep = @w ? ($dir->{'sep_str'} || " ") : "";
push(@rv, $indent_str.$dir->{'name'}.$sep.&join_words(@w).";");
}
return wantarray ? @rv : $rv[0];
}
# indent_string(indent, [&file-lines])
# Converts an indent depth or exact whitespace to exact whitespace
sub indent_string
{
my ($indent, $lref) = @_;
return "" if (!defined($indent));
return &config_indent_step($lref) x $indent if ($indent =~ /^\d+$/);
return $indent;
}
# directive_value_separator(&directive, old-line)
# Returns whitespace between directive name and value
sub directive_value_separator
{
my ($dir, $oldline) = @_;
return undef if (!@{$dir->{'words'} || []});
return $1 if (defined($oldline) &&
$oldline =~ /^\s*\Q$dir->{'name'}\E(\s+)/);
return $dir->{'sep_str'} || " ";
}
# join_words(word, etc..)
# Returns a string made by joining directive words
sub join_words
{
my @rv;
foreach my $w (@_) {
if ($w eq "") {
push(@rv, '""');
}
elsif ($w =~ /\s|;|\$/ && $w !~ /"/ && $w !~ /^\$/) {
push(@rv, "\"$w\"");
}
elsif ($w =~ /\s|;|\$/ && $w !~ /^\$/) {
push(@rv, "'$w'");
}
else {
push(@rv, $w);
}
}
return join(" ", @rv);
}
# value_to_struct(name, value)
# Converts a string, array ref or hash ref to a config struct
sub value_to_struct
{
my ($name, $value) = @_;
if (ref($value) eq 'HASH') {
# Already in correct format
$value->{'name'} ||= $name;
return $value;
}
elsif (ref($value) eq 'ARRAY') {
# Array of words
return { 'name' => $name,
'words' => $value,
'value' => $value->[0] };
}
else {
# Single value
return { 'name' => $name,
'words' => [ $value ],
'value' => $value };
}
}
# get_nginx_version()
# Returns the version number of the installed Nginx binary
sub get_nginx_version
{
my $out = &backquote_command("$config{'nginx_cmd'} -v 2>&1 </dev/null");
return $out =~ /version:\s*nginx\/([0-9\.]+)/i ? $1 : undef;
}
# list_nginx_directives()
# Returns a hash ref of hash refs, with name, module, default and context keys
sub list_nginx_directives
{
if (!%list_directives_cache) {
my $lref = &read_file_lines(
"$module_root_directory/nginx-directives", 1);
foreach my $l (@$lref) {
my ($module, $name, $default, $context) = split(/\t/, $l);
$list_directives_cache{$name} =
{ 'module' => $module,
'name' => $name,
'default' => $default eq '-' ? undef : $default,
'context' => $context eq '-' ? undef :
[ split(/,/, $context) ],
};
}
}
return \%list_directives_cache;
}
# get_default(name)
# Returns the default value for some directive
sub get_default
{
my ($name) = @_;
my $dirs = &list_nginx_directives();
my $dir = $dirs->{$name};
return $dir ? $dir->{'default'} : undef;
}
sub get_default_server_param
{
my $ver = &get_nginx_version();
return &compare_version_numbers($ver, "0.8.21") >= 0 ?
"default_server" : "default";
}
# list_nginx_modules()
# Returns a list of enabled modules. Includes those compiled in by default
# unless disabled, plus extra compiled in at build time.
sub list_nginx_modules
{
if (!@list_modules_cache) {
@list_modules_cache = ( 'http_core', 'http_access', 'http_access',
'http_auth_basic', 'http_autoindex',
'http_browser', 'http_charset',
'http_empty_gif', 'http_fastcgi', 'http_geo',
'http_gzip', 'http_limit_req',
'http_limit_zone', 'http_map',
'http_memcached', 'http_proxy',
'http_referer', 'http_rewrite',
'http_scgi', 'http_split_clients',
'http_ssi', 'http_userid', 'http_index',
'http_uwsgi', 'http_log', 'core' );
my $out = &backquote_command("$config{'nginx_cmd'} -V 2>&1 </dev/null");
while($out =~ s/--with-(\S+)_module\s+//) {
push(@list_modules_cache, $1);
}
while($out =~ s/--without-(\S+)_module\s+//) {
@list_modules_cache = grep { $_ ne $1 } @list_modules_cache;
}
}
return @list_modules_cache;
}
# supported_directive(name, [&parent])
# Returns 1 if the module for some directive is supported on this system
sub supported_directive
{
my ($name, $parent) = @_;
my $dirs = &list_nginx_directives();
my $dir = $dirs->{$name};
return 0 if (!$dir);
return 0 if ($dir->{'context'} && $parent &&
&indexof($parent->{'name'}, @{$dir->{'context'}}) < 0);
my @mods = &list_nginx_modules();
#return 0 if (&indexof($dir->{'module'}, @mods) < 0);
return 1;
}
# nginx_onoff_input(name, &parent)
# Returns HTML for a table row for an on/off input
sub nginx_onoff_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my $value = &find_value($name, $parent);
$value ||= &get_default($name);
$value ||= "";
return &ui_table_row($text{'opt_'.$name},
&ui_yesno_radio($name, $value =~ /on|true|yes/i ? 1 : 0));
}
# nginx_onoff_parse(name, &parent, &in)
# Updates the config with input from nginx_onoff_input
sub nginx_onoff_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
&save_directive($parent, $name, [ $in->{$name} ? "on" : "off" ]);
}
# nginx_opt_input(name, &parent, size, prefix, suffix, [multi-value])
# Returns HTML for an optional text field
sub nginx_opt_input
{
my ($name, $parent, $size, $prefix, $suffix, $multi) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my $value = $obj ? ($multi ? &join_words(@{$obj->{'words'}})
: $obj->{'value'})
: undef;
my $def = &get_default($name);
return &ui_table_row($text{'opt_'.$name},
&ui_opt_textbox($name, $value, $size,
$text{'default'}.($def ? " ($def)" : ""), $prefix).
$suffix, $size > 40 ? 3 : 1);
}
# nginx_opt_parse(name, &parent, &in, [regex], [&validator], [multi-value])
# Updates the config with input from nginx_opt_input
sub nginx_opt_parse
{
my ($name, $parent, $in, $regexp, $vfunc, $multi) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"}) {
&save_directive($parent, $name, [ ]);
}
else {
my $v = $in->{$name};
my @w = $multi ? &split_quoted_string($v) : ( $v );
$v eq '' && &error(&text('opt_missing', $text{'opt_'.$name}));
!$regexp || $v =~ /$regexp/ || &error($text{'opt_e'.$name} || $name);
my $err = $vfunc && &$vfunc($v, $name);
$err && &error($err);
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@w } ]);
}
}
# nginx_text_input(name, &parent, size, suffix, [multi-value])
# Returns HTML for a non-optional text field
sub nginx_text_input
{
my ($name, $parent, $size, $suffix, $multi) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my $value = $obj ? ($multi ? &join_words(@{$obj->{'words'}})
: $obj->{'value'})
: undef;
$suffix ||= "";
return &ui_table_row($text{'opt_'.$name},
&ui_textbox($name, $value, $size).$suffix, $size > 40 ? 3 : 1);
}
# nginx_text_parse(name, &parent, &in, [regex], [&validator], [multi-value])
# Updates the config with input from nginx_text_input
sub nginx_text_parse
{
my ($name, $parent, $in, $regexp, $vfunc, $multi) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
my $v = $in->{$name};
my @w = $multi ? &split_quoted_string($v) : ( $v );
foreach my $wv (@w) {
$wv eq '' && &error(&text('opt_missing', $text{'opt_'.$name}));
!$regexp || $wv =~ /$regexp/ || &error($text{'opt_e'.$name});
my $err = $vfunc && &$vfunc($wv, $name);
$err && &error($err);
}
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@w } ]);
}
# nginx_error_log_input(name, &parent)
# Returns HTML specifically for setting the error_log directive
sub nginx_error_log_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my $def = $parent->{'name'} eq 'server' ? $text{'opt_global'}
: &get_default($name);
$def =~ s/^\$\{prefix\}\///;
return &ui_table_row($text{'opt_'.$name},
&ui_radio($name."_def", $obj ? 0 : 1,
[ [ 1, $text{'default'}.($def ? " ($def)" : "")."<br>" ],
[ 0, $text{'logs_file'} ] ])." ".
&ui_textbox($name, $obj ? $obj->{'words'}->[0] : undef, 40)." ".
$text{'logs_level'}." ".
&ui_select($name."_level", $obj ? $obj->{'words'}->[1] : "",
[ [ "", "&lt;$text{'default'}&gt;" ],
"debug", "info", "notice", "warn", "error", "crit" ]));
}
# nginx_error_log_parse(name, &parent, &in)
# Validate input from nginx_error_log_input
sub nginx_error_log_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"}) {
&save_directive($parent, $name, [ ]);
}
else {
$in->{$name} || &error(&text('opt_missing', $text{'opt_'.$name}));
$in->{$name} =~ /^\/\S+$/ || &error($text{'opt_e'.$name});
my @w = ( $in->{$name} );
push(@w, $in->{$name."_level"}) if ($in->{$name."_level"});
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@w } ]);
}
}
# nginx_access_log_input(name, &parent)
# Returns HTML specifically for setting the access_log directive
sub nginx_access_log_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my $mode = !$obj ? 1 : $obj->{'value'} eq 'off' ? 2 : 0;
my $buffer = $mode == 0 && $obj->{'words'}->[2] =~ /buffer=(\S+)/ ? $1 : "";
my $def = $parent->{'name'} eq 'server' ? $text{'opt_global'}
: &get_default($name);
return &ui_table_row($text{'opt_'.$name},
&ui_radio($name."_def", $mode,
[ [ 1, $text{'default'}.($def ? " ($def)" : "")."<br>" ],
[ 2, $text{'logs_disabled'}."<br>" ],
[ 0, $text{'logs_file'} ] ])." ".
&ui_textbox($name, $mode == 0 ? $obj->{'words'}->[0] : undef, 40)." ".
$text{'logs_format'}." ".
&ui_select($name."_format", $mode == 0 ? $obj->{'words'}->[1] : "",
[ [ "", "&lt;$text{'default'}&gt;" ],
&list_log_formats($parent) ])." ".
$text{'logs_buffer'}." ".
&ui_textbox($name."_buffer", $buffer, 6));
}
# nginx_access_log_parse(name, &parent, &in)
# Validate input from nginx_access_log_input
sub nginx_access_log_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"} == 1) {
&save_directive($parent, $name, [ ]);
}
elsif ($in->{$name."_def"} == 2) {
&save_directive($parent, $name, [ "off" ]);
}
else {
$in->{$name} || &error(&text('opt_missing', $text{'opt_'.$name}));
$in->{$name} =~ /^\/\S+$/ || &error($text{'opt_e'.$name});
my @w = ( $in->{$name} );
push(@w, $in->{$name."_format"}) if ($in->{$name."_format"});
my $buffer = $in->{$name."_buffer"};
if ($buffer) {
$buffer =~ /^\d+[bKMGT]?$/i || &error($text{'logs_ebuffer'});
push(@w, "buffer=$buffer");
}
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@w } ]);
}
}
# nginx_user_input(name, &parent)
# Returns HTML for a user field with an optional group
sub nginx_user_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my $def = &get_default($name);
return &ui_table_row($text{'opt_'.$name},
&ui_radio($name."_def", $obj ? 0 : 1,
[ [ 1, $text{'default'}.($def ? " ($def)" : "")."<br>" ],
[ 0, $text{'misc_username'} ] ])." ".
&ui_user_textbox($name, $obj ? $obj->{'words'}->[0] : "")." ".
$text{'misc_group'}." ".
&ui_group_textbox($name."_group", $obj ? $obj->{'words'}->[1] : ""));
}
# nginx_user_parse(name, &parent, &in)
# Validate input from nginx_user_input
sub nginx_user_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"} == 1) {
&save_directive($parent, $name, [ ]);
}
else {
$in->{$name} || &error(&text('opt_missing', $text{'opt_'.$name}));
defined(getpwnam($in->{$name})) || &error($text{'misc_euser'});
my @w = ( $in->{$name} );
my $group = $in->{$name."_group"};
if ($group) {
defined(getgrnam($group)) || &error($text{'misc_egroup'});
push(@w, $group);
}
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@w } ]);
}
}
# nginx_logformat_input(name, parent)
# Returns HTML for entering multiple log formats
sub nginx_logformat_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my @obj = &find($name, $parent);
my $ftable = &ui_columns_start([ $text{'logs_fname'},
$text{'logs_ftext'} ]);
my $i = 0;
foreach my $o (@obj, { 'words' => [ ] }) {
my @w = @{$o->{'words'}};
$ftable .= &ui_columns_row([
&ui_textbox($name."_name_$i", shift(@w), 20),
&ui_textbox($name."_text_$i", join(" ", @w), 60),
]);
$i++;
}
$ftable .= &ui_columns_end();
return &ui_table_row($text{'opt_'.$name}, $ftable, 3);
}
# nginx_logformat_parse(name, &parent, &in)
# Validate input from nginx_logformat_input
sub nginx_logformat_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
my @obj;
for(my $i=0; defined(my $fname = $in{$name."_name_$i"}); $i++) {
next if (!$fname);
my $ftext = $in{$name."_text_$i"};
$fname =~ /^[a-zA-Z0-9\-\.\_]+$/ ||
&error(&text('logs_efname', $fname));
$ftext =~ /\S/ || &error(&text('logs_etext', $fname));
push(@obj, { 'name' => $name,
'words' => [ $fname, $ftext ] });
}
&save_directive($parent, $name, \@obj);
}
# nginx_multi_input(name, &parent, &options)
# Returns HTML for selecting multiple options
sub nginx_multi_input
{
my ($name, $parent, $opts) = @_;
return undef if (!&supported_directive($name, $parent));
my $def = &get_default($name);
my $obj = &find($name, $parent);
return &ui_table_row($text{'opt_'.$name},
&ui_radio($name."_def", $obj ? 0 : 1,
[ [ 1, $text{'default'}.($def ? " ($def)" : "") ],
[ 0, $text{'opt_selected'}."<br>" ] ])." ".
&ui_select($name, $obj ? $obj->{'words'} : [ ], $opts, scalar(@$opts),
1, 1));
}
# nginx_multi_parse(name, &parent)
# Validate input from nginx_multi_input
sub nginx_multi_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"} == 1) {
&save_directive($parent, $name, [ ]);
}
else {
my @w = split(/\0/, $in->{$name});
@w || &error(&text('opt_missing', $text{'opt_'.$name}));
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@w } ]);
}
}
# nginx_param_input(name, &parent, [name-text, value-text])
# Returns HTML for entering multiple name value paramters
sub nginx_param_input
{
my ($name, $parent, $ntext, $vtext) = @_;
$ntext ||= $text{'fcgi_pname'};
$vtext ||= $text{'fcgi_pvalue'};
return undef if (!&supported_directive($name, $parent));
my @obj = &find($name, $parent);
my $ftable = &ui_columns_start([ $ntext, $vtext ]);
my $i = 0;
foreach my $o (@obj, { 'words' => [ ] }) {
my @w = @{$o->{'words'}};
$ftable .= &ui_columns_row([
&ui_textbox($name."_name_$i", shift(@w), 20),
&ui_textbox($name."_value_$i", join(" ", @w), 60),
]);
$i++;
}
$ftable .= &ui_columns_end();
return &ui_table_row($text{'opt_'.$name}, $ftable, 3);
}
# nginx_params_parse(name, &parent, &in)
# Parses inputs from nginx_param_input
sub nginx_params_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
my @obj;
for(my $i=0; defined(my $pname = $in{$name."_name_$i"}); $i++) {
next if (!$pname);
my $pvalue = $in{$name."_value_$i"};
$pname =~ /^[a-zA-Z0-9\-\.\_]+$/ ||
&error(&text('fcgi_epname', $pname));
$pvalue =~ /\S/ || &error(&text('fcgi_epvalue', $pname));
push(@obj, { 'name' => $name,
'words' => [ $pname, $pvalue ] });
}
&save_directive($parent, $name, \@obj);
}
# nginx_opt_list_input(name, &parent, size, prefix, suffix)
# Returns HTML for an optional text field with multiple values
sub nginx_opt_list_input
{
my ($name, $parent, $size, $prefix, $suffix) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my $value = $obj ? join(" ", @{$obj->{'words'}}) : "";
my $def = &get_default($name);
return &ui_table_row($text{'opt_'.$name},
&ui_opt_textbox($name, $value, $size,
$text{'default'}.($def ? " ($def)" : ""), $prefix).
$suffix, $size > 40 ? 3 : 1);
}
# nginx_opt_list_parse(name, &parent, &in, [regex], [&validator])
# Updates the config with input from nginx_opt_list_input
sub nginx_opt_list_parse
{
my ($name, $parent, $in, $regexp, $vfunc) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"}) {
&save_directive($parent, $name, [ ]);
}
else {
my @v = &split_quoted_string($in->{$name});
@v || &error(&text('opt_missing', $text{'opt_'.$name}));
foreach my $v (@v) {
!$regexp || $v =~ /$regexp/ ||
&error(&text('opt_e'.$name, $v) || $name);
my $err = $vfunc && &$vfunc($v, $name);
$err && &error($err);
}
&save_directive($parent, $name, [ { 'name' => $name,
'words' => \@v } ]);
}
}
# nginx_textarea_input(name, &parent, width, height)
# Returns HTML for entering the values of multiple directives of the same type,
# in a text area
sub nginx_textarea_input
{
my ($name, $parent, $width, $height) = @_;
return undef if (!&supported_directive($name, $parent));
my @obj = &find($name, $parent);
return &ui_table_row($text{'opt_'.$name},
&ui_textarea($name,
join("\n", map { $_->{'words'}->[0] } @obj),
$height, $width), 3);
}
# nginx_textarea_parse(name, &parent, &in, [&regex], [&validator])
# Parses inputs from nginx_param_input
sub nginx_textarea_parse
{
my ($name, $parent, $in, $regexp, $vfunc) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
my @obj;
foreach my $v (split(/\r?\n/, $in->{$name})) {
!$regexp || $v =~ /$regexp/ ||
&error(&text('opt_e'.$name, $v) || $name);
my $err = $vfunc && &$vfunc($v, $name);
$err && &error($err);
push(@obj, { 'name' => $name,
'words' => [ $v ] });
}
&save_directive($parent, $name, \@obj);
}
# nginx_access_input(name1, name2, &parent)
# Returns HTML for setting allow and deny directives
sub nginx_access_input
{
my ($allow, $deny, $parent) = @_;
return undef if (!&supported_directive($allow, $parent));
my @obj = sort { $a->{'line'} <=> $b->{'line'} }
(&find($allow, $parent), &find($deny, $parent));
my $table = &ui_columns_start([ $text{'access_mode'},
$text{'access_value'} ], 100, 0,
[ "nowrap", "nowrap" ]);
my $i =0;
foreach my $o (@obj, { }, { }) {
my $v = $o->{'value'};
$v = "" if (lc($v) eq "all");
$table .= &ui_columns_row([
&ui_select($allow."_mode_".$i,
$o->{'name'},
[ [ "", "&nbsp;" ],
[ "allow", $text{'access_allow'} ],
[ "deny", $text{'access_deny'} ] ]),
&ui_opt_textbox($allow."_addr_".$i, $v, 30,
$text{'access_all'}, $text{'access_addr'}),
]);
$i++;
}
$table .= &ui_columns_end();
return &ui_table_row($text{'opt_'.$allow}, $table, 3);
}
# nginx_access_parse(name1, name2, &parent, &in)
# Parse inputs from nginx_access_input
sub nginx_access_parse
{
my ($allow, $deny, $parent, $in) = @_;
return undef if (!&supported_directive($allow, $parent));
$in ||= \%in;
my @obj;
my @old = sort { $a->{'line'} <=> $b->{'line'} }
(&find($allow, $parent), &find($deny, $parent));
for(my $i=0; defined(my $mode = $in->{$allow."_mode_".$i}); $i++) {
next if (!$mode);
my $addr;
if ($in->{$allow."_addr_".$i."_def"}) {
$addr = "all";
}
else {
$addr = $in->{$allow."_addr_".$i};
$addr || &error(&text('access_eaddrnone', $i+1));
&check_ipaddress($addr) ||
$addr =~ /^(\S+)\/(\d+)$/ &&
&check_ipaddress("$1") && $2 > 0 && $2 <= 32 ||
&check_ip6address($addr) ||
$addr =~ /^(\S+)\/(\d+)$/ && &check_ip6address("$1") ||
&error(&text('access_eaddr', $addr));
}
push(@obj, { 'name' => $mode,
'words' => [ $addr ] });
}
&save_directive($parent, \@old, \@obj);
}
# nginx_realm_input(name, &parent)
# Returns HTML for entering an authentication realm
sub nginx_realm_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my $value = &find_value($name, $parent);
my $def = &get_default($name);
return &ui_table_row($text{'opt_'.$name},
&ui_radio($name."_def",
!$value ? 1 : $value eq "off" ? 2 : 0,
[ [ 1, $text{'default'}.($def ? " ($def)" : "") ],
[ 2, $text{'access_off'} ],
[ 0, $text{'access_realm'}." ".
&ui_textbox($name, $value eq "off" ? "" : $value, 40) ]
]), 3);
}
# nginx_realm_parse(name, &parent, &in)
# Updates the config with input from nginx_realm_input
sub nginx_realm_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
if ($in->{$name."_def"} == 1) {
&save_directive($parent, $name, [ ]);
}
elsif ($in->{$name."_def"} == 2) {
&save_directive($parent, $name, [ "off" ]);
}
else {
my $v = $in->{$name};
$v eq '' && &error(&text('opt_missing', $text{'opt_'.$name}));
&save_directive($parent, $name, [ $v ]);
}
}
# nginx_passfile_input(name, &parent, server-id, path)
# Returns HTML for a password file field
sub nginx_passfile_input
{
my ($name, $parent, $id, $path) = @_;
my $value = &find_value($name, $parent);
my $edit;
if ($value =~ /^\/\S/) {
$edit = " <a href='list_users.cgi?file=".&urlize($value).
"&id=".&urlize($id)."&path=".&urlize($path)."'>".
$text{'access_edit'}."</a>";
}
return &nginx_opt_input($name, $parent, 50, $text{'access_pfile'},
&file_chooser_button($name).$edit);
}
# nginx_passfile_parse(name, &parent, &in)
# Parse input from nginx_passfile_input
sub nginx_passfile_parse
{
my ($name, $parent, $in) = @_;
$in ||= \%in;
$in->{$name."_def"} || &can_directory($in->{$name}) ||
&error(&text('access_ecannot',
"<tt>".&html_escape($in->{$name})."</tt>",
"<tt>".&html_escape($access{'root'})."</tt>"));
&nginx_opt_parse($name, $parent, $in, undef,
sub { return $_[0] !~ /^\// ? $text{'access_eabsolute'} :
-d $_[0] ? $text{'access_edir'} : undef });
}
# nginx_rewrite_input(name, &parent)
# Returns HTML for setting rewrite directives
sub nginx_rewrite_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my @obj = &find($name, $parent);
my $table = &ui_columns_start([ $text{'rewrite_from'},
$text{'rewrite_to'},
$text{'rewrite_flag'} ], 100, 0,
[ "nowrap", "nowrap" ]);
my $i =0;
foreach my $o (@obj, { }, { }) {
$table .= &ui_columns_row([
&ui_textbox($name."_from_$i", $o->{'words'}->[0], 30),
&ui_textbox($name."_to_$i", $o->{'words'}->[1], 40),
&ui_select($name."_flag_$i", $o->{'words'}->[2],
[ map { [ $_, $text{'rewrite_'.$_} ] }
('last', 'break', 'redirect', 'permanent') ]),
]);
$i++;
}
$table .= &ui_columns_end();
return &ui_table_row($text{'opt_'.$name}, $table, 3);
}
# nginx_rewrite_parse(name1, name2, &parent, &in)
# Parse inputs from nginx_rewrite_input
sub nginx_rewrite_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
my @obj;
for(my $i=0; defined(my $from = $in->{$name."_from_".$i}); $i++) {
next if (!$from);
$from =~ /^\S+$/ || &error(&text('rewrite_efrom', $i+1));
my $to = $in->{$name."_to_".$i};
$to =~ /^\S+$/ || &error(&text('rewrite_eto', $i+1));
my $flag = $in->{$name."_flag_".$i};
push(@obj, { 'name' => $name,
'words' => [ $from, $to, $flag ] });
}
&save_directive($parent, $name, \@obj);
}
# list_log_formats([&server])
# Returns a list of all log format names
sub list_log_formats
{
my ($server) = @_;
my $parent = &get_config_parent();
my @rv = ( "combined" );
my $http = &find("http", $parent);
foreach my $l (&find("log_format", $http)) {
push(@rv, $l->{'words'}->[0]);
}
if ($server && $server->{'name'} eq 'server') {
foreach my $l (&find("log_format", $server)) {
push(@rv, $l->{'words'}->[0]);
}
}
return &unique(@rv);
}
# is_nginx_running()
# Returns the PID if nginx is running
sub is_nginx_running
{
my $parent = &get_config_parent();
my $pidfile = &find_value("pid", $parent);
$pidfile ||= &get_default("pid");
$pidfile ||= $config{'pid_file'};
if ($pidfile =~ /^\//) {
return &check_pid_file($pidfile);
}
else {
my ($pid) = &find_byname("nginx");
return $pid;
}
}
# stop_nginx()
# Attempt to stop nginx, return an error on failure or undef on success
sub stop_nginx
{
my $out = &backquote_logged("$config{'stop_cmd'} 2>&1 </dev/null");
return $? ? $out : undef;
}
# start_nginx()
# Attempt to start nginx, return an error on failure or undef on success
sub start_nginx
{
my $out = &backquote_logged("$config{'start_cmd'} 2>&1 </dev/null");
return $? ? $out : undef;
}
# apply_nginx()
# Attempt to apply the nginx config, return an error on failure or undef
# on success
sub apply_nginx
{
my $out = &backquote_logged("$config{'apply_cmd'} 2>&1 </dev/null");
return $? ? $out : undef;
}
# nginx_action_links()
# Returns HTML for service actions to put in the page header
sub nginx_action_links
{
my $args = "redir=".&urlize(&this_url());
my @rv;
if (&is_nginx_running()) {
if ($access{'stop'}) {
push(@rv, &ui_link("stop.cgi?$args", $text{'index_stop'}));
}
push(@rv, &ui_link("restart.cgi?$args", $text{'index_restart'}));
}
elsif ($access{'stop'}) {
push(@rv, &ui_link("start.cgi?$args", $text{'index_start'}));
}
return join("<br>\n", @rv);
}
# this_url()
# Returns the current module URL
sub this_url
{
my $url = $ENV{'SCRIPT_NAME'};
$url .= "?$ENV{'QUERY_STRING'}"
if (defined($ENV{'QUERY_STRING'}) && $ENV{'QUERY_STRING'} ne "");
return $url;
}
# test_config()
# Returns an error message if the config is invalid
sub test_config
{
&clean_language() if (defined(&clean_language));
my $out = &backquote_logged("$config{'nginx_cmd'} -t 2>&1 </dev/null");
&reset_environment() if (defined(&clean_language));
return $? || $out !~ /syntax\s+is\s+ok/ ? $out : undef;
}
# find_server(id)
# Convenience function to find an HTTP server object with some ID
sub find_server
{
my ($id) = @_;
my $conf = &get_config();
my $http = &find("http", $conf);
return undef if (!$http);
my @servers = &find("server", $http);
my ($idname, $idrootdir) = split(/;/, $id);
foreach my $s (@servers) {
my $name = &find_value("server_name", $s);
next if ($idname ne $name);
my $rootdir = &find_value("root", $s);
if (!$rootdir) {
my @locs = &find("location", $s);
my ($rootloc) = grep { $_->{'value'} eq '/' } @locs;
$rootdir = $rootloc ? &find_value("root", $rootloc) : "";
}
next if ($idrootdir ne $rootdir);
return $s;
}
return undef;
}
# server_id(&server)
# Given a server, return a unique ID for it as used by the module
sub server_id
{
my ($s) = @_;
my $name = &find_value("server_name", $s);
my $rootdir = &find_value("root", $s);
if (!$rootdir) {
my @locs = &find("location", $s);
my ($rootloc) = grep { $_->{'value'} eq '/' } @locs;
if ($rootloc) {
$rootdir = &find_value("root", $rootloc);
}
$rootdir ||= "";
}
return $name.";".$rootdir;
}
# find_location(&server, path)
# Finds the location with some path in a given server object
sub find_location
{
my ($server, $path) = @_;
foreach my $l (&find("location", $server)) {
return $l if (&location_path($l) eq $path);
}
return undef;
}
# location_path(&location)
# Returns the URL path or pattern from a location block
sub location_path
{
my ($location) = @_;
my @w = @{$location->{'words'}};
return @w ? $w[$#w] : "";
}
# split_ip_port(string)
# Given an ip:port pair as used in a listen directive, split them up
sub split_ip_port
{
my ($l) = @_;
if ($l =~ /^\d+$/) {
return (undef, $l);
}
elsif ($l =~ /^\[(\S+)\]:(\d+)$/) {
return ($1, $2);
}
elsif ($l =~ /^\[(\S+)\]$/) {
return ($1, 80);
}
elsif ($l =~ /^(\S+):(\d+)$/) {
return ($1, $2);
}
else {
return ($l, 80);
}
}
# server_desc(&server)
# Returns a description of a server block
sub server_desc
{
my ($server) = @_;
my $name = &find_value("server_name", $server);
return $name ? &text('server_desc', "<tt>".&html_escape($name)."</tt>")
: $text{'server_descnone'};
}
# is_default_server_block(&server)
# Returns 1 if a server block is the package default/catch-all server
sub is_default_server_block
{
my ($server) = @_;
my $name = &find_value("server_name", $server);
return 1 if (!$name || $name eq "_" || $name eq "-");
return 0;
}
# location_desc(&server, &location)
# Returns a description of a location in a server block
sub location_desc
{
my ($server, $location) = @_;
my $name = &find_value("server_name", $server);
my $path = &location_path($location);
return $name ? &text('location_desc', "<tt>".&html_escape($name)."</tt>",
"<tt>".&html_escape($path)."</tt>")
: &text('location_descnone',
"<tt>".&html_escape($path)."</tt>");
}
# match_desc(string)
# Converts a location match type like ~ into a human-readable mode
sub match_desc
{
my ($m) = @_;
return $m eq "=" ? $text{'match_exact'} :
$m eq "~" ? $text{'match_case'} :
$m eq "~*" ? $text{'match_nocase'} :
$m eq "^~" ? $text{'match_noregexp'} :
$m eq "\@" ? $text{'match_named'} :
$m eq "" ? $text{'match_default'} :
"Unknown match type $m";
}
sub list_match_types
{
return ("", "=", "~", "~*", "^~", "\@");
}
# create_server_link(&server)
# Creates a link from a directory like sites-enabled to sites-available for
# a new server block
sub create_server_link
{
my ($server) = @_;
if ($config{'add_link'}) {
my $link = $server->{'file'};
$link =~ s/^.*\///;
$link = $config{'add_link'}."/".$link;
&symlink_logged($server->{'file'}, $link);
}
}
# delete_server_link(&server)
# Deletes the link from a directory like sites-enabled to sites-available for
# a server block being removed
sub delete_server_link
{
my ($server) = @_;
if ($config{'add_link'}) {
my $file = $server->{'file'};
my $short = $file;
$short =~ s/^.*\///;
opendir(LINKDIR, $config{'add_link'});
foreach my $f (readdir(LINKDIR)) {
if ($f ne "." && $f ne ".." &&
(&resolve_links($config{'add_link'}."/".$f) eq $file ||
$short eq $f)) {
&unlink_logged($config{'add_link'}."/".$f);
}
}
closedir(LINKDIR);
}
}
# delete_server_file_if_empty(&server)
# If the file for a server is empty, delete it
sub delete_server_file_if_empty
{
my ($server) = @_;
my $lref = &read_file_lines($server->{'file'}, 1);
my $count = 0;
foreach my $l (@$lref) {
$count++ if ($l =~ /\S/);
}
if (!$count) {
&unlink_logged($server->{'file'});
}
}
# valid_cert_file(filename)
# Returns an error message if a cert file is invalid, or undef if OK
sub valid_cert_file
{
my ($file) = @_;
-r $file && !-d $file || return $text{'ssl_ecertfile'};
my $data = &read_file_contents($file);
my @lines = grep { /\S/ } split(/\r?\n/, $data);
my $begin = "-----BEGIN CERTIFICATE-----";
my $end = "-----END CERTIFICATE-----";
$data =~ /$begin/ ||
return &text('ssl_ecertbegin', "-----BEGIN CERTIFICATE-----");
$data =~ /$end/ ||
return &text('ssl_ecertend', "-----END CERTIFICATE-----");
for(my $i=0; $i<@lines; $i++) {
$lines[$i] =~ /^-----(BEGIN|END)/ ||
$lines[$i] =~ /^[A-Za-z0-9\+\/=]+$/ ||
return &text('ssl_ecertline', $i+1);
}
@lines > 4 || return &text('ssl_ecertlines', scalar(@lines));
return undef;
}
# valid_key_file(filename)
# Returns an error message if a key file is invalid, or undef if OK
sub valid_key_file
{
my ($file) = @_;
-r $file && !-d $file || return $text{'ssl_ekeyfile'};
my $data = &read_file_contents($file);
my @lines = grep { /\S/ } split(/\r?\n/, $data);
my $begin = "-----BEGIN (RSA )?PRIVATE KEY-----";
my $end = "-----END (RSA )?PRIVATE KEY-----";
$data =~ /$begin/ ||
return &text('ssl_ekeybegin', "-----BEGIN PRIVATE KEY-----");
$data =~ /$end/ ||
return &text('ssl_ekeyend', "-----END PRIVATE KEY-----");
for(my $i=0; $i<@lines; $i++) {
$lines[$i] =~ /^-----(BEGIN|END)/ ||
$lines[$i] =~ /^[A-Za-z0-9\+\/=]+$/ ||
return &text('ssl_ekeyline', $i+1);
}
@lines > 4 || return &text('ssl_ekeylines', scalar(@lines));
return undef;
}
# can_edit_server(&server)
# Returns 1 if some server can be managed
sub can_edit_server
{
my ($server) = @_;
return 1 if (!$access{'vhosts'});
my $name = &find_value("server_name", $server);
return 0 if (!$name);
return &indexoflc($name, split(/\s+/, $access{'vhosts'})) >= 0;
}
# can_directory(dir)
# Check if some directory is under one of the allowed roots
sub can_directory
{
my ($dir) = @_;
foreach my $root (split(/\s+/, $access{'root'})) {
return 1 if (&is_under_directory($root, $dir));
}
return 0;
}
# switch_write_user(mode)
# If mode is 1, switch to another user for writing password files.
# If 0, switch back to root.
sub switch_write_user
{
my ($mode) = @_;
return if ($access{'user'} eq 'root');
if ($mode) {
my @uinfo = getpwnam($access{'user'});
@uinfo || &error("Write user $access{'user'} does not exist!");
$) = $uinfo[3]." ".join(" ", $uinfo[2], &other_groups($uinfo[0]));
$> = $uinfo[2];
}
else {
$) = 0;
$> = 0;
}
}
1;