async def start_server(
config: ServerConfig,
log_level: str = "INFO",
) -> None:
"""Start a Misfin server."""
configure_logging(log_level=log_level)
config.validate_files()
certfile, keyfile, identity_certfile, identity_keyfile = _ensure_certificates(config)
# Create TLS context using PyOpenSSL-based permissive context that
# accepts any client cert (including self-signed) without CA validation.
# Client identity is verified at the application layer via TOFU.
ssl_context = create_permissive_server_context(
certfile=str(certfile),
keyfile=str(keyfile),
request_client_cert=True,
)
middleware = _build_middleware(config)
# Create mailbox directory with restrictive permissions
config.server.mailbox_dir.mkdir(parents=True, exist_ok=True)
os.chmod(config.server.mailbox_dir, 0o700)
# Compute identity certificate fingerprint for probe responses
id_cert = load_pem_x509_certificate(identity_certfile.read_bytes())
id_fingerprint = normalize_fingerprint(get_certificate_fingerprint(id_cert))
# Set up encryption if enabled
encryption_manager = _setup_encryption(config)
# Load per-mailbox recipient fingerprints
cert_dir = config.server.identity_cert_dir or config.server.mailbox_dir
recipient_fps = _load_recipient_fingerprints(cert_dir, id_fingerprint)
# Create subscription token store for mailing lists
subscription_store: SubscriptionTokenStore | None = None
if config.lists.enable:
from .lists import SUBSCRIPTION_DB_FILE
subscription_store = SubscriptionTokenStore(
config.server.mailbox_dir / SUBSCRIPTION_DB_FILE
)
# Create base handler
base_handler = FileMailboxHandler(
mailbox_dir=config.server.mailbox_dir,
hostname=config.server.hostname,
recipient_fingerprint_fn=lambda m: recipient_fps.get(m, id_fingerprint),
identity_cert_fingerprint=id_fingerprint,
encryption_manager=encryption_manager,
auto_reply_enabled=config.auto_reply.enable,
auto_reply_interval=config.auto_reply.interval,
identity_certfile=identity_certfile,
identity_keyfile=identity_keyfile,
port=config.server.port,
lists_enabled=config.lists.enable,
lists_archive=config.lists.archive,
subscription_store=subscription_store,
)
handler, cache = _setup_verification(
config, base_handler, identity_certfile, identity_keyfile
)
# Start Misfin server — TLS handled by TLSServerProtocol (no ssl= param)
loop = asyncio.get_running_loop()
misfin_server = await loop.create_server(
lambda: TLSServerProtocol(
lambda: MisfinServerProtocol(
message_handler=handler.handle_message,
middleware=middleware,
),
ssl_context,
),
host=config.server.host,
port=config.server.port,
)
logger.info(
"server_started",
host=config.server.host,
port=config.server.port,
hostname=config.server.hostname,
rate_limiting=config.rate_limit.enable,
access_control=config.access_control.enable,
encryption=config.encryption.enable,
gmap=config.gmap.enable,
mailbox_dir=str(config.server.mailbox_dir),
)
gmap_server = await _start_gmap_server(
config, certfile, keyfile, cert_dir, recipient_fps
)
try:
if gmap_server is not None:
async with misfin_server, gmap_server:
await asyncio.gather(
misfin_server.serve_forever(),
gmap_server.serve_forever(),
)
else:
async with misfin_server:
await misfin_server.serve_forever()
except asyncio.CancelledError:
pass
finally:
if cache is not None:
cache.close()
if subscription_store is not None:
subscription_store.close()
logger.info("server_stopped")