<commit_before># 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_config()
# Returns a list of dovecot config entries
sub get_config
{
if (!scalar(@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' => $2,
			     'value' => $3,
			     'enabled' => !$1,
			     'section' => 1,
			     'members' => [ ],
			     'indent' => scalar(@sections),
			     'line' => $lnum,
			     'eline' => $lnum,
			     'file' => $file, };
		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;
		$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' => $2,
			       'value' => $3,
			       'enabled' => !$1,
			       'line' => $lnum,
			       'file' => $file, };
		if ($section) {
			$dir->{'sectionname'} = $section->{'name'};
			$dir->{'sectionvalue'} = $section->{'value'};
			push(@{$section->{'members'}}, $dir);
			$section->{'eline'} = $lnum;
			}

		# 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])
# Mode 0=enabled, 1=disabled, 2=both
sub find
{
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[$#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 {
	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 = ref($name) ? $name : &find($name, $conf, 0, $sname, $svalue);
local $newline = ref($name) ? "$name->{'name'} = $value" : "$name = $value";
if ($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($conf, $dir->{'line'}, $dir->{'file'}, -1);
	@$conf = grep { $_ ne $dir } @$conf;
	}
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 comment
		local $lref = &read_file_lines($cmt->{'file'});
		splice(@$lref, $cmt->{'line'}+1, 0, $newline);
		&renumber($conf, $cmt->{'line'}+1, $cmt->{'file'}, 1);
		push(@$conf, { 'name' => $name,
			       'value' => $value,<commit_msg>Update first occurrance of a directive https://www.virtualmin.com/node/25425<commit_after># 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_config()
# Returns a list of dovecot config entries
sub get_config
{
if (!scalar(@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' => $2,
			     'value' => $3,
			     'enabled' => !$1,
			     'section' => 1,
			     'members' => [ ],
			     'indent' => scalar(@sections),
			     'line' => $lnum,
			     'eline' => $lnum,
			     'file' => $file, };
		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;
		$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' => $2,
			       'value' => $3,
			       'enabled' => !$1,
			       'line' => $lnum,
			       'file' => $file, };
		if ($section) {
			$dir->{'sectionname'} = $section->{'name'};
			$dir->{'sectionvalue'} = $section->{'value'};
			push(@{$section->{'members'}}, $dir);
			$section->{'eline'} = $lnum;
			}

		# 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;
	}
return wantarray ? @rv : $first ? $rv[0] : $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 {
	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 = ref($name) ? $name : &find($name, $conf, 0, $sname, $svalue, 1);
local $newline = ref($name) ? "$name->{'name'} = $value" : "$name = $value";
if ($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($conf, $dir->{'line'}, $dir->{'file'}, -1);
	@$conf = grep { $_ ne $dir } @$conf;
	}
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 comment
		local $lref = &read_file_lines($cmt->{'file'});
		splice(@$lref, $cmt->{'line'}+1, 0, $newline);
		&renumber($conf, $cmt->{'line'}+1, $cmt->{'file'}, 1);
		push(@$conf, { 'name' => $name,
			       'value' => $value,
{'fraction_non_alphanumeric': 0.247446571109776, 'fraction_numerical': 0.0072955111149257575, 'mean_word_length': 3.2932940309506264, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 125, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4588357', 'n_tokens_mistral': 5065, 'n_tokens_neox': 4542, 'n_words': 1082}