mirror of
https://github.com/webmin/webmin.git
synced 2025-07-21 23:40:34 +00:00
582 lines
15 KiB
Perl
Executable File
582 lines
15 KiB
Perl
Executable File
# Functions for parsing the dovecot config file
|
|
|
|
BEGIN { push(@INC, ".."); };
|
|
use WebminCore;
|
|
&init_config();
|
|
|
|
@supported_auths = ( "anonymous", "plain", "digest-md5", "cram-md5", "apop" );
|
|
@mail_envs = ( undef, "maildir:~/Maildir", "mbox:~/mail/:INBOX=/var/mail/%u",
|
|
"maildir:~/Maildir:mbox:~/mail/" );
|
|
|
|
# get_config_file()
|
|
# Returns the full path to the first valid config file
|
|
sub get_config_file
|
|
{
|
|
foreach my $f (split(/\s+/, $config{'dovecot_config'})) {
|
|
return $f if (-r $f);
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# get_add_config_file()
|
|
# Returns the full path to the first valid config file for new top-level
|
|
# directives
|
|
sub get_add_config_file
|
|
{
|
|
foreach my $f (split(/\s+/, $config{'add_config'})) {
|
|
return $f if (-r $f);
|
|
}
|
|
return &get_config_file();
|
|
}
|
|
|
|
# get_config()
|
|
# Returns a list of dovecot config entries
|
|
sub get_config
|
|
{
|
|
if (!@get_config_cache) {
|
|
@get_config_cache = &read_config_file(&get_config_file());
|
|
}
|
|
return \@get_config_cache;
|
|
}
|
|
|
|
# read_config_file(filename, [&include-parent-rv])
|
|
# Convert a file into a list od directives
|
|
sub read_config_file
|
|
{
|
|
local ($file, $incrv) = @_;
|
|
local $filedir = $file;
|
|
$filedir =~ s/\/[^\/]+$//;
|
|
local $lnum = 0;
|
|
local ($section, @sections);
|
|
open(CONF, "<".$file);
|
|
local @lines = <CONF>;
|
|
close(CONF);
|
|
local $_;
|
|
local @rv;
|
|
local $section;
|
|
foreach (@lines) {
|
|
s/\r|\n//g;
|
|
if (/^(\s*(#?)\s*)([a-z0-9\_]+)\s*(\S*)\s*\{\s*$/) {
|
|
# Start of a section .. add this as a value too
|
|
local $oldsection = $section;
|
|
if ($section) {
|
|
push(@sections, $section); # save old
|
|
}
|
|
$section = { 'name' => $3,
|
|
'value' => $4,
|
|
'enabled' => !$2,
|
|
'space' => $1,
|
|
'section' => 1,
|
|
'members' => [ ],
|
|
'indent' => scalar(@sections),
|
|
'line' => $lnum,
|
|
'eline' => $lnum,
|
|
'file' => $file, };
|
|
$section->{'space'} =~ s/#//;
|
|
if ($oldsection) {
|
|
$section->{'sectionname'} =
|
|
$oldsection->{'name'};
|
|
$section->{'sectionvalue'} =
|
|
$oldsection->{'value'};
|
|
}
|
|
push(@rv, $section);
|
|
}
|
|
elsif (/^\s*(#?)\s*}\s*$/ && $section) {
|
|
# End of a section
|
|
$section->{'eline'} = $lnum;
|
|
if (@sections) {
|
|
$section = pop(@sections);
|
|
}
|
|
else {
|
|
$section = undef;
|
|
}
|
|
}
|
|
elsif (/^(\s*)(#?)([a-z0-9\_]+)\s+=\s*(.*)/) {
|
|
# A directive inside a section
|
|
local $dir = { 'name' => $3,
|
|
'value' => $4,
|
|
'enabled' => !$2,
|
|
'space' => $1,
|
|
'line' => $lnum,
|
|
'eline' => $lnum,
|
|
'file' => $file, };
|
|
if ($section) {
|
|
$dir->{'sectionname'} = $section->{'name'};
|
|
$dir->{'sectionvalue'} = $section->{'value'};
|
|
push(@{$section->{'members'}}, $dir);
|
|
$section->{'eline'} = $lnum;
|
|
}
|
|
$dir->{'value'} =~ s/\s+$//;
|
|
|
|
# Fix up references to other variables
|
|
my @w = split(/\s+/, $dir->{'value'});
|
|
my $changed;
|
|
foreach my $w (@w) {
|
|
if ($w =~ /^\$(\S+)/) {
|
|
my $var = $1;
|
|
my ($prev) = grep { $_->{'name'} eq $var } @rv;
|
|
if (!$prev && $incrv) {
|
|
($prev) = grep { $_->{'name'} eq $var }
|
|
@$incrv;
|
|
}
|
|
if ($prev) {
|
|
$w = $prev->{'value'};
|
|
$changed = 1;
|
|
}
|
|
else {
|
|
$w = undef;
|
|
$changed = 1;
|
|
}
|
|
}
|
|
}
|
|
if ($changed) {
|
|
@w = grep { defined($_) } @w;
|
|
$dir->{'value'} = join(" ", @w);
|
|
}
|
|
push(@rv, $dir);
|
|
}
|
|
elsif (/^\s*!(include|include_try)\s+(\S+)/) {
|
|
# Include file(s)
|
|
local $glob = $2;
|
|
if ($glob !~ /^\//) {
|
|
$glob = $filedir."/".$glob;
|
|
}
|
|
foreach my $i (glob($glob)) {
|
|
push(@rv, &read_config_file($i, \@rv));
|
|
}
|
|
}
|
|
$lnum++;
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# find(name, &config, [disabled-mode], [sectionname], [sectionvalue], [first])
|
|
# Mode 0=enabled, 1=disabled, 2=both
|
|
sub find
|
|
{
|
|
local ($name, $conf, $mode, $sname, $svalue, $first) = @_;
|
|
local @rv = grep { !$_->{'section'} &&
|
|
$_->{'name'} eq $name &&
|
|
($mode == 0 && $_->{'enabled'} ||
|
|
$mode == 1 && !$_->{'enabled'} || $mode == 2) } @$conf;
|
|
if (defined($sname)) {
|
|
# If a section was requested, limit to it
|
|
@rv = grep { $_->{'sectionname'} eq $sname &&
|
|
$_->{'sectionvalue'} eq $svalue } @rv;
|
|
}
|
|
if (wantarray) {
|
|
return @rv;
|
|
}
|
|
elsif ($first) {
|
|
return $rv[0];
|
|
}
|
|
else {
|
|
return $rv[$#rv];
|
|
}
|
|
}
|
|
|
|
# find_value(name, &config, [disabled-mode], [sectionname], [sectionvalue])
|
|
# Mode 0=enabled, 1=disabled, 2=both
|
|
sub find_value
|
|
{
|
|
local @rv = &find(@_);
|
|
if (wantarray) {
|
|
return map { $_->{'value'} } @rv;
|
|
}
|
|
elsif (!@rv) {
|
|
return undef;
|
|
}
|
|
else {
|
|
# Prefer the last one that isn't self-referential
|
|
my @unself = grep { $_->{'value'} !~ /\$\Q$name\E/ } @rv;
|
|
@rv = @unself if (@unself);
|
|
return $rv[$#rv]->{'value'};
|
|
}
|
|
}
|
|
|
|
# find_section(name, &config, [disabled-mode], [sectionname], [sectionvalue])
|
|
# Returns a Dovecot config section object
|
|
sub find_section
|
|
{
|
|
local ($name, $conf, $mode, $sname, $svalue) = @_;
|
|
local @rv = grep { $_->{'section'} &&
|
|
$_->{'name'} eq $name &&
|
|
($mode == 0 && $_->{'enabled'} ||
|
|
$mode == 1 && !$_->{'enabled'} || $mode == 2) } @$conf;
|
|
if (defined($sname)) {
|
|
# If a section was requested, limit to it
|
|
@rv = grep { $_->{'sectionname'} eq $sname &&
|
|
$_->{'sectionvalue'} eq $svalue } @rv;
|
|
}
|
|
return wantarray ? @rv : $rv[0];
|
|
}
|
|
|
|
# save_directive(&conf, name|&dir, value, [sectionname], [sectionvalue])
|
|
# Updates one directive in the config file
|
|
sub save_directive
|
|
{
|
|
local ($conf, $name, $value, $sname, $svalue) = @_;
|
|
local $dir;
|
|
if (ref($name)) {
|
|
# Old directive given
|
|
$dir = $name;
|
|
}
|
|
else {
|
|
# Find by name, by prefer those that aren't self-referential
|
|
my @dirs = &find($name, $conf, 0, $sname, $svalue, 1);
|
|
($dir) = grep { $_->{'value'} !~ /\$\Q$name\E/ } @dirs;
|
|
if (!$dir) {
|
|
$dir = $dirs[0];
|
|
}
|
|
}
|
|
local $newline = ref($name) ? "$name->{'name'} = $value" : "$name = $value";
|
|
if ($dir) {
|
|
$newline = $dir->{'space'}.$newline;
|
|
}
|
|
elsif ($sname) {
|
|
$newline = " ".$newline;
|
|
}
|
|
if ($dir && defined($value)) {
|
|
# Updating some directive
|
|
local $lref = &read_file_lines($dir->{'file'});
|
|
$lref->[$dir->{'line'}] = $newline;
|
|
$dir->{'value'} = $value;
|
|
}
|
|
elsif ($dir && !defined($value)) {
|
|
# Deleting some directive
|
|
local $lref = &read_file_lines($dir->{'file'});
|
|
splice(@$lref, $dir->{'line'}, 1);
|
|
&renumber(\@get_config_cache, $dir->{'line'}, $dir->{'file'}, -1);
|
|
my $idx = &indexof($dir, @$conf);
|
|
if ($idx >= 0) {
|
|
splice(@$conf, $idx, 1);
|
|
}
|
|
}
|
|
elsif (!$dir && defined($value)) {
|
|
# Adding some directive .. put it after the commented version, if any
|
|
local $cmt = &find($name, $conf, 1, $sname, $svalue);
|
|
if ($cmt) {
|
|
# After commented version of same directive
|
|
local $lref = &read_file_lines($cmt->{'file'});
|
|
$newline = $cmt->{'space'}.$newline;
|
|
splice(@$lref, $cmt->{'line'}+1, 0, $newline);
|
|
&renumber($conf, $cmt->{'line'}+1, $cmt->{'file'}, 1);
|
|
push(@$conf, { 'name' => $name,
|
|
'value' => $value,
|
|
'enabled' => 1,
|
|
'file' => $cmt->{'file'},
|
|
'line' => $cmt->{'line'}+1,
|
|
'eline' => $cmt->{'line'}+1,
|
|
'sectionname' => $sname,
|
|
'sectionvalue' => $svalue });
|
|
}
|
|
elsif ($sname) {
|
|
# Put at end of section
|
|
local @insect = grep { $_->{'sectionname'} eq $sname &&
|
|
$_->{'sectionvalue'} eq $svalue } @$conf;
|
|
@insect || &error("Failed to find section $sname $svalue !");
|
|
local $lref = &read_file_lines($insect[$#insect]->{'file'});
|
|
local $line = $insect[$#insect]->{'line'}+1;
|
|
$newline = $insect[$#insect]->{'space'}.$newline;
|
|
splice(@$lref, $line, 0, $newline);
|
|
&renumber($conf, $line, $insect[$#insect]->{'file'}, 1);
|
|
push(@$conf, { 'name' => $name,
|
|
'value' => $value,
|
|
'enabled' => 1,
|
|
'file' => $insect[$#insect]->{'file'},
|
|
'line' => $line,
|
|
'eline' => $line,
|
|
'sectionname' => $sname,
|
|
'sectionvalue' => $svalue });
|
|
}
|
|
else {
|
|
# Need to put at end of main config
|
|
local $file = &get_add_config_file();
|
|
local $lref = &read_file_lines($file);
|
|
push(@$lref, $newline);
|
|
push(@$conf, { 'name' => $name,
|
|
'value' => $value,
|
|
'enabled' => 1,
|
|
'file' => $file,
|
|
'line' => scalar(@$lref)-1,
|
|
'eline' => scalar(@$lref)-1,
|
|
'sectionname' => $sname,
|
|
'sectionvalue' => $svalue });
|
|
}
|
|
}
|
|
}
|
|
|
|
# save_section(&conf, §ion)
|
|
# Updates one section in the config file
|
|
sub save_section
|
|
{
|
|
local ($conf, $section) = @_;
|
|
local $lref = &read_file_lines($section->{'file'});
|
|
local $indent = " " x $section->{'indent'};
|
|
local @newlines;
|
|
push(@newlines, $indent.$section->{'name'}." ".$section->{'value'}." {");
|
|
foreach my $m (@{$section->{'members'}}) {
|
|
push(@newlines, $indent." ".$m->{'name'}." = ".$m->{'value'});
|
|
}
|
|
push(@newlines, $indent."}");
|
|
local $oldlen = $section->{'eline'} - $section->{'line'} + 1;
|
|
splice(@$lref, $section->{'line'}, $oldlen, @newlines);
|
|
&renumber($conf, $section->{'eline'}, $section->{'file'},
|
|
scalar(@newlines)-$oldlen);
|
|
$section->{'eline'} = $section->{'line'} + scalar(@newlines) - 1;
|
|
my $i = 1;
|
|
foreach my $m (@{$section->{'members'}}) {
|
|
$m->{'line'} = $m->{'eline'} = $section->{'line'} + $i++;
|
|
$m->{'space'} = " ";
|
|
$m->{'file'} = $section->{'file'};
|
|
$m->{'sectionname'} = $section->{'name'};
|
|
$m->{'sectionvalue'} = $section->{'value'};
|
|
}
|
|
}
|
|
|
|
# create_section(&conf, §ion, [&parent], [&before])
|
|
# Adds a section to the config file
|
|
sub create_section
|
|
{
|
|
local ($conf, $section, $parent, $before) = @_;
|
|
local $indent = " " x $section->{'indent'};
|
|
local @newlines;
|
|
push(@newlines, $indent.$section->{'name'}." ".$section->{'value'}." {");
|
|
foreach my $m (@{$section->{'members'}}) {
|
|
push(@newlines, $indent." ".$m->{'name'}." = ".$m->{'value'});
|
|
}
|
|
push(@newlines, $indent."}");
|
|
my $file;
|
|
my $lref;
|
|
if ($parent) {
|
|
# Add to another config block
|
|
$file = $parent->{'file'};
|
|
$lref = &read_file_lines($file);
|
|
$section->{'line'} = $parent->{'eline'};
|
|
}
|
|
else {
|
|
# Add to the global config file
|
|
$file = &get_config_file();
|
|
$lref = &read_file_lines($file);
|
|
if ($before) {
|
|
# Add before another block
|
|
$section->{'line'} = $before->{'line'};
|
|
}
|
|
else {
|
|
# Add at the end
|
|
$section->{'line'} = scalar(@$lref);
|
|
}
|
|
}
|
|
splice(@$lref, $section->{'line'}, 0, @newlines);
|
|
&renumber($conf, $section->{'eline'}, $section->{'file'},
|
|
scalar(@newlines)-$oldlen);
|
|
$section->{'eline'} = $section->{'line'} + scalar(@newlines) - 1;
|
|
$section->{'file'} = $file;
|
|
my $i = 1;
|
|
foreach my $m (@{$section->{'members'}}) {
|
|
$m->{'line'} = $m->{'eline'} = $section->{'line'} + $i++;
|
|
$m->{'space'} = " ";
|
|
$m->{'indent'} = $section->{'indent'} + 1;
|
|
$m->{'file'} = $section->{'file'};
|
|
$m->{'sectionname'} = $section->{'name'};
|
|
$m->{'sectionvalue'} = $section->{'value'};
|
|
}
|
|
}
|
|
|
|
# renumber(&conf, line, file, offset)
|
|
sub renumber
|
|
{
|
|
my ($conf, $line, $file, $offset) = @_;
|
|
foreach my $c (@$conf) {
|
|
if ($c->{'file'} eq $file) {
|
|
$c->{'line'} += $offset if ($c->{'line'} >= $line);
|
|
$c->{'eline'} += $offset if ($c->{'eline'} >= $line);
|
|
}
|
|
}
|
|
}
|
|
|
|
# renumber_section_and_members(&conffull, file, line, offset, member_sname, member_svalue)
|
|
sub renumber_section_and_members
|
|
{
|
|
my ($conffull, $file, $line, $offset, $member_sname, $member_svalue) = @_;
|
|
my @section = &find_section($sname, $conffull);
|
|
if ($member_sname || $member_svalue) {
|
|
@section = grep {
|
|
($_->{'members'}->[0]->{'sectionname'} eq $member_sname || !defined($member_sname)) &&
|
|
($_->{'members'}->[0]->{'sectionvalue'} eq $member_svalue || !defined($member_svalue)) &&
|
|
$_->{'members'}->[0]->{'file'} eq $file
|
|
} @section;
|
|
}
|
|
&renumber(\@section, $line, $file, $offset);
|
|
}
|
|
|
|
# is_dovecot_running()
|
|
# Returns the PID if the server process is active, undef if not
|
|
sub is_dovecot_running
|
|
{
|
|
# Try the configured PID file first
|
|
local $pid =&check_pid_file($config{'pid_file'});
|
|
return $pid if ($pid);
|
|
|
|
# Look in the base dir
|
|
local $base = &find_value("base_dir", &get_config(), 2);
|
|
return &check_pid_file("$base/master.pid");
|
|
}
|
|
|
|
# stop_dovecot()
|
|
# Attempts to stop the dovecot server process, returning an error message or
|
|
# undef if successful
|
|
sub stop_dovecot
|
|
{
|
|
if ($config{'init_script'}) {
|
|
&foreign_require("init");
|
|
my ($ok, $out) = &init::stop_action($config{'init_script'});
|
|
return $ok ? undef : "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
else {
|
|
local $pid = &is_dovecot_running();
|
|
if ($pid && kill('TERM', $pid)) {
|
|
return undef;
|
|
}
|
|
else {
|
|
return $text{'stop_erunning'};
|
|
}
|
|
}
|
|
}
|
|
|
|
# start_dovecot()
|
|
# Attempts to start the dovecot server process, returning an error message or
|
|
# undef if successful
|
|
sub start_dovecot
|
|
{
|
|
if ($config{'init_script'}) {
|
|
&foreign_require("init");
|
|
my ($ok, $out) = &init::start_action($config{'init_script'});
|
|
return $ok ? undef : "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
else {
|
|
local $cmd = $config{'dovecot'};
|
|
local $temp = &transname();
|
|
&system_logged("$cmd >$temp 2>&1 </dev/null &");
|
|
sleep(1);
|
|
local $out = &read_file_contents($temp);
|
|
&unlink_file($temp);
|
|
return &is_dovecot_running() ? undef : "<pre>$out</pre>";
|
|
}
|
|
}
|
|
|
|
# apply_configration([full-restart])
|
|
# Reload the Dovecot configuration, optionally with a full restart
|
|
sub apply_configuration
|
|
{
|
|
local ($restart) = @_;
|
|
local $pid = &is_dovecot_running();
|
|
if (!$pid) {
|
|
return $text{'stop_erunning'};
|
|
}
|
|
elsif ($restart) {
|
|
# Fully shut down and re-start
|
|
if ($config{'init_script'}) {
|
|
&foreign_require("init");
|
|
my ($ok, $out) = &init::restart_action($config{'init_script'});
|
|
return $ok ? undef : "<pre>".&html_escape($out)."</pre>";
|
|
}
|
|
else {
|
|
&stop_dovecot();
|
|
local $err;
|
|
for(my $i=0; $i<5; $i++) {
|
|
$err = &start_dovecot();
|
|
last if (!$err);
|
|
sleep(1);
|
|
}
|
|
return $err;
|
|
}
|
|
}
|
|
else {
|
|
# Send the HUP signal
|
|
return &kill_logged('HUP', $pid) ? undef : $!;
|
|
}
|
|
}
|
|
|
|
# getdef(name, [&mapping])
|
|
# Returns 'Default (value)' for some config
|
|
sub getdef
|
|
{
|
|
local $def = &find_value($_[0], &get_config(), 1);
|
|
if (defined($def)) {
|
|
local $map;
|
|
if ($_[1]) {
|
|
($map) = grep { $_->[0] eq $def } @{$_[1]};
|
|
}
|
|
if (defined($map)) {
|
|
return "$text{'default'} ($map->[1])";
|
|
}
|
|
elsif ($def) {
|
|
return "$text{'default'} ($def)";
|
|
}
|
|
}
|
|
return $text{'default'};
|
|
}
|
|
|
|
# get_dovecot_version()
|
|
# Returns the dovecot version number, or undef if not available
|
|
sub get_dovecot_version
|
|
{
|
|
local $out = &backquote_command("$config{'dovecot'} --version 2>&1");
|
|
return $out =~ /([0-9\.]+)/ ? $1 : undef;
|
|
}
|
|
|
|
# version_atleast(ver)
|
|
# Returns 1 if running at least some version or above
|
|
sub version_atleast
|
|
{
|
|
local ($wantver) = @_;
|
|
local $ver = &get_dovecot_version();
|
|
return 0 if (!$ver);
|
|
return &compare_version_numbers($ver, $wantver) >= 0;
|
|
}
|
|
|
|
sub list_lock_methods
|
|
{
|
|
local ($forindex) = @_;
|
|
return ( "dotlock", "fcntl", "flock", $forindex ? ( ) : ( "lockf" ) );
|
|
}
|
|
|
|
# lock_dovecot_files([&conf])
|
|
# Lock all files in the Dovecot config
|
|
sub lock_dovecot_files
|
|
{
|
|
local ($conf) = @_;
|
|
$conf ||= &get_config();
|
|
foreach my $f (&unique(map { $_->{'file'} } @$conf)) {
|
|
&lock_file($f);
|
|
}
|
|
}
|
|
|
|
# unlock_dovecot_files([&conf])
|
|
# Release lock on all files
|
|
sub unlock_dovecot_files
|
|
{
|
|
local ($conf) = @_;
|
|
$conf ||= &get_config();
|
|
foreach my $f (reverse(&unique(map { $_->{'file'} } @$conf))) {
|
|
&unlock_file($f);
|
|
}
|
|
}
|
|
|
|
# get_supported_protocols()
|
|
# Returns the list of usable protocols for the current Dovecot version
|
|
sub get_supported_protocols
|
|
{
|
|
if (&get_dovecot_version() >= 2) {
|
|
return ( "imap", "pop3", "lmtp" );
|
|
}
|
|
else {
|
|
return ( "imap", "pop3", "imaps", "pop3s" );
|
|
}
|
|
}
|
|
|
|
1;
|
|
r
|
|
|