Unique IP per POP3/IMAP domain (assume domain)?

In Virtualmin, for mail, my users are like:

sales@some-domain.com
sales@some-other-domain.com

If I give each domain its own IP address, can I set each to assume respective domain based on the IP that is being accessed (so that a user’s mail client can go to “mail.some-domain.com” and just enter “sales” for for the login instead of the whole email address)?

Thanks.

SYSTEM INFORMATION
OS type and version Ubuntu 24.04.2
Virtualmin version 7.30.8

I have what I think is a working solution for this in case it ever comes up for anyone (or for me when I’ve forgotten this). No warranty is implied. I do not know if this is a good way to do this, etc, but would like to hear why not (and better ones).

This will rely on a file you make, so:

sudo nano /etc/dovecot/ip_to_domain.txt

Which will be formatted thusly:

81.16.1.72 oneexample.com
10.221.14.9 anotherexample.com

(If there is no domain name provided in the login and there is no IP to domain match for the IP they’re connected to, the script will make a log like:

ip2domain: Lua passdb: no mapping for lip=81.12.109.165; leaving 'ronnie' unchanged

And then standard disconnection with a failed login lines.)

So we’ll tell 10-auth.conf that we’re going to use a .lua script during authentication:

sudo nano /etc/dovecot/conf.d/10-auth.conf

The first two lines of the below will probably be there already, there will be other lines and those need to be commented out and the rest of these added, these should be the only ones active when you’re done.

disable_plaintext_auth = no
auth_mechanisms = plain login
passdb {
  driver = lua
  args = file=/etc/dovecot/ip2domain.lua
  result_success = continue-fail
  result_failure = continue
}
passdb {
  driver = pam
}
userdb {
  driver = passwd
}
service auth {
    unix_listener /var/spool/postfix/private/auth {
        mode = 0660
        user = postfix
        group = postfix
    }
}

There’s a ton of commented lines in that file so you can check that it matches like this:

cat /etc/dovecot/conf.d/10-auth.conf | grep -vE '#|^$'

Then:

sudo systemctl restart dovecot

Now add these to the bottom of (telling smtpd to use dovecot’s authentication with some parameters):

sudo nano /etc/postfix/main.cf
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unknown_sender_domain

I have some other stuff going on (with milter greylisting and such) so I am not sure if this is otherwise all defaults (the main thing would be that you don’t already have something set for those three things, if so, comment yours) but here is a slightly anonymized version of mine for reference:

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 3.6
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level = may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level = dane
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = testmail001.myrealdomain.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, testmail001.myrealdomain.com, localhost.myrealdomain.com, , localhost
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 54.6.192.3
mailbox_command = /usr/bin/procmail-wrapper -o -a $DOMAIN -d $LOGNAME
mailbox_size_limit = 0
recipient_delimiter = +
inet_protocols = all
virtual_alias_maps = hash:/etc/postfix/virtual
sender_bcc_maps = hash:/etc/postfix/bcc
sender_dependent_default_transport_maps = hash:/etc/postfix/dependent
home_mailbox = Maildir/
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
smtp_dns_support_level = dnssec
smtp_host_lookup = dns
allow_percent_hack = no
resolve_dequoted_address = no
milter_default_action = accept
smtpd_milters = inet:127.0.0.1:8694,local:/var/run/milter-greylist/milter-greylist.sock
non_smtpd_milters = inet:127.0.0.1:8694,local:/var/run/milter-greylist/milter-greylist.sock
tls_server_sni_maps = hash:/etc/postfix/sni_map
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unknown_sender_domain
sudo systemctl restart postfix 

Last we’ll need to make that .lua script I mentioned:

sudo nano /etc/dovecot/ip2domain.lua

-- /etc/dovecot/ip2domain.lua

local mapping_file_path = "/etc/dovecot/ip_to_domain.txt"

