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