Initial Source code version
This commit is contained in:
parent
90f5f38812
commit
d979374cbc
23 changed files with 2628 additions and 0 deletions
116
app/workers/utils.py
Normal file
116
app/workers/utils.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import asyncio
|
||||
import socket
|
||||
import ssl
|
||||
import smtplib
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Any
|
||||
import dns.resolver
|
||||
from app.config import settings
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class MXRecord:
|
||||
preference: int
|
||||
exchange: str
|
||||
|
||||
async def resolve_mx(domain: str) -> List[MXRecord]:
|
||||
def _sync() -> List[MXRecord]:
|
||||
r = dns.resolver.Resolver()
|
||||
r.timeout = settings.DNS_TIMEOUT
|
||||
r.lifetime = settings.DNS_LIFETIME
|
||||
answers = r.resolve(domain, 'MX')
|
||||
recs = []
|
||||
for rr in answers:
|
||||
recs.append(MXRecord(preference=int(rr.preference), exchange=str(rr.exchange).rstrip('.')))
|
||||
recs.sort(key=lambda x: x.preference)
|
||||
return recs
|
||||
return await asyncio.to_thread(_sync)
|
||||
|
||||
async def resolve_txt(domain: str) -> List[str]:
|
||||
def _sync() -> List[str]:
|
||||
r = dns.resolver.Resolver()
|
||||
r.timeout = settings.DNS_TIMEOUT
|
||||
r.lifetime = settings.DNS_LIFETIME
|
||||
try:
|
||||
answers = r.resolve(domain, 'TXT')
|
||||
except Exception:
|
||||
return []
|
||||
out = []
|
||||
for rr in answers:
|
||||
s = ''.join(part.decode('utf-8') if isinstance(part, bytes) else str(part) for part in getattr(rr, 'strings', []))
|
||||
if not s:
|
||||
s = str(rr)
|
||||
s = s.strip('"')
|
||||
out.append(s)
|
||||
return out
|
||||
return await asyncio.to_thread(_sync)
|
||||
|
||||
def parse_spf(txt_records: List[str]) -> Dict[str, Any]:
|
||||
spf_records = [t for t in txt_records if t.lower().startswith('v=spf1')]
|
||||
details = []
|
||||
for spf in spf_records:
|
||||
tokens = spf.split()
|
||||
mechanisms = [t for t in tokens[1:]]
|
||||
qualifier = next((t for t in tokens if t.endswith('all')), None)
|
||||
details.append({
|
||||
'record': spf,
|
||||
'mechanisms': mechanisms,
|
||||
'has_all': any(tok.endswith('all') for tok in tokens),
|
||||
'all_qualifier': qualifier,
|
||||
})
|
||||
return {
|
||||
'present': len(spf_records) > 0,
|
||||
'records': spf_records,
|
||||
'parsed': details,
|
||||
}
|
||||
|
||||
async def smtp_starttls_probe(host: str, port: int = 25) -> Dict[str, Any]:
|
||||
def _sync() -> Dict[str, Any]:
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = True
|
||||
context.verify_mode = ssl.CERT_REQUIRED
|
||||
result: Dict[str, Any] = {
|
||||
'host': host,
|
||||
'port': port,
|
||||
'ehlo': False,
|
||||
'starttls_advertised': False,
|
||||
'tls_established': False,
|
||||
'tls_version': None,
|
||||
'cipher': None,
|
||||
'cert': None,
|
||||
}
|
||||
try:
|
||||
with smtplib.SMTP(host=host, port=port, timeout=settings.SMTP_TIMEOUT) as server:
|
||||
code, _ = server.ehlo()
|
||||
result['ehlo'] = (200 <= code < 400)
|
||||
if 'starttls' in (server.esmtp_features or {}):
|
||||
result['starttls_advertised'] = True
|
||||
code, _ = server.starttls(context=context)
|
||||
if 200 <= code < 400:
|
||||
result['tls_established'] = True
|
||||
server.ehlo()
|
||||
try:
|
||||
sslobj = server.sock
|
||||
result['tls_version'] = getattr(sslobj, 'version', lambda: None)()
|
||||
result['cipher'] = getattr(sslobj, 'cipher', lambda: None)()
|
||||
try:
|
||||
cert = sslobj.getpeercert()
|
||||
result['cert'] = cert
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
result['starttls_advertised'] = False
|
||||
except (ssl.SSLError, smtplib.SMTPException, OSError, socket.error) as e:
|
||||
result['error'] = str(e)
|
||||
return result
|
||||
return await asyncio.to_thread(_sync)
|
||||
|
||||
# Cert date parsing (OpenSSL style: 'Jun 5 12:00:00 2025 GMT')
|
||||
def parse_cert_time(s: str) -> datetime | None:
|
||||
try:
|
||||
norm = ' '.join(s.split())
|
||||
return datetime.strptime(norm, '%b %d %H:%M:%S %Y %Z')
|
||||
except Exception:
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue