mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-08 18:13:37 +00:00
[Web][Postfix] Add mail policies: per-recipient send/receive rules for mailboxes and groups
This commit is contained in:
36 files changed
+2677
-42
No files matched your search
@@ -26,6 +26,7 @@ RUN groupadd -g 102 postfix \
|
||||
postfix \
|
||||
postfix-mysql \
|
||||
postfix-pcre \
|
||||
python3 \
|
||||
redis-tools \
|
||||
sasl2-bin \
|
||||
sudo \
|
||||
@@ -46,6 +47,7 @@ COPY postfix.sh /opt/postfix.sh
|
||||
COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham
|
||||
COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam
|
||||
COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh
|
||||
COPY mailcow-policy.py /usr/local/bin/mailcow-policy.py
|
||||
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
@@ -53,6 +55,7 @@ RUN chmod +x /opt/postfix.sh \
|
||||
/usr/local/bin/rspamd-pipe-ham \
|
||||
/usr/local/bin/rspamd-pipe-spam \
|
||||
/usr/local/bin/whitelist_forwardinghosts.sh \
|
||||
/usr/local/bin/mailcow-policy.py \
|
||||
/usr/local/sbin/stop-supervisor.sh
|
||||
RUN rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/python3
|
||||
# Policy service of the mail policies (mailcow-policy in master.cf, spawned by Postfix): reads the policy requests
|
||||
# of smtpd and asks the mailcow UI for the action (nginx:8081/mailpolicy.php). Without an answer the recipient is
|
||||
# deferred; smtpd only asks while policies or internal aliases exist (mysql_mail_policy_maps.cf).
|
||||
|
||||
import http.client
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
from urllib.parse import urlencode
|
||||
|
||||
# one line: DUNNO or a rejection with code and text
|
||||
valid_action = re.compile(rb'DUNNO|[45]\d\d [45]\.\d{1,3}\.\d{1,3} [\x20-\x7e]{1,200}')
|
||||
unavailable = b'DEFER_IF_PERMIT Mail policy check temporarily unavailable'
|
||||
# address of nginx, resolved once per process (a lookup costs more than the check)
|
||||
nginx = None
|
||||
|
||||
def check(request):
|
||||
global nginx
|
||||
body = urlencode(sorted(request.items())).encode()
|
||||
try:
|
||||
if nginx is None:
|
||||
nginx = socket.gethostbyname('nginx')
|
||||
# http.client sets TCP_NODELAY, so the body is not held back until the headers are acknowledged
|
||||
conn = http.client.HTTPConnection(nginx, 8081, timeout=10)
|
||||
try:
|
||||
conn.request('POST', '/mailpolicy.php', body, {'Host': 'nginx', 'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
response = conn.getresponse()
|
||||
action = response.read()
|
||||
finally:
|
||||
conn.close()
|
||||
except (OSError, http.client.HTTPException):
|
||||
# nginx may have a new address after a restart
|
||||
nginx = None
|
||||
return unavailable
|
||||
return action if response.status == 200 and valid_action.fullmatch(action) else unavailable
|
||||
|
||||
def main():
|
||||
request = {}
|
||||
# attributes as bytes: addresses may contain any 8-bit characters
|
||||
for line in sys.stdin.buffer:
|
||||
line = line.rstrip(b'\r\n')
|
||||
if line:
|
||||
name, separator, value = line.partition(b'=')
|
||||
if separator:
|
||||
request[name] = value
|
||||
continue
|
||||
sys.stdout.buffer.write(b'action=' + check(request) + b'\n\n')
|
||||
sys.stdout.buffer.flush()
|
||||
request = {}
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -15,6 +15,21 @@ until dig +short mailcow.email > /dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Wait for updated schema (DB_VERSION from docker-compose.yml), the maps read tables the init_db of php-fpm creates on updates
|
||||
DBV_NOW=$(mariadb --ssl=false --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN 2>/dev/null)
|
||||
DBV_WAIT=0
|
||||
while [[ "${DBV_NOW}" != "${DB_VERSION}" ]] && [[ ${DBV_WAIT} -lt 60 ]]; do
|
||||
echo "Waiting for schema update..."
|
||||
sleep 5
|
||||
DBV_WAIT=$((DBV_WAIT + 1))
|
||||
DBV_NOW=$(mariadb --ssl=false --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN 2>/dev/null)
|
||||
done
|
||||
if [[ "${DBV_NOW}" != "${DB_VERSION}" ]]; then
|
||||
echo -e "\e[31mDB schema is ${DBV_NOW}, expected ${DB_VERSION} - starting anyway, check the php-fpm log (init_db)\e[0m"
|
||||
else
|
||||
echo "DB schema is ${DBV_NOW}"
|
||||
fi
|
||||
|
||||
cat <<EOF > /etc/aliases
|
||||
# Autogenerated by mailcow
|
||||
null: /dev/null
|
||||
@@ -224,6 +239,20 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports
|
||||
LIMIT 1;
|
||||
EOF
|
||||
|
||||
# Mail policies: while policies or internal aliases exist, smtpd asks the policy service (mailcow_mail_policy in main.cf)
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_mail_policy_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT 'mailcow_mail_policy' FROM mail_policy_member
|
||||
JOIN mail_policy ON mail_policy.id = mail_policy_member.policy_id
|
||||
WHERE mail_policy.active = '1' AND '%s' != ''
|
||||
UNION SELECT 'mailcow_mail_policy' FROM alias WHERE internal = '1' AND active = '1'
|
||||
LIMIT 1;
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
|
||||
Reference in new issue
Block a user