# 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 $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 {$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)" : "")."
" ], [ 0, $text{'logs_file'} ] ])." ". &ui_textbox($name, $obj ? $obj->{'words'}->[0] : undef, 40)." ". $text{'logs_level'}." ". &ui_select($name."_level", $obj ? $obj->{'words'}->[1] : "", [ [ "", "<$text{'default'}>" ], "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)" : "")."
" ], [ 2, $text{'logs_disabled'}."
" ], [ 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] : "", [ [ "", "<$text{'default'}>" ], &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)" : "")."
" ], [ 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'}."
" ] ])." ". &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, [®ex], [&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'}, [ [ "", " " ], [ "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 = " ". $text{'access_edit'}.""; } 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', "".&html_escape($in->{$name})."", "".&html_escape($access{'root'})."")); &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 &1 &1 \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 {'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', "".&html_escape($name)."") : $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', "".&html_escape($name)."", "".&html_escape($path)."") : &text('location_descnone', "".&html_escape($path).""); } # 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;