local function load_ip_to_domain_mapping()
  local ip_to_domain = {}
  local file = io.open(mapping_file_path, "r")
  if not file then return ip_to_domain end
  for line in file:lines() do
    line = line:match("^%s*(.-)%s*$")
    if line ~= "" and not line:match("^%s*#") then
      local ip, domain = line:match("^(%S+)%s+(%S+)$")
      if ip and domain then ip_to_domain[ip] = domain end
    end
  end
  file:close()
  return ip_to_domain
end

local ip_to_domain = load_ip_to_domain_mapping()
local default_domain = "default.com"  -- or nil to force-fail when unmapped

function auth_password_verify(req)
  local lip = req.lip or req.local_ip
  local login_user = req.user or ""

  -- If client already sent a domain, keep it and continue to PAM
  if login_user:find("@", 1, true) then
    return dovecot.auth.PASSDB_RESULT_OK, { user = login_user }
  end

  -- No domain provided: map from listening IP
  local domain = lip and ip_to_domain[lip]
  if not domain then
    log_message(("Lua passdb: no mapping for lip=%s; leaving '%s' unchanged"):format(tostring(lip), login_user))
    -- Still return OK+table so Dovecot doesn't error; PAM will decide auth
    return dovecot.auth.PASSDB_RESULT_OK, { user = login_user }
  end

  local mapped = login_user .. "@" .. domain
  log_message(("Lua passdb: mapping '%s' -> '%s' (lip=%s)"):format(login_user, mapped, tostring(lip)))

  -- Return OK with the mapped user; passdb policy will force continuation
  return dovecot.auth.PASSDB_RESULT_OK, { user = mapped }
end


function script_init() return 0 end
function script_deinit() end

function log_message(msg)
  local safe = msg:gsub("'", "'\\''")  -- basic shell-escape single quotes
  os.execute(string.format("/usr/bin/logger -p mail.info -t ip2domain '%s'", safe))
end
chmod +x /etc/dovecot/ip2domain.lua

Now logins with the domain included work normally, but logins without a domain are checked against /etc/dovecot/ip_to_domain.txt and if there is a domain associated with the IP that the user has connected to the login name is automatically appended with that domain and the authentication proceeds.

ip2domain: Lua passdb: mapping 'ronnie' -> 'ronnie@anotherexample.com' (lip=10.221.14.9)

You can test, in principle, this way:

doveadm auth test -x service=smtp -x lip=[the-ip-you-want-to-be-attaching-to] [login-name] [password]

(no brackets on any of those) you can compare by using [login-name@domain.com].

(Works for POP/IMAP/SMTP.)

As I mentioned, if this is glaringly a bad idea, please let me know.

For the record there is overhead involved in launching the .lua script, by which I mean that the process takes longer (and so is inefficient and probably not ideal to scale massively). Users should be encouraged to put their full email address as their login name.

Ron

1 Like

Thanks for following up with your solution.

Sorry I missed this when it first came in. I don’t think a dedicated IP is necessary or useful. Why not just use the hostname?

You don’t need dedicated separate IP to allow users to connect to mail.some-domain.com, that’s always possible. On modern systems the services will even use the right TLS certificate.

So…in short, I don’t see anything dangerous about what you’re doing, but I think you’re doing more than you need to (but I haven’t checked to see if you could use a hostname instead of IP, but I don’t know why you wouldn’t be able to).

Are dovecot and postfix aware of what domain you’ve chosen to connect to (in multi-domain systems)?

Like if I connect POP3/IMAP4/SMTP to “domain.com” via ‘whatever some guy uses for mail on his Mac Classic’ do those services know which domain.com I tried? (I had imagined that as a purely IP level connection beginning with “LOGIN”.)

Do all (any?) mail clients send that along? (Presuming, of course, that I have only specified “login” as my login.)

Ron

So…they have to have that information for SNI to work. Whether they make it available to make decisions about logins I don’t know. But, the service definitely knows the hostname (in modern versions that support SNI).

1 Like