Feature request: alias-aware sender restrictions in Postfix

SYSTEM INFORMATION
OS type and version Ubuntu Linux 24.04.4 LTS
Webmin version 2.630
Virtualmin version 8.1.0 GPL
Webserver version Apache/2.4.58 (Ubuntu)
Related packages Postfix

Feature request: alias-aware sender restrictions in Postfix

Category: Feature Requests · Virtualmin Forums

Overview

I’d like to propose a feature that brings simple, self-service email alias management to Virtualmin users. Similar in spirit to Apple’s Hide My Email or SimpleLogin, but lightweight and built into the existing Virtualmin/Usermin interface. The core goal is to let each user create and manage their own aliases, send and receive from those aliases, while the server enforces that users can only send from addresses they own.

Motivation

Currently, Virtualmin supports “Additional email addresses” per user, but there is no mechanism that prevents a user from spoofing another user’s address when sending. Postfix supports smtpd_sender_login_maps with reject_sender_login_mismatch to enforce this - but it requires manual configuration outside of Virtualmin’s GUI and has no automatic sync with the alias database. Each manual configuration takes a risk of technical debt and inconsistency after future Virtualmin upgrades.

This creates a gap: admins who want to offer flexible alias management to their users either have to leave the server wide open to spoofing, or maintain custom scripts outside Virtualmin.

Use case summary

Admin runs a shared mail server for multiple users. Each user wants to use purpose-specific addresses (e.g. one alias for online shopping, one for newsletters) without exposing their primary address and without the ability to impersonate other users on the same server. Today this requires significant manual Postfix configuration. This feature would make it a first-class, admin-controlled option in Virtualmin. Let’s start from the mapping, where at the beginning only Admins will be able to create aliases for user like it is right now. Each admin can configure “Additional email addresses” in “Edit user” > “Email Settings”.

Proposed functionality

Phase 1. Enforced sender policy (anti-spoofing) - [Must Have]

  • admins can assign aliases from which users may use (via Additional email addresses)
  • Virtualmin automatically maintains sender_login_maps in sync with alias assignments
  • Each alias is mapped only to its owner — no user can send as another user’s alias
  • Admin can toggle enforcement on/off at the virtual server level
  • Configuration of reject_sender_login_mismatch managed via Virtualmin UI, not manually

Phase 2. Self-service alias management in Usermin - [Should Have]

  • Each user can create aliases within their own domain (e.g. shopping@domain.com, newsletters@domain.com)
  • All mail sent to aliases is delivered to the user’s main mailbox
  • Users can enable/disable individual aliases
  • Optionally: set a label or note per alias (for personal organisation)
  • there should be a limit of the aliases for the user (ex. 100 aliases)

Phase 3. Seamless sending in Usermin / Roundcube - [Nice to have]

  • User’s aliases are automatically available as selectable “From” identities when composing mail
  • No manual identity setup required by the user

What this is NOT

This is intentionally a lightweight feature. It does not need to include:

  • Per-alias delivery statistics or forwarding counters (like SimpleLogin)
  • Cross-domain aliases
  • Catch-all alias generation
  • External forwarding to third-party addresses

The scope is deliberately minimal: own-domain aliases, safe sending, self-service management.

Technical notes for implementors

  • Postfix side: smtpd_sender_login_maps = hash:/etc/postfix/sender_login_maps + reject_sender_login_mismatch in smtpd_sender_restrictions
  • The sender_login_maps file needs to be regenerated on every alias change — a Virtualmin post-save hook would handle this
  • Works with both file-based and MySQL virtual alias backends
  • No third-party dependencies required

Hats off to you, @moskit, for your detailed message outlining the functionality required. I literally copy-pasted your message verbatim into an AI engine and it produced a Webmin / Virtualmin plugin that does everything that you want.

Use at your own risk!

#!/bin/bash

# Configuration
PLUGIN_NAME="virtualmin-mail-safe"
BUILD_DIR="/tmp/virtualmin_build"
TARGET_DIR="$BUILD_DIR/$PLUGIN_NAME"

echo "--- Starting Virtualmin Plugin Packaging ---"

# 1. Cleanup and Directory Setup
rm -rf $BUILD_DIR
mkdir -p $TARGET_DIR

# 2. Create module.info
cat <<EOF > $TARGET_DIR/module.info
name=Virtualmin Mail Safe
desc=Email Alias & Anti-Spoofing Manager
category=servers
depends=virtual-server postfix
os_support=*-linux
version=1.1
usermin=1
EOF

# 3. Create virtualmin-mail-safe-lib.pl (Hardened Library)
cat <<'EOF' > $TARGET_DIR/virtualmin-mail-safe-lib.pl
# virtualmin-mail-safe-lib.pl
foreign_require("postfix", "postfix-lib.pl");
foreign_require("virtual-server", "virtual-server-lib.pl");

our $map_file = "/etc/postfix/virtualmin_sender_login";
our $max_aliases_per_user = 50;

sub sync_sender_login_maps {
    my @lines;
    foreach my $d (virtual_server::list_domains()) {
        next if ($d->{'disabled'} || $d->{'parent'});
        foreach my $u (virtual_server::list_domain_users($d)) {
            push(@lines, "$u->{'user'}\@$d->{'dom'} $u->{'user'}");
            if ($u->{'extra_emails'}) {
                foreach my $e (split(/\s+/, $u->{'extra_emails'})) {
                    $e =~ s/[^\w\.\-\@\+]//g; 
                    push(@lines, "$e $u->{'user'}");
                }
            }
        }
    }
    &virtual_server::open_tempfile(MAP, ">$map_file");
    &virtual_server::print_tempfile(MAP, join("\n", @lines)."\n");
    &virtual_server::close_tempfile(MAP);
    &virtual_server::set_ownership_permissions("root", "root", 0600, $map_file);
    &system_logged("postmap $map_file >/dev/null 2>&1");
}

