Compare commits

...

10 Commits

Author SHA1 Message Date
Ilia Ross
15e7c5ea40 Make the function and hash names clearer 2025-08-25 22:31:54 +03:00
Ilia Ross
f5b3e5ebc4 Drop param that was obsolete for ages 2025-08-25 12:48:55 +03:00
Ilia Ross
9c050ad764 Fix mapped supported option to be saved correctly 2025-08-25 12:17:23 +03:00
Ilia Ross
4f4a3feaca Fix not to show yet pages that do not have support in Dovecot 2.4 2025-08-25 12:12:19 +03:00
Ilia Ross
0573074cac Fix to use mapped API specifically for known options, without magic 2025-08-24 23:00:23 +03:00
Ilia Ross
863e178c7b Add a proper implementation for handling Dovecot version chaos 2025-08-22 16:22:48 +03:00
Ilia Ross
6de9014357 Revert to original code for now 2025-08-22 14:56:12 +03:00
Ilia Ross
f03f7dcf44 Fix to simplify variable name 2025-08-22 14:52:46 +03:00
Ilia Ross
d9295e0f30 Fix not to hardcode module name 2025-08-21 12:31:52 +03:00
Ilia Ross
acf0cf9ca0 Add support for Dovecot 2.4 in API 2025-08-21 00:29:10 +03:00
7 changed files with 266 additions and 33 deletions

View File

