emailsec.spf
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
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'>