coulomb-social/scripts/native_recovery/mail_sink.py
tegwick 7b4b75089c
Some checks failed
Use-case regression gate / usecase-regressions (push) Has been cancelled
Verify native reset SMTP issuer and app recovery through Chromium
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 00:15:24 +01:00

119 lines
4.7 KiB
Python

"""Bounded synthetic SMTP/mailbox fixture, available only inside a network-none peer."""
import email
import email.policy
import json
import re
import socketserver
import threading
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
MESSAGES = []
LOCK = threading.Lock()
class SMTP(socketserver.StreamRequestHandler):
def handle(self):
self.connection.settimeout(15)
recipient = None
sender = False
def reply(line):
self.wfile.write((line + '\r\n').encode())
self.wfile.flush()
reply('220 synthetic fixture')
while True:
raw = self.rfile.readline(8193)
if not raw or len(raw) > 8192:
return
command = raw.decode(errors='replace').strip()
upper = command.upper()
if upper.startswith(('EHLO ', 'HELO ')):
reply('250 synthetic fixture')
elif upper.startswith('MAIL FROM:'):
sender = upper == 'MAIL FROM:<NO-REPLY@EXAMPLE.TEST>'
reply('250 OK' if sender else '550 fixture sender only')
elif upper.startswith('RCPT TO:'):
recipient = ('alice@example.test'
if upper == 'RCPT TO:<ALICE@EXAMPLE.TEST>' else None)
reply('250 OK' if recipient else '550 fixture recipient only')
elif upper == 'DATA' and sender and recipient:
reply('354 End with dot')
lines, size = [], 0
while True:
line = self.rfile.readline(65537)
if not line or len(line) > 65536:
return
if line == b'.\r\n':
break
size += len(line)
if size > 65536:
reply('552 fixture message too large')
return
lines.append(line[1:] if line.startswith(b'..') else line)
message = email.message_from_bytes(b''.join(lines), policy=email.policy.default)
body = message.get_body(preferencelist=('plain', 'html'))
text = body.get_content() if body else message.get_content()
links = re.findall(r'https://auth\.recovery\.test:9091/'
r'reset-password/step2\?token=[A-Za-z0-9_.-]+', str(text))
with LOCK:
if len(MESSAGES) >= 20:
reply('452 fixture mailbox full')
return
MESSAGES.append({'number': len(MESSAGES) + 1, 'recipient': recipient,
'reset_links': list(dict.fromkeys(links))})
recipient, sender = None, False
reply('250 fixture accepted')
elif upper in {'RSET', 'NOOP'}:
recipient, sender = None, False
reply('250 OK')
elif upper == 'QUIT':
reply('221 bye')
return
else:
reply('502 fixture command refused')
class SMTPServer(socketserver.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class Mailbox(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def do_GET(self): # noqa: N802
if urllib.parse.urlsplit(self.path).path == '/client-callback':
body = b'{"status":"synthetic_callback_receiver"}'
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
self.end_headers()
self.wfile.write(body)
return
if self.path not in {'/health', '/messages'}:
self.send_error(404)
return
with LOCK:
body = json.dumps({'messages': MESSAGES} if self.path == '/messages'
else {'status': 'fixture-ready'}).encode()
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
self.end_headers()
self.wfile.write(body)
if __name__ == '__main__':
if not __debug__ or {p.name for p in Path('/sys/class/net').iterdir()} != {'lo'}:
raise SystemExit('Fixture loopback boundary required')
with SMTPServer(('127.0.0.1', 2525), SMTP) as smtp:
thread = threading.Thread(target=smtp.serve_forever, daemon=True)
thread.start()
ThreadingHTTPServer(('127.0.0.1', 8088), Mailbox).serve_forever()