emailsec.spf

1from .checker import check_spf, SPFCheck, SPFResult
2
3__all__ = [
4    "check_spf",
5    "SPFCheck",
6    "SPFResult",
7]
async def check_spf(sender_ip: str, sender: str) -> SPFCheck:
69async def check_spf(sender_ip: str, sender: str) -> SPFCheck:
70    parsed_ip = ipaddress.ip_address(sender_ip)
71    if "@" not in sender:
72        sender = f"postmaster@{sender}"
73    domain = sender.split("@", maxsplit=1)[-1]
74
75    result_builder = partial(SPFCheck, domain=domain, sender_ip=sender_ip)
76
77    dns_resolver = DNSResolver()
78
79    try:
80        res, explanation = await _rec_check_host(
81            dns_resolver, parsed_ip, sender, domain
82        )
83    except errors.Permerror as error:
84        return result_builder(result=SPFResult.PERMERROR, exp=error.args[0])
85    except errors.Temperror as error:
86        return result_builder(result=SPFResult.TEMPERROR, exp=error.args[0])
87    else:
88        return result_builder(result=res, exp=explanation)
@dataclass
class SPFCheck:
39@dataclass
40class SPFCheck:
41    result: SPFResult
42    domain: str
43    sender_ip: str
44    exp: str
45
46    # TODO: figure out how to include the mechanism
SPFCheck( result: SPFResult, domain: str, sender_ip: str, exp: str)
result: SPFResult
domain: str
sender_ip: str
exp: str
class SPFResult(enum.StrEnum):
17class SPFResult(enum.StrEnum):
18    NONE = "none"
19    NEUTRAL = "neutral"
20    PASS = "pass"
21    FAIL = "fail"
22    SOFTFAIL = "softfail"
23    TEMPERROR = "temperror"
24    PERMERROR = "permerror"
25
26    @classmethod
27    def from_qualifier(cls, q: Qualifier) -> "SPFResult":
28        match q:
29            case Qualifier.PASS:
30                return cls.PASS
31            case Qualifier.FAIL:
32                return cls.FAIL
33            case Qualifier.SOFTFAIL:
34                return cls.SOFTFAIL
35            case Qualifier.NEUTRAL:
36                return cls.NEUTRAL
NONE = <SPFResult.NONE: 'none'>
NEUTRAL = <SPFResult.NEUTRAL: 'neutral'>
PASS = <SPFResult.PASS: 'pass'>
FAIL = <SPFResult.FAIL: 'fail'>
SOFTFAIL = <SPFResult.SOFTFAIL: 'softfail'>
TEMPERROR = <SPFResult.TEMPERROR: 'temperror'>
PERMERROR = <SPFResult.PERMERROR: 'permerror'>
@classmethod
def from_qualifier(cls, q: emailsec.spf.parser.Qualifier) -> SPFResult:
26    @classmethod
27    def from_qualifier(cls, q: Qualifier) -> "SPFResult":
28        match q:
29            case Qualifier.PASS:
30                return cls.PASS
31            case Qualifier.FAIL:
32                return cls.FAIL
33            case Qualifier.SOFTFAIL:
34                return cls.SOFTFAIL
35            case Qualifier.NEUTRAL:
36                return cls.NEUTRAL