[TUTORIAL] Block firebase early

ivenae

Well-Known Member
Feb 11, 2022
134
52
48
42
firebasemail / firebaseapp are domains hosted by Google that are commonly used to send spam.
You could add a regex for firebasemail to /etc/postfix/senderaccess, but that alone is not enough.

Some senders use Firebase with their own custom domains, but you can still block them early: their domain’s TXT SPF record contains _spf.firebasemail.com. You can use this information to reject these emails before the DATA command, saving resources and avoiding spam in your queue.

Code:
apt install python3-dnspython

Create skript: /usr/local/bin/spf-policy.py
Python:
#!/usr/bin/env python3

import sys
import time
import dns.resolver
from functools import lru_cache

BLOCK_INCLUDE = "_spf.firebasemail.com"
MAX_SPF_DEPTH = 10
CACHE_SIZE = 4096

resolver = dns.resolver.Resolver()
resolver.timeout = 2
resolver.lifetime = 3


def log(msg):
    sys.stderr.write(f"firebase-spf-policy: {msg}\n")
    sys.stderr.flush()


@lru_cache(maxsize=CACHE_SIZE)
def get_spf(domain):
    try:
        answers = resolver.resolve(domain, "TXT")
        for r in answers:
            txt = "".join([s.decode() for s in r.strings])
            if txt.startswith("v=spf1"):
                return txt
    except Exception:
        pass
    return None


def spf_contains_block(domain, depth=0, visited=None):
    if visited is None:
        visited = set()

    if depth > MAX_SPF_DEPTH:
        return False

    if domain in visited:
        return False

    visited.add(domain)

    spf = get_spf(domain)
    if not spf:
        return False

    parts = spf.split()

    for p in parts:
        if p.startswith("include:"):
            include = p.split(":", 1)[1]

            if include == BLOCK_INCLUDE:
                return True

            if spf_contains_block(include, depth + 1, visited):
                return True

    return False


def handle_request(attrs):
    sender = attrs.get("sender", "")

    if "@" not in sender:
        return "dunno"

    domain = sender.split("@", 1)[1].lower()

    try:
        if spf_contains_block(domain):
            log(f"blocked sender domain {domain}")
            return "reject reject for policy reason"
    except Exception as e:
        log(f"error checking {domain}: {e}")

    return "dunno"


def main():
    while True:

        attrs = {}

        while True:
            line = sys.stdin.readline()

            if line == "":
                return

            line = line.strip()

            if not line:
                break

            if "=" in line:
                k, v = line.split("=", 1)
                attrs[k] = v

        if not attrs:
            continue

        action = handle_request(attrs)

        print(f"action={action}\n")
        sys.stdout.flush()


if __name__ == "__main__":
    main()

Don't forget to set execution bit:
Code:
chmod +x /usr/local/bin/spf-policy.py

copy templates, if not already done:

cp /var/lib/pmg/templates/master.cf.in /etc/pmg/templates/
cp /var/lib/pmg/templates/main.cf.in /etc/pmg/templates/

Add the policy_service to the existing smtpd_sender_restrictions to main.cf.in:
Code:
smtpd_sender_restrictions =
        check_policy_service    unix:private/spf-policy
Add the two lines to the bottom of the master.cf.in
Code:
spf-policy unix  -       n       n       -       0       spawn
    user=nobody argv=/usr/local/bin/spf_policy.py


pmgconfig sync --restart
 
Last edited: