This repository has been archived on 2025-03-31. You can view files and clone it, but cannot push or open issues or pull requests.
mail-arc/qmail-arc.py

127 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Qmail mail validator
This is a script meant to be used in qmail before forwarding mail to e.g. Gmail. It will verify
the incoming mail against SPF, DKIM and more and add the appropriate ARC headers. This makes Gmail
(probably) accept mails for which there's a DMARC rule with "reject" set.
Inspiration taken from http://bazaar.launchpad.net/~dkimpy-hackers/dkimpy/trunk/view/head:/arcsign.py
"""
from __future__ import print_function
__author__ = "Markus Birth"
import os
import re
import socket
import sys
import authres
import authres.arc
import dkim
import spf
AUTHSERV_ID = socket.getfqdn() # domain or hostname of mail server
AUTHSERV_HOSTNAME = socket.getfqdn() # full hostname of mail server
DKIM_DOMAIN = b"birth-online.de"
DKIM_SELECTOR = b"mbirth"
# pylint: disable=C0103,C0301
if sys.version_info[0] >= 3:
# Make sys.stdin and stdout binary streams.
sys.stdin = sys.stdin.detach()
sys.stdout = sys.stdout.detach()
privkey = open('/home/mbirth/.dkim-privkey', 'rb').read()
message = sys.stdin.read()
linesep = dkim.util.get_linesep(message)
# Find this line:
# Received: from unknown (sv3-smtp2.lithium.com [208.74.204.9])
# by eukelade.uberspace.de with SMTP; 23 Jun 2017 18:43:18 -0000
up_srv_ip_match = re.search("Received: from (.*?) \(.*? \[([0-9a-f.:]+)\].*by ", message.decode("utf-8"), re.MULTILINE | re.DOTALL)
#sys.stdout.write(repr(up_srv_ip_match).encode("utf-8"))
if not up_srv_ip_match:
# Pass-thru message
sys.stdout.write(message)
sys.exit(0)
up_srv_helo = up_srv_ip_match.group(1).lower()
up_srv_ip = up_srv_ip_match.group(2)
sender_address = os.getenv('SENDER')
results_list = []
### REV IP LOOKUP
iprev_res = "fail"
iprev_hn = "Lookup error"
try:
up_srv_hostn = socket.gethostbyaddr(up_srv_ip)
if up_srv_helo == up_srv_hostn[0]:
iprev_res = "pass"
iprev_hn = up_srv_hostn[0]
else:
iprev_res = "fail"
except:
iprev_res = "temperror"
iprev_result = authres.IPRevAuthenticationResult(result=iprev_res, policy_iprev=up_srv_ip, policy_iprev_comment=iprev_hn)
results_list += [iprev_result]
### SPF CHECK
spf_result = spf.check2(i=up_srv_ip, s=sender_address, h=up_srv_helo)
spf_res = authres.SPFAuthenticationResult(result=spf_result[0], smtp_mailfrom=sender_address, smtp_helo=up_srv_helo, reason=spf_result[1])
results_list += [spf_res]
# Write Received-SPF header (must be added ABOVE Received: lines for this server)
sys.stdout.write('Received-SPF: {0} ({1}) receiver={2}; client-ip={3}; helo={4}; envelope-from={5};'.format(spf_result[0], spf_result[1], AUTHSERV_HOSTNAME, up_srv_ip, up_srv_helo, sender_address).encode("utf-8") + linesep)
### ARC SIGNATURE
#import logging
#logging.basicConfig(level=10)
try:
cv = dkim.CV_None.decode("ascii")
if re.search(b'arc-seal', message, re.IGNORECASE):
arc_vrfy = dkim.arc_verify(message)
cv = arc_vrfy[0].decode("ascii")
arc_res = authres.arc.ARCAuthenticationResult(result=cv)
results_list += [arc_res]
except Exception as e:
sys.stdout.write("X-MTA-Error: qmail-arc failed ARC verifying ({}).".format(e).encode("utf-8") + linesep)
#raise
pass
try:
### PREP AUTH RESULT
auth_res = authres.AuthenticationResultsHeader(authserv_id=AUTHSERV_ID, results=results_list)
auth_res_str = str(auth_res).encode("utf-8") + linesep
message = auth_res_str + message
# parameters: message, selector, domain, privkey, srv_id, signature_algorithm
sig = dkim.arc_sign(message, DKIM_SELECTOR, DKIM_DOMAIN, privkey, b"eukelade.uberspace.de")
#sys.stdout.write(repr(sig).encode("utf-8"))
for line in sig:
sys.stdout.write(line)
except Exception as e:
sys.stdout.write("X-MTA-Error: qmail-arc failed ARC signing ({}).".format(e).encode("utf-8") + linesep)
#raise
pass
#sys.exit(0)
sys.stdout.write(message)