@@ -11,6 +11,12 @@ use WebminCore;
@mail_envs = ( undef, "maildir:~/Maildir", "mbox:~/mail/:INBOX=/var/mail/%u",
"maildir:~/Maildir:mbox:~/mail/" );
# Dovecot version specific mapping if any
our %dovecot;
our $version = &get_dovecot_based_version();
require "$module_root_directory/$module_name$version-lib.pl"
if (-r "$module_root_directory/$module_name$version-lib.pl");
# get_config_file()
# Returns the full path to the first valid config file
sub get_config_file
@@ -521,7 +527,7 @@ else {
# Returns 'Default (value)' for some config
sub getdef
{
local $def = &find_value($_[0], &get_config(), 1);
local $def = &find_value_mapped($_[0], &get_config(), 1);
if (defined($def)) {
local $map;
if ($_[1]) {
@@ -545,6 +551,15 @@ local $out = &backquote_command("$config{'dovecot'} --version 2>&1");
return $out =~ /([0-9\.]+)/ ? $1 : undef;
}
# get_dovecot_based_version()
# Returns the dovecot version number without the patch level and without dots
sub get_dovecot_based_version
{
my $base_version = &get_dovecot_version();
$base_version = $base_version =~ /^(\d)\.(\d)/ ? $1.$2 : '';
return $base_version;
}
# version_atleast(ver)
# Returns 1 if running at least some version or above
sub version_atleast
@@ -595,5 +610,67 @@ else {
}
}
# find_mapped(name, &conf, [mode], [sectionname], [sectionvalue], [first])
# Version aware read
sub find_mapped
{
return &dovecot_shim('find', @_)
if defined &dovecot_shim && !$dovecot_shims{main};
return &find(@_);
}
# find_value_mapped(name, &config, [disabled-mode], [sectionname], [sectionvalue])
# Version aware value reader
sub find_value_mapped
{
my ($name, $conf, $mode, $sname, $svalue) = @_;
my @rv;
if (defined &dovecot_shim && !$dovecot_shims{main}) {
@rv = &dovecot_shim('find', $name, $conf, $mode, $sname, $svalue, undef);
}
else {
@rv = &find($name, $conf, $mode, $sname, $svalue, undef);
}
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'};
}
}
# save_directive_mapped(&conf, name|&dir, value, [sectionname], [sectionvalue])
# Version aware write
sub save_directive_mapped
{
return &dovecot_shim('save_directive', @_)
if defined &dovecot_shim && !$dovecot_shims{main};
return &save_directive(@_);
}
# save_section_mapped(&conf, &section)
# Version aware section update
sub save_section_mapped
{
return &dovecot_shim('save_section', @_)
if defined &dovecot_shim && !$dovecot_shims{main};
return &save_section(@_);
}
# create_section_mapped(&conf, &section, [&parent], [&before])
# Version aware section create
sub create_section_mapped
{
return &dovecot_shim('create_section', @_)
if defined &dovecot_shim && !$dovecot_shims{main};
return &create_section(@_);
}
1;

150
dovecot/dovecot24-lib.pl Executable file
View File

@@ -0,0 +1,150 @@
# Dovecot 2.4 compatibility shims
# %dovecot_shims
# Dispatch table of 2.4 overrides keyed by sub name
our %dovecot_shims = (
find => \&find_24,
save_directive => \&save_directive_24,
save_section => \&save_section_24,
create_section => \&create_section_24,
);
# dovecot_shim(caller-sub, @args)
# Single entrypoint that routes a call to a version-specific override
sub dovecot_shim
{
my ($sub, @args) = @_;
return if $dovecot_shims{main};
(my $subname = $sub) =~ s/^.*:://;
if (my $thissub = $dovecot_shims{$subname}) {
return $thissub->(@args);
}
my $mainsub = \&{$sub};
local $dovecot_shims{main} = 1;
return $mainsub->(@args);
}
# params([param])
# Simple mapping
sub params
{
my $name = shift;
my %map = (
# Auth
auth_default_realm => 'auth_default_domain',
disable_plaintext_auth => 'auth_allow_cleartext',
# SSL
ssl_ca => 'ssl_server_ca_file',
ssl_ca_file => 'ssl_client_ca_file',
ssl_cert => 'ssl_server_cert_file',
ssl_cert_file => 'ssl_server_cert_file',
ssl_key => 'ssl_server_key_file',
ssl_key_file => 'ssl_server_key_file',
ssl_key_password => 'ssl_server_key_password',
);
return wantarray ? %map : ($map{$name} || $name);
}
# map_find(name)
# Returns lookup key for compatibility
sub map_find
{
my ($name) = @_;
return &params($name);
}
# map_value(name, value)
# Converts values for compatibility
sub map_value
{
my ($name, $value) = @_;
$name = &map_find($name);
if ($name =~ /^(ssl_server_cert_file|ssl_server_key_file|ssl_server_ca_file|ssl_client_ca_file)$/) {
# Drop obsolete "<"
$value =~ s/^\s*<\s*// if defined $value;
}
elsif ($name eq 'auth_allow_cleartext') {
# Flip value
$value = lc($value) eq 'no' ? 'yes' : 'no' if defined $value;
}
return ($name, $value);
}
# map_members(members)
# Converts values to all members of a section
sub map_members
{
my ($members) = @_;
my @members;
for my $m (@{$members || []}) {
my ($n, $v) = &map_value($m->{name}, $m->{value});
my %copy = %$m;
$copy{name} = $n;
$copy{value} = $v;
push(@members, \%copy);
}
return \@members;
}
# find_24(name, &config, [disabled-mode], [sectionname], [sectionvalue], [first])
# Finds mapped or original directive
sub find_24
{
local ($name, $conf, $mode, $sname, $svalue, $first) = @_;
my $req = $name;
$name = &map_find($name);
local $dovecot_shims{main} = 1;
my @rv = &find($name, $conf, $mode, $sname, $svalue, undef);
foreach my $rv (@rv) {
my (undef, $v) = &map_value($req, $rv->{value});
$rv->{value} = $v;
}
return @rv if wantarray;
return $rv[0] if $first;
return $rv[$#rv];
}
# save_directive_24(&conf, name|&dir, value, [sectionname], [sectionvalue])
# Updates mapped or original directive in the config file
sub save_directive_24
{
local ($conf, $name, $value, $sname, $svalue) = @_;
if (ref $name) {
my ($nn, $vv) = &map_value($name->{name}, $value);
$name->{name} = $nn; $value = $vv;
local $dovecot_shims{main} = 1;
return &save_directive($conf, $name, $value, $sname, $svalue);
}
else {
my ($nn, $vv) = &map_value($name, $value);
local $dovecot_shims{main} = 1;
return &save_directive($conf, $nn, $vv, $sname, $svalue);
}
}
# save_section_24(&conf, &section)
# Updates one section in the config file with members mapped
sub save_section_24
{
local ($conf, $section) = @_;
$section->{members} = &map_members($section->{members});
local $dovecot_shims{main} = 1;
return &save_section($conf, $section);
}
# create_section_24(&conf, &section, [&parent], [&before])
# Adds a section to the config file with members mapped
sub create_section_24
{
local ($conf, $section, $parent, $before) = @_;
$section->{members} = &map_members($section->{members});
local $dovecot_shims{main} = 1;
return &create_section($conf, $section, $parent, $before);
}
1;

View File

@@ -14,7 +14,7 @@ print &ui_table_row($text{'login_realms'},
&ui_opt_textbox("realms", $realms, 40, $text{'login_none'}), 3);
# Default authentication realm
$realm = &find_value("auth_default_realm", $conf);
$realm = &find_value_mapped("auth_default_realm", $conf);
print &ui_table_row($text{'login_realm'},
&ui_opt_textbox("realm", $realm, 10, $text{'default'}));

View File

@@ -9,41 +9,41 @@ print &ui_form_start("save_ssl.cgi", "post");
print &ui_table_start($text{'ssl_header'}, "width=100%", 4);
# SSL cert and key files
if (&find_value("ssl_cert", $conf, 2) || &version_atleast("2.2")) {
$cert = &find_value("ssl_cert", $conf, 0, "");
if (&find_value_mapped("ssl_cert", $conf, 2) || &version_atleast("2.2")) {
$cert = &find_value_mapped("ssl_cert", $conf, 0, "");
$cert =~ s/^<//;
}
else {
$cert = &find_value("ssl_cert_file", $conf);
$cert = &find_value_mapped("ssl_cert_file", $conf);
}
print &ui_table_row($text{'ssl_cert'},
&ui_opt_textbox("cert", $cert, 40, &getdef("ssl_cert_file")), 3,
[ undef, "nowrap" ]);
if (&find_value("ssl_key", $conf, 2) || &version_atleast("2.2")) {
$key = &find_value("ssl_key", $conf, 0, "");
if (&find_value_mapped("ssl_key", $conf, 2) || &version_atleast("2.2")) {
$key = &find_value_mapped("ssl_key", $conf, 0, "");
$key =~ s/^<//;
}
else {
$key = &find_value("ssl_key_file", $conf);
$key = &find_value_mapped("ssl_key_file", $conf);
}
print &ui_table_row($text{'ssl_key'},
&ui_opt_textbox("key", $key, 40, &getdef("ssl_key_file")), 3,
[ undef, "nowrap" ]);
# SSL key password
$pass = &find_value("ssl_key_password", $conf);
$pass = &find_value_mapped("ssl_key_password", $conf);
print &ui_table_row($text{'ssl_pass'},
&ui_opt_textbox("pass", $pass, 20, $text{'ssl_prompt'}), 3,
[ undef, "nowrap" ]);
# SSL CA file
if (&find_value("ssl_ca", $conf, 2) || &version_atleast("2.2")) {
$ca = &find_value("ssl_ca", $conf, 0, "");
if (&find_value_mapped("ssl_ca", $conf, 2) || &version_atleast("2.2")) {
$ca = &find_value_mapped("ssl_ca", $conf, 0, "");
$ca =~ s/^<//;
}
else {
$ca = &find_value("ssl_ca_file", $conf);
$ca = &find_value_mapped("ssl_ca_file", $conf);
}
print &ui_table_row($text{'ssl_ca'},
&ui_opt_textbox("ca", $ca, 40,
@@ -51,15 +51,17 @@ print &ui_table_row($text{'ssl_ca'},
[ undef, "nowrap" ]);
# Parameter regen time
$regen = &find_value("ssl_parameters_regenerate", $conf);
print &ui_table_row($text{'ssl_regen'},
&ui_opt_textbox("regen", $regen, 5,
&getdef("ssl_parameters_regenerate")).
" ".$text{'ssl_hours'}, 3);
if ($version < 23) {
$regen = &find_value("ssl_parameters_regenerate", $conf);
print &ui_table_row($text{'ssl_regen'},
&ui_opt_textbox("regen", $regen, 5,
&getdef("ssl_parameters_regenerate")).
" ".$text{'ssl_hours'}, 3);
}
# Disable plaintext passwords when not SSL
@opts = ( [ 'yes', $text{'yes'} ], [ 'no', $text{'no'} ] );
$plain = &find_value("disable_plaintext_auth", $conf);
$plain = &find_value_mapped("disable_plaintext_auth", $conf);
print &ui_table_row($text{'ssl_plain'},
&ui_radio("plain", $plain,
[ @opts,

View File

@@ -33,6 +33,7 @@ if (!&get_config_file()) {
# Show icons for option categories
@pages = ( "net", "login", "mail", "ssl", "manual" );
@pages = grep { $_ ne 'login' && $_ ne 'mail' } @pages unless $version < 24;
@titles = map { $text{$_."_title"} } @pages;
@icons = map { "images/".$_.".gif" } @pages;
@links = map { "edit_".$_.".cgi" } @pages;

View File

@@ -10,7 +10,7 @@ $conf = &get_config();
# Allowed and default realm
&save_directive($conf, "auth_realms",
$in{'realms_def'} ? undef : $in{'realms'});
&save_directive($conf, "auth_default_realm",
&save_directive_mapped($conf, "auth_default_realm",
$in{'realm_def'} ? undef : $in{'realm'});
# Authentication mechanisms

View File

@@ -10,51 +10,54 @@ $conf = &get_config();
# Save SSL cert and key
$in{'cert_def'} || -r $in{'cert'} || $in{'cert'} =~ /^[<>\|]/ ||
&error($text{'ssl_ecert'});
if (&find_value("ssl_cert", $conf, 2) || &version_atleast("2.2")) {
if (&find_value_mapped("ssl_cert", $conf, 2) || &version_atleast("2.2")) {
$in{'cert'} = "<".$in{'cert'} if ($in{'cert'} =~ /^\//);
&save_directive($conf, "ssl_cert",
&save_directive_mapped($conf, "ssl_cert",
$in{'cert_def'} ? undef : $in{'cert'}, "");
}
else {
&save_directive($conf, "ssl_cert_file",
&save_directive_mapped($conf, "ssl_cert_file",
$in{'cert_def'} ? undef : $in{'cert'});
}
$in{'key_def'} || -r $in{'key'} || $in{'key'} =~ /^[<>\|]/ ||
&error($text{'ssl_ekey'});
if (&find_value("ssl_key", $conf, 2) || &version_atleast("2.2")) {
if (&find_value_mapped("ssl_key", $conf, 2) || &version_atleast("2.2")) {
$in{'key'} = "<".$in{'key'} if ($in{'key'} =~ /^\//);
&save_directive($conf, "ssl_key",
&save_directive_mapped($conf, "ssl_key",
$in{'key_def'} ? undef : $in{'key'}, "");
}
else {
&save_directive($conf, "ssl_key_file",
&save_directive_mapped($conf, "ssl_key_file",
$in{'key_def'} ? undef : $in{'key'});
}
# Save SSL CA cert
$in{'ca_def'} || -r $in{'ca'} || $in{'ca'} =~ /^[<>\|]/ ||
&error($text{'ssl_eca'});
if (&find_value("ssl_ca", $conf, 2) || &version_atleast("2.2")) {
if (&find_value_mapped("ssl_ca", $conf, 2) || &version_atleast("2.2")) {
$in{'ca'} = "<".$in{'ca'} if ($in{'ca'} =~ /^\//);
&save_directive($conf, "ssl_ca",
&save_directive_mapped($conf, "ssl_ca",
$in{'ca_def'} ? undef : $in{'ca'}, "");
}
else {
&save_directive($conf, "ssl_ca_file",
&save_directive_mapped($conf, "ssl_ca_file",
$in{'ca_def'} ? undef : $in{'ca'});
}
# Save SSL key password
$in{'pass_def'} || $in{'pass'} =~ /\S/ || &error($text{'ssl_epass'});
&save_directive($conf, "ssl_key_password",
&save_directive_mapped($conf, "ssl_key_password",
$in{'pass_def'} ? undef : $in{'pass'});
$in{'regen_def'} || $in{'regen'} =~ /^\d+$/ || &error($text{'ssl_eregen'});
&save_directive($conf, "ssl_parameters_regenerate",
$in{'regen_def'} ? undef : $in{'regen'});
if ($version < 23) {
$in{'regen_def'} || $in{'regen'} =~ /^\d+$/ ||
&error($text{'ssl_eregen'});
&save_directive($conf, "ssl_parameters_regenerate",
$in{'regen_def'} ? undef : $in{'regen'});
}
&save_directive($conf, "disable_plaintext_auth",
&save_directive_mapped($conf, "disable_plaintext_auth",
$in{'plain'} ? $in{'plain'} : undef);
&flush_file_lines();