From 807a6eeaf9862266561b0e608f06e3241d30c06e Mon Sep 17 00:00:00 2001 From: Mr Sleeps Date: Thu, 11 Jun 2026 16:53:52 +0100 Subject: [PATCH] Added Exim and Dovecot LMTP configs --- .../dovecot/etc/dovecot/conf.d/10-auth.conf | 121 ++++++++++++ .../dovecot/etc/dovecot/conf.d/10-master.conf | 177 +++++++++++++++++ .../dovecot/etc/dovecot/conf.d/20-lmtp.conf | 62 ++++++ .../dovecot/conf.d/auth-vexim-sql.conf.ext | 44 +++++ docs/debian-conf.d/acl/20_vexim_whitelist | 46 +++++ .../router/250_vexim_virtual_domains_lmtp | 179 ++++++++++++++++++ .../transport/30_vexim_dovecot_lmtp | 9 + 7 files changed, 638 insertions(+) create mode 100644 docs/clients/dovecot/etc/dovecot/conf.d/10-auth.conf create mode 100644 docs/clients/dovecot/etc/dovecot/conf.d/10-master.conf create mode 100644 docs/clients/dovecot/etc/dovecot/conf.d/20-lmtp.conf create mode 100644 docs/clients/dovecot/etc/dovecot/conf.d/auth-vexim-sql.conf.ext create mode 100644 docs/debian-conf.d/acl/20_vexim_whitelist create mode 100644 docs/debian-conf.d/router/250_vexim_virtual_domains_lmtp create mode 100644 docs/debian-conf.d/transport/30_vexim_dovecot_lmtp diff --git a/docs/clients/dovecot/etc/dovecot/conf.d/10-auth.conf b/docs/clients/dovecot/etc/dovecot/conf.d/10-auth.conf new file mode 100644 index 00000000..2f20b992 --- /dev/null +++ b/docs/clients/dovecot/etc/dovecot/conf.d/10-auth.conf @@ -0,0 +1,121 @@ +## +## Authentication processes +## + +# Enable LOGIN command and all other plaintext authentications even if +# SSL/TLS is not used (LOGINDISABLED capability). Note that if the remote IP +# matches the local IP (ie. you're connecting from the same computer), the +# connection is considered secure and plaintext authentication is allowed, +# unless ssl = required. +#auth_allow_cleartext = yes + +# Authentication cache size (e.g. 10M). 0 means it's disabled. Note that +# bsdauth, PAM and vpopmail require cache_key to be set for caching to be used. +#auth_cache_size = 0 +# Time to live for cached data. After TTL expires the cached record is no +# longer used, *except* if the main database lookup returns internal failure. +# We also try to handle password changes automatically: If user's previous +# authentication was successful, but this one wasn't, the cache isn't used. +# For now this works only with plaintext authentication. +#auth_cache_ttl = 1 hour +# TTL for negative hits (user not found, password mismatch). +# 0 disables caching them completely. +#auth_cache_negative_ttl = 1 hour + +# Space separated list of realms for SASL authentication mechanisms that need +# them. You can leave it empty if you don't want to support multiple realms. +# Many clients simply use the first one listed here, so keep the default realm +# first. +#auth_realms = +# +# Default realm/domain to use if none was specified. This is used for both +# SASL realms and appending @domain to username in plaintext logins. +#auth_default_domain = + +# List of allowed characters in username. If the user-given username contains +# a character not listed in here, the login automatically fails. This is just +# an extra check to make sure user can't exploit any potential quote escaping +# vulnerabilities with SQL/LDAP databases. If you want to allow all characters, +# set this value to empty. +#auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ + +# Username character translations before it's looked up from databases. The +# value contains series of from -> to characters. For example "#@/@" means +# that '#' and '/' characters are translated to '@'. +#auth_username_translation = + +# Username formatting before it's looked up from databases. +#auth_username_format = %{user|lower} +#auth_username_format = %{user|username|lower} + +# If you want to allow master users to log in by specifying the master +# username within the normal username string (ie. not using SASL mechanism's +# support for it), you can specify the separator character here. The format +# is then . UW-IMAP uses "*" as the +# separator, so that could be a good choice. +#auth_master_user_separator = + +# Username to use for users logging in with ANONYMOUS SASL mechanism +#auth_anonymous_username = anonymous + +# Host name to use in GSSAPI principal names. The default is to use the +# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab +# entries. +#auth_gssapi_hostname = + +# Kerberos keytab to use for the GSSAPI mechanism. Will use the system +# default (usually /etc/krb5.keytab) if not specified. You may need to change +# the auth service to run as root to be able to read this file. +#auth_krb5_keytab = + +# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and +# ntlm_auth helper. +#auth_use_winbind = no + +# Path for Samba's ntlm_auth helper binary. +#auth_winbind_helper_path = /usr/bin/ntlm_auth + +# Time to delay before replying to failed authentications. +#auth_failure_delay = 2 secs + +# Require a valid SSL client certificate or the authentication fails. +#auth_ssl_require_client_cert = no + +# Take the username from client's SSL certificate, using +# X509_NAME_get_text_by_NID() which returns the subject's DN's +# CommonName. +#auth_ssl_username_from_cert = no + +# Space separated list of wanted authentication mechanisms: +# plain login digest-md5 cram-md5 ntlm anonymous gssapi +# gss-spnego xoauth2 oauthbearer +# NOTE: See also auth_allow_cleartext setting. +#auth_mechanisms = plain login + +## +## Password and user databases +## + +# +# Password database is used to verify user's password (and nothing more). +# You can have multiple passdbs and userdbs. This is useful if you want to +# allow both system users (/etc/passwd) and virtual users to login without +# duplicating the system users into virtual database. +# +# +# +# User database specifies where mails are located and what user/group IDs +# own them. For single-UID configuration use "static" userdb. +# +# + +#!include auth-deny.conf.ext +#!include auth-master.conf.ext +#!include auth-oauth2.conf.ext + +#!include auth-system.conf.ext +#!include auth-sql.conf.ext +#!include auth-ldap.conf.ext +#!include auth-passwdfile.conf.ext +#!include auth-static.conf.ext +!include auth-vexim-sql.conf.ext diff --git a/docs/clients/dovecot/etc/dovecot/conf.d/10-master.conf b/docs/clients/dovecot/etc/dovecot/conf.d/10-master.conf new file mode 100644 index 00000000..f25b4ce7 --- /dev/null +++ b/docs/clients/dovecot/etc/dovecot/conf.d/10-master.conf @@ -0,0 +1,177 @@ +## +#i +## LMTP specific settings +## + +# Support proxying to other LMTP/SMTP servers by performing passdb lookups. +#lmtp_proxy = no + +# When recipient address includes the detail (e.g. user+detail), try to save +# the mail to the detail mailbox. See also recipient_delimiter and +# lda_mailbox_autocreate settings. +#lmtp_save_to_detail_mailbox = no + +# Verify quota before replying to RCPT TO. This adds a small overhead. +#lmtp_rcpt_check_quota = no + +# Add "Received:" header to mails delivered. +#lmtp_add_received_header = yes + +# Which recipient address to use for Delivered-To: header and Received: +# header. The default is "final", which is the same as the one given to +# RCPT TO command. "original" uses the address given in RCPT TO's ORCPT +# parameter, "none" uses nothing. Note that "none" is currently always used +# when a mail has multiple recipients. +#lmtp_hdr_delivery_address = final + +# Workarounds for various client bugs: +# whitespace-before-path: +# Allow one or more spaces or tabs between `MAIL FROM:' and path and between +# `RCPT TO:' and path. +# mailbox-for-path: +# Allow using bare Mailbox syntax (i.e., without <...>) instead of full path +# syntax. +# +#lmtp_client_workarounds { +# whitespace-before-path = yes +#} +#default_process_limit = 100 +#default_client_limit = 1000 + +# Default VSZ (virtual memory size) limit for service processes. This is mainly +# intended to catch and kill processes that leak memory before they eat up +# everything. +#default_vsz_limit = 256M + +# Login user is internally used by login processes. This is the most untrusted +# user in Dovecot system. It shouldn't have access to anything at all. +#default_login_user = dovenull + +# Internal user is used by unprivileged processes. It should be separate from +# login user, so that login processes can't disturb other processes. +#default_internal_user = dovecot + +service imap-login { + inet_listener imap { + #port = 143 + } + inet_listener imaps { + #port = 993 + #ssl = yes + } + + # Number of connections to handle before starting a new process. Typically + # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0 + # is faster. + #service_restart_request_count = 1 + + # Number of processes to always keep waiting for more connections. + #process_min_avail = 0 + + # If you set service_restart_request_count=0, you probably need to grow this. + #vsz_limit = 256M # default +} + +service pop3-login { + inet_listener pop3 { + #port = 110 + } + inet_listener pop3s { + #port = 995 + #ssl = yes + } +} + +service submission-login { + inet_listener submission { + #port = 587 + } + inet_listener submissions { + #port = 465 + } +} + +service lmtp { +# unix_listener /var/run/dovecot/lmtp { +# mode = 0666 +# } + user = vexim +} + +#service lmtp { +# unix_listener lmtp { +# #mode = 0666 +# } +# +# # Create inet listener only if you can't use the above UNIX socket +# #inet_listener lmtp { +# # Avoid making LMTP visible for the entire internet +# #listen = 127.0.0.1 +# #port = 24 +# #} +#} + +service imap { + # Most of the memory goes to mmap()ing files. You may need to increase this + # limit if you have huge mailboxes. + #vsz_limit = 256M # default + + # Max. number of IMAP processes (connections) + #process_limit = 1024 +} + +service pop3 { + # Max. number of POP3 processes (connections) + #process_limit = 1024 +} + +service submission { + # Max. number of SMTP Submission processes (connections) + #process_limit = 1024 +} + +service auth { + # auth_socket_path points to this userdb socket by default. It's typically + # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have + # full permissions to this socket are able to get a list of all usernames and + # get the results of everyone's userdb lookups. + # + # The default 0666 mode allows anyone to connect to the socket, but the + # userdb lookups will succeed only if the userdb returns an "uid" field that + # matches the caller process's UID. Also if caller's uid or gid matches the + # socket's uid or gid the lookup succeeds. Anything else causes a failure. + # + # To give the caller full permissions to lookup all users, set the mode to + # something else than 0666 and Dovecot lets the kernel enforce the + # permissions (e.g. 0777 allows everyone full permissions). + unix_listener auth-userdb { + #mode = 0666 + #user = + #group = + } + + # Postfix smtp-auth + #unix_listener /var/spool/postfix/private/auth { + # mode = 0666 + #} + + # Auth process is run as this user. + #user = $SET:default_internal_user +} + +service auth-worker { + # Auth worker process is run as root by default, so that it can access + # /etc/shadow. If this isn't necessary, the user should be changed to + # $SET:default_internal_user. + #user = root +} + +service dict { + # If dict proxy is used, mail processes should have access to its socket. + # For example: mode=0660, group=vmail and global mail_access_groups=vmail + unix_listener dict { + #mode = 0600 + #user = + #group = + } +} diff --git a/docs/clients/dovecot/etc/dovecot/conf.d/20-lmtp.conf b/docs/clients/dovecot/etc/dovecot/conf.d/20-lmtp.conf new file mode 100644 index 00000000..422262c3 --- /dev/null +++ b/docs/clients/dovecot/etc/dovecot/conf.d/20-lmtp.conf @@ -0,0 +1,62 @@ +## +#i +## LMTP specific settings +## + +# Support proxying to other LMTP/SMTP servers by performing passdb lookups. +#lmtp_proxy = no + +# When recipient address includes the detail (e.g. user+detail), try to save +# the mail to the detail mailbox. See also recipient_delimiter and +# lda_mailbox_autocreate settings. +#lmtp_save_to_detail_mailbox = no + +# Verify quota before replying to RCPT TO. This adds a small overhead. +#lmtp_rcpt_check_quota = no + +# Add "Received:" header to mails delivered. +#lmtp_add_received_header = yes + +# Which recipient address to use for Delivered-To: header and Received: +# header. The default is "final", which is the same as the one given to +# RCPT TO command. "original" uses the address given in RCPT TO's ORCPT +# parameter, "none" uses nothing. Note that "none" is currently always used +# when a mail has multiple recipients. +#lmtp_hdr_delivery_address = final + +# Workarounds for various client bugs: +# whitespace-before-path: +# Allow one or more spaces or tabs between `MAIL FROM:' and path and between +# `RCPT TO:' and path. +# mailbox-for-path: +# Allow using bare Mailbox syntax (i.e., without <...>) instead of full path +# syntax. +# +#lmtp_client_workarounds { +# whitespace-before-path = yes +#} + +#protocol lmtp { + #mail_plugins { + # sieve = yes + #} + + # This strips the domain name before delivery, since the default + # userdb in Debian is /etc/passwd, which doesn't include domain + # names in the user. If you're using a different userdb backend + # that does include domain names, you may wish to remove this. See + # https://doc.dovecot.org/2.4.1/howto/lmtp/exim.html and + # https://doc.dovecot.org/2.4.1/core/summaries/settings.html#auth_username_format + #auth_username_format = %{user | username | lower} +#} + +protocol lmtp { + # Enable Sieve plugin for LMTP + mail_plugins = sieve + + # For virtual users - this is critical + auth_username_format = %{user | username} + + # Logging to help debug + info_log_path = /var/log/dovecot-lmtp.log +} diff --git a/docs/clients/dovecot/etc/dovecot/conf.d/auth-vexim-sql.conf.ext b/docs/clients/dovecot/etc/dovecot/conf.d/auth-vexim-sql.conf.ext new file mode 100644 index 00000000..21055408 --- /dev/null +++ b/docs/clients/dovecot/etc/dovecot/conf.d/auth-vexim-sql.conf.ext @@ -0,0 +1,44 @@ +# File: /etc/dovecot/conf.d/auth-vexim-sql.conf.ext +# Authentication for Vexim SQL users + +# Database driver and connection +sql_driver = mysql + +mysql localhost { + dbname = vexim + user = vexim + password = YOUR_PASSWORD +} + +# Password database +passdb sql { + default_password_scheme = BCRYPT + + query = \ + SELECT username AS user, \ + CONCAT('{CRYPT}', crypt) AS password \ + FROM users \ + WHERE username = '%{original_user}' \ + AND enabled = 1 \ + AND type IN ('local', 'piped') +} + +# User database +userdb sql { + query = \ + SELECT pop AS home, \ + uid, \ + gid, \ + CONCAT('maildir:', smtp) AS mail, \ + CONCAT('*:bytes=', quota, 'M') AS quota_rule \ + FROM users \ + WHERE username = '%{original_user}' \ + AND enabled = 1 \ + AND type IN ('local', 'piped') + + iterate_query = \ + SELECT username AS user \ + FROM users \ + WHERE enabled = 1 \ + AND type IN ('local', 'piped') +} diff --git a/docs/debian-conf.d/acl/20_vexim_whitelist b/docs/debian-conf.d/acl/20_vexim_whitelist new file mode 100644 index 00000000..506a35d3 --- /dev/null +++ b/docs/debian-conf.d/acl/20_vexim_whitelist @@ -0,0 +1,46 @@ +acl_local_deny_exceptions: +## YOU WILL NEED TO DELETE YOUR STANDARD EXIM 20_exim4-config_local_deny_exceptions FILE OR EXIM WILL BREAK! + + ######################################################## + # GLOBAL WHITELIST (SQL) + ######################################################## + + accept + condition = ${lookup mysql{ \ + SELECT 1 FROM whitelist_senders \ + WHERE domain_id = 0 \ + AND sender = '${quote_mysql:$sender_address}' \ + LIMIT 1 \ + }{yes}{no}} + + ######################################################## + # DOMAIN WHITELIST + ######################################################## + + accept + condition = ${lookup mysql{ \ + SELECT 1 FROM whitelist_senders ws \ + JOIN domains d ON ws.domain_id = d.domain_id \ + WHERE d.domain = '${quote_mysql:$domain}' \ + AND ws.localpart IS NULL \ + AND ws.sender = '${quote_mysql:$sender_address}' \ + LIMIT 1 \ + }{yes}{no}} + + ######################################################## + # USER WHITELIST + ######################################################## + + accept + condition = ${lookup mysql{ \ + SELECT 1 FROM whitelist_senders ws \ + JOIN domains d ON ws.domain_id = d.domain_id \ + WHERE d.domain = '${quote_mysql:$domain}' \ + AND ( \ + ws.localpart = '${quote_mysql:$local_part}' \ + OR ws.localpart = '*' \ + ) \ + AND ws.sender = '${quote_mysql:$sender_address}' \ + LIMIT 1 \ + }{yes}{no}} + diff --git a/docs/debian-conf.d/router/250_vexim_virtual_domains_lmtp b/docs/debian-conf.d/router/250_vexim_virtual_domains_lmtp new file mode 100644 index 00000000..7d883554 --- /dev/null +++ b/docs/debian-conf.d/router/250_vexim_virtual_domains_lmtp @@ -0,0 +1,179 @@ +### router/250_vexim_virtual_domains +### Updated to allow Exim to deliver to Dovecot via lmtp +################################# + +virtual_vacation: + driver = accept + domains = +local_domains + condition = ${if and { \ + {!match {$h_precedence:}{(?i)junk|bulk|list}} \ + {eq \ + {1} \ + {${lookup mysql{ \ + SELECT u.on_vacation \ + FROM users u \ + INNER JOIN domains d ON (d.domain_id = u.domain_id) \ + WHERE u.localpart = '${quote_mysql:$local_part}' \ + AND d.domain = '${quote_mysql:$domain}' \ + }}} \ + }} {yes}{no}} + no_verify + no_expn + unseen + transport = virtual_vacation_delivery + +virtual_forward: + driver = redirect + domains = +local_domains + check_ancestor + unseen = ${if eq \ + {1} \ + {${lookup mysql{ \ + SELECT u.unseen \ + FROM users u \ + INNER JOIN domains d ON (u.domain_id = d.domain_id) \ + WHERE u.localpart = '${quote_mysql:$local_part}' \ + AND d.domain = '${quote_mysql:$domain}' \ + AND u.on_forward = '1' \ + }}} {yes}{no}} + data = ${lookup mysql{ \ + SELECT u.forward \ + FROM users u \ + INNER JOIN domains d ON (u.domain_id = d.domain_id) \ + WHERE u.localpart = '${quote_mysql:$local_part}' \ + and d.domain = '${quote_mysql:$domain}' \ + and u.on_forward = '1' \ + }} + # We explicitly make this condition NOT forward mailing list mail! + condition = ${if and { \ + {!match {$h_precedence:}{(?i)junk}} \ + {eq \ + {1} \ + {${lookup mysql{ \ + SELECT u.on_forward \ + FROM users u \ + INNER JOIN domains d ON (u.domain_id = d.domain_id) \ + WHERE u.localpart = '${quote_mysql:$local_part}' \ + AND d.domain = '${quote_mysql:$domain}' \ + AND u.on_forward = '1' \ + }}} \ + }} {yes}{no} } + +virtual_domains: + driver = accept + domains = +local_domains + address_data = ${lookup mysql{\ + SELECT \ + u.smtp, \ + u.sa_tag * 10 AS sa_tag, \ + u.on_spamassassin AND d.spamassassin AS on_spamassassin, \ + u.uid AS uid, \ + u.gid AS gid, \ + quota \ + FROM users u \ + INNER JOIN domains d ON (u.domain_id = d.domain_id) \ + WHERE u.localpart = '${quote_mysql:$local_part}' \ + AND d.domain = '${quote_mysql:$domain}' \ + AND d.enabled = '1' \ + AND u.enabled = '1' \ + }{$value}fail} + condition = ${if !eq {$address_data}{fail} {yes}{no}} + debug_print = "DEBUG: virtual_domains router - local_part=$local_part domain=$domain" + headers_add = ${if and { \ + {match{$domain}{$original_domain}} \ + {match{$local_part}{$original_local_part}} \ + {>={$spam_score_int}{${extract{sa_tag}{$address_data}}}} \ + {eq{1}{${extract{on_spamassassin}{$address_data}}}} \ + } {X-Spam-Flag: YES\nX-Spam-Score: $acl_m_spam_score\nVEXIM_SPAM_REPORT_HEADER_NAME: $acl_m_spam_report}{} } + .ifdef VEXIM_LOCALPART_SUFFIX + local_part_suffix = VEXIM_LOCALPART_SUFFIX + local_part_suffix_optional + .endif + retry_use_local_part + transport = vexim_dovecot_lmtp + + +# A group is a list of users +# +# If a group is marked public +# then anyone on the Internet can write to it +# else only members can write to it +# +# If not public non member sender will receive a "550 Unknown user" message +virtual_dom_groups: + driver = redirect + domains = +local_domains + allow_fail + senders = ${if eq \ + {Y} \ + {${lookup mysql{ \ + SELECT g.is_public \ + FROM domains d \ + INNER JOIN `groups` g ON (g.domain_id = d.domain_id) \ + WHERE d.domain = '${quote_mysql:$domain}' \ + AND g.name = '${quote_mysql:$local_part}' \ + AND d.enabled = '1' \ + AND g.enabled = '1' \ + }}} \ + {$sender_address} \ + {${lookup mysql{ \ + SELECT CONCAT_WS('@', u.localpart, d.domain) AS sender \ + FROM domains d \ + INNER JOIN `groups` g ON (g.domain_id = d.domain_id) \ + INNER JOIN group_contents c ON (c.group_id = g.id) \ + INNER JOIN users u ON (u.user_id = c.member_id AND u.domain_id = d.domain_id) \ + WHERE d.domain = '${quote_mysql:$domain}' \ + AND g.name = '${quote_mysql:$local_part}' \ + AND g.is_public = 'N' \ + AND d.enabled = '1' \ + AND g.enabled = '1' \ + AND u.enabled = '1' \ + HAVING sender = '${quote_mysql:$sender_address}' \ + LIMIT 1 \ + }}}} + data = ${lookup mysql{ \ + SELECT CONCAT_WS('@', u.localpart, d.domain) \ + FROM domains d \ + INNER JOIN `groups` g ON (g.domain_id = d.domain_id) \ + INNER JOIN group_contents c ON (c.group_id = g.id) \ + INNER JOIN users u ON (u.user_id = c.member_id AND u.domain_id = d.domain_id) \ + WHERE d.domain = '${quote_mysql:$domain}' \ + AND g.name = '${quote_mysql:$local_part}' \ + AND d.enabled = '1' \ + AND g.enabled = '1' \ + AND u.enabled = '1' \ + }} + # using local_part_suffixes enables possibility to use user+"something" localparts + # which could cause you trouble if you're creating email addresses with plus signs in them. + .ifdef VEXIM_LOCALPART_SUFFIX + local_part_suffix = VEXIM_LOCALPART_SUFFIX + local_part_suffix_optional + .endif + retry_use_local_part + reply_transport = address_reply + pipe_transport = address_pipe + +virtual_domains_catchall: + driver = redirect + domains = +local_domains + allow_fail + data = ${lookup mysql{ \ + SELECT u.smtp \ + FROM users u \ + INNER JOIN domains d ON (u.domain_id = d.domain_id) \ + WHERE localpart = '*' \ + AND domain = '${quote_mysql:$domain}' \ + }} + retry_use_local_part + +virtual_domain_alias: + driver = redirect + domains = +local_domains + allow_fail + data = ${lookup mysql{ \ + SELECT CONCAT('${quote_mysql:$local_part}@', d.domain) \ + FROM domains d \ + INNER JOIN domainalias a ON (a.domain_id = d.domain_id) \ + WHERE a.alias = '${quote_mysql:$domain}' \ + }} + retry_use_local_part diff --git a/docs/debian-conf.d/transport/30_vexim_dovecot_lmtp b/docs/debian-conf.d/transport/30_vexim_dovecot_lmtp new file mode 100644 index 00000000..9ae5e4bb --- /dev/null +++ b/docs/debian-conf.d/transport/30_vexim_dovecot_lmtp @@ -0,0 +1,9 @@ +### transport/30_vexim_dovecot_lmtp +### New transport to allow Exim to deliver via lmtp +####################################### + +vexim_dovecot_lmtp: + driver = lmtp + socket = /var/run/dovecot/lmtp + batch_max = 200 +