#! /usr/bin/python3

# $Id: revspf 48238 2019-07-10 15:07:45Z wsl $
# $URL: https://svn.uvt.nl/its-id/trunk/sources/dmarc2srs/revspf $

# Voeg een Authentication-Results header toe die aangeeft wat er zou
# gebeuren als we deze mail zouden versturen vanaf onze mailservers.

import Milter
from Milter.utils import parse_addr
from spf import check2 as check_spf
from pwd import getpwnam, getpwuid
from grp import getgrnam
from os import setresuid, setresgid, umask
from sys import argv, stderr
from socket import getfqdn
from re import compile as regcomp

if len(argv) != 2:
	raise Exception("usage: %s <configuration file>" % (argv[0],))

config = {}

with open(argv[1]) as fh:
	exec(fh.read(), {}, config)

umask(config.get('umask', 0o007))

socket = config['socket']

# de authority van de Authentication-Results header:
authority = config.get('authority', getfqdn())

# adres dat gebruikt wordt voor adressen zonder domein:
default_domain = config['default_domain']

# adres van één van de externe mailservers van onszelf:
sender_ip = config['sender_ip']

looks_like_number = regcomp('[0-9]+').fullmatch

user = config.get('user', None)
group = config.get('group', None)

if user is not None:
	if looks_like_number(user):
		uid = int(user)
		if group is None:
			pwent = getpwuid(user)
	else:
		pwent = getpwnam(user)
		uid = pwent.pw_uid

	if group is None:
		gid = pwent.pw_gid
		setresgid(gid, gid, gid)
		setgroups(getgrouplist(user, gid))

if group is not None:
	if looks_like_number(group):
		gid = int(group)
	else:
		gid = getgrnam(group).gr_gid
	setresgid(gid, gid, gid)
	#setgroups([gid])
		
if user is not None:
	setresuid(uid, uid, uid)

class RevSPF(Milter.Base):
	def log(self, msg):
		# Milter log() seems broken, just write to stderr and let
		# systemd handle it.
		# return super().log("revspf: " + msg)
		print(msg, file = stderr, flush = True)

	def envfrom(self, address, *extra):
		self.address = address
		return Milter.CONTINUE

	def eom(self):
		try:
			# parse_addr() returnt een lijst met 1 of 2 elementen, afhankelijk
			# van of er een @ in het adres voorkomt.
			parsed = parse_addr(self.address)
			if len(parsed) < 2:
				# adres zonder @
				localpart, = parsed
				if localpart == '':
					# lege sender (<>)
					return Milter.CONTINUE
				domain = default_domain
			else:
				localpart, domain = parsed
			address = localpart + '@' + domain

			result, explanation = check_spf(i = sender_ip, s = address, h = None)
			self.log("spf check for %s, result = %s: %s" % (address, result, explanation))
			self.addheader('Authentication-Results', "%s; spf=%s smtp.mailfrom=%s" % (authority, result, address));

		except Exception as e:
			self.log(str(e))

		return Milter.CONTINUE

Milter.factory = RevSPF
Milter.set_flags(Milter.ADDHDRS)
Milter.runmilter('revspf', socket)
