Listing email accounts hosted on a Webmin / Virtualmin server


Here's a little script that I found handy to scan Postfix's virtual address table, compare the domains with the ones actually hosted by the system, and tell me what's really going on.

This works great for servers setup with Webmin and Virtualmin, or with plain postfix installs.

See the comments about how the script determines who "we" really are.


# Looks at /etc/postfix/virtual and tells us which of those emails
# are _actually_ hosted by this system, based on whether DNS lookups of
# the domains seem to point to "us"... where "us" is defined as any
# of the IP addresses on any of localhost's interfaces.
# Naturally, this will fail if your system is behind a gateway/firewall,
# because we have no way of probing that gadget to see how connections
# are routed from "The Internet" to us.

# Copyright (c) 2012, William Lindley bill -at- saltriversystems -dot- com
# 2012-06-06

# This script is free software, you may distribute it and/or modify it
# under the same terms as Perl itself.

use Net::DNS;
use Socket qw/inet_aton/;

use IO::Socket;
use IO::Interface qw(:flags);

my $s = IO::Socket::INET->new(Proto => 'udp');
my @interfaces = $s->if_list;
my %local_interfaces;

for my $if (@interfaces) {
    my $flags = $s->if_flags($if);

    if ( ( $flags & IFF_RUNNING ) && 
     !( $flags & IFF_LOOPBACK ) &&
     !( $flags & IFF_NOARP )) {
    $local_interfaces{$if}{address} = $s->if_addr($if);
    $local_interfaces{$s->if_addr($if)}{interface} = $if;


my $r = Net::DNS::Resolver->new;

open VIRTUAL, '<', '/etc/postfix/virtual';

my %domains_hosted;

while (<VIRTUAL>) {
    s/#.*$//;  # Remove after comment
    my ($address, $alias) = split;
    if ($address) {
    if ($alias !~ /@/) { # Only for local addresses (not forwarded)
        my ($name, $domain) = ($address =~ /^([^@]+)@(.+)$/);
        next unless $name;
#        print "[$name]@[$domain] -> [$alias]n";
        $domains_hosted{$domain}{hosted} = 1;

use Data::Dumper;

foreach my $domain (keys %domains_hosted) {

# Liberally borrowed from David Landgren (grinder)'s code at
    my %res;
    my $rr = $r->query( $domain, 'MX' );
    if ($rr) {
    for my $mx( $rr->answer ) {
            if( $mx->type eq 'CNAME' ) {
                my $a_rr = $r->query( $mx->cname, 'A' );
                if( !$a_rr ) {
                    push @{$res{-1}}, { ip => $mx->cname, forw => $r->
                        errorstring, back => 'CNAME' };
                } else {
                    $_->type eq "A"
                        and push @{$res{-1}}, { ip => $mx->cname, forw => $_->address, back => 'CNAME' }
            for( $a_rr->answer );

            next unless $mx->type eq 'MX';

            my $a_rr = $r->query( $mx->exchange, 'A' );

            if( !$a_rr ) {
                push @{$res{$mx->preference ? $mx->preference : 0}}, {
                    ip   => $mx->exchange,
                    forw => $r->errorstring,
                    back => $r->errorstring,

            my @a;
            for my $a( $a_rr->answer ) {
                next unless $a->type eq "A";

                my $ptr_rr = $r->query( join( '.', reverse( split /./ , $a->address )) . '', 'PTR' );
        if ($local_interfaces{$a->address}{interface}) {
                if( !$ptr_rr ) {
                    push @{$res{$mx->preference}}, {
                        ip => $a->address,
                        forw => $mx->exchange,
                        back => $r->errorstring,
                } else {
                    foreach ( $ptr_rr->answer ) {
            if ( $_->type eq 'PTR' ) {
                push @{$res{$mx->preference}}, {
                ip => $a->address,
                forw => lc $mx->exchange,
                back => lc $_->ptrdname,

    $domains_hosted{$domain}{mx} = %res;

# This could be greatly expanded by doing more with the data herein:
# print Dumper(%domains_hosted);

print "These email accounts are actually hosted here:n";

foreach my $domain (sort keys %domains_hosted) {
    next unless $domains_hosted{$domain}{local};
    print $domain . "n";
    foreach my $email (sort keys %{$domains_hosted{$domain}{address}}) {
    print "   ${email}@${domain}n";


Postfix as mail relay for Exchange


Taken from however note that the "your_recipients" file in the postfix, the "plusone_recipients" in the cron job, and the "example_recipients" in the Perl file should probably all be the same. -- wl 200808

This method consists of a simple perl script which uses Net::LDAP to retrieve Active Directory users' "proxyAddresses" which are both primary and secondary SMTP addresses (as opposed to using "mail" which would only retrieve a user's primary SMTP address). Nothing needs to be run on the Active Directory domain controllers; this script requires only TCP port 389 access to your Active Directory domain controllers

The resulting output is in the format: " OK" which then must be postmap(ped).

Add the following to your Postfix 2.0+ to use the relay_recipient_maps feature of Postfix, which will now reject unknown users:

relay_recipient_maps = hash:/etc/postfix/your_recipients

Note: the Exchange domains in question MUST be entered in relay_domains, and NOT in mydestination.

Also note if you would like to prevent Postfix from rejecting with "User unknown in relay recipient table" and would rather Postfix say "User unknown" set show_user_unknown_table_name = no in

I have the script cronned every hour with the following cron job:

cd /etc/postfix ; ./ && postmap plusone_recipients

Conceivably this script can be easily modified to support other LDAP servers by changing the M$-specific "proxyAddresses" search base and output modification.