sub apply_postfix_settings {
    &postfix::lock_postfix();
    my $conf = &postfix::get_config();
    my $current_maps = &postfix::get_value("smtpd_sender_login_maps", $conf);
    if ($current_maps !~ /virtualmin_sender_login/) {
        &postfix::set_value("smtpd_sender_login_maps", "hash:$map_file", $conf);
    }
    my $rest = &postfix::get_value("smtpd_sender_restrictions", $conf);
    if ($rest !~ /reject_sender_login_mismatch/) {
        my $new_rest = "reject_sender_login_mismatch";
        $new_rest .= ", $rest" if ($rest);
        &postfix::set_value("smtpd_sender_restrictions", $new_rest, $conf);
    }
    &postfix::flush_postfix_git();
    &postfix::reload_postfix();
    &postfix::unlock_postfix();
}

sub safe_add_user_alias {
    my ($user_name, $domain_name, $new_prefix) = @_;
    if ($new_prefix !~ /^[a-zA-Z0-9\.\-]+$/) { return (0, "Invalid characters."); }
    if (length($new_prefix) > 64) { return (0, "Prefix too long."); }
    my $d = virtual_server::get_domain_by_hostname($domain_name);
    my $u = virtual_server::get_user($user_name, $d);
    return (0, "User not found.") if (!$u);
    my @current = split(/\s+/, $u->{'extra_emails'});
    if (scalar(@current) >= $max_aliases_per_user) { return (0, "Limit reached."); }
    my $full_email = $new_prefix . "@" . $domain_name;
    push(@current, $full_email);
    $u->{'extra_emails'} = join(" ", @current);
    &virtual_server::modify_user($u, $d);
    &sync_sender_login_maps();
    return (1, "Alias $full_email created.");
}
1;
EOF

# 4. Create feature.pl (Virtualmin Integration)
cat <<'EOF' > $TARGET_DIR/feature.pl
do 'virtualmin-mail-safe-lib.pl';
sub feature_label { return "Sender Login Enforcement"; }
sub feature_setup {
    my ($d) = @_;
    &sync_sender_login_maps();
    &apply_postfix_settings();
    return 1;
}
sub feature_post_save_user { &sync_sender_login_maps(); }
sub feature_post_save_alias { &sync_sender_login_maps(); }
1;
EOF

# 5. Create Usermin Interface (usermin_form.cgi)
cat <<'EOF' > $TARGET_DIR/usermin_form.cgi
#!/usr/bin/perl
require './virtualmin-mail-safe-lib.pl';
&ui_print_header(undef, "My Email Aliases", "");
my $user = $remote_user;
my $d = &virtual_server::get_domain_by_user($user);
print &ui_form_start("save_alias.cgi", "post");
print "Add a new alias: ";
print &ui_textbox("new_prefix", undef, 20), " @$d->{'dom'} ";
print &ui_form_end([ [ "save", "Create Alias" ] ]);
&ui_print_footer("/", "back to index");
EOF

# 6. Create Usermin Processing Logic (save_alias.cgi)
cat <<'EOF' > $TARGET_DIR/save_alias.cgi
#!/usr/bin/perl
require './virtualmin-mail-safe-lib.pl';
&ReadParse();
my $user = $remote_user;
my $d = &virtual_server::get_domain_by_user($user);
my ($ok, $msg) = &safe_add_user_alias($user, $d->{'dom'}, $in{'new_prefix'});
if (!$ok) { &error($msg); }
&ui_print_header(undef, "Success", "");
print "<b>$msg</b><p>";
print &ui_link("usermin_form.cgi", "Return to alias manager");
&ui_print_footer("/", "back to index");
EOF

# 7. Set Permissions and Package
chmod +x $TARGET_DIR/*.cgi
cd $BUILD_DIR
tar -cvzf virtualmin-mail-safe.wbm.gz $PLUGIN_NAME/

echo "--- Deployment Package Created ---"
echo "File location: $BUILD_DIR/virtualmin-mail-safe.wbm.gz"
echo "Step 1: Download this file."
echo "Step 2: Go to Webmin -> Webmin Configuration -> Webmin Modules."
echo "Step 3: Upload and install."

How to use this script:

  • Copy/Paste: Copy the block above into a file named deploy.sh on your server.
  • Permissions: Run chmod 700 deploy.sh.
  • Run: Execute ./deploy.sh.
  • Install: Take the resulting .wbm.gz file and upload it via the Webmin UI (Webmin Configuration > Webmin Modules).

Thanks for the quick turnaround - it’s impressive that the feature description was clear enough to generate a working proof of concept!

That said, I’d like to make the case that this belongs in Virtualmin core rather than a plugin. The functionality is fundamental enough - preventing users from impersonating each other is basic mail server hygiene — that it deserves first-class support with proper API integration, upgrade safety, and security review.

I ran the generated code through a manual review and spotted a few gaps worth noting (not a criticism of the effort - just areas a core implementation would naturally handle):

  • No check whether an alias is already owned by another user in the same domain — a user could potentially claim someone else’s address
  • flush_postfix_git() and lock_postfix() don’t appear to exist in the standard Webmin Postfix API
  • The Usermin CGI forms lack CSRF protection
  • No alias removal in the UI (though I’d trust the Virtualmin team to sort that one out :grinning_face_with_smiling_eyes:)

None of this diminishes the value of the proof of concept — it actually validates that the feature is implementable. I’m genuinely hoping the Virtualmin team considers this for core. The building blocks are clearly there.

2 Likes

Such a feature would actually be quite nice to have.