.. This file is part of PEPSI. Copyright (C) 2026 Pepsi contributors PEPSI is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. =========== pepsi-httpd =========== *The HTTP/HTTPS server: MTA-STS policy, the Web Key Directory and Prometheus metrics.* Role ==== ``pepsi-httpd`` answers HTTP requests for the Pepsi system. It serves the **MTA-STS policy file** (so the policy no longer has to be hosted elsewhere), the **Web Key Directory** that publishes our users' OpenPGP keys, and a Prometheus ``/metrics`` page. It is built around a generic dispatch table so further endpoints can be added without touching the server core. Run it with ``pepsi-httpd serve``. HTTP/1.1 (RFC 9112) with the semantics of RFC 9110, over ``hyper``; TLS is RFC 8446 over ``rustls``. What it serves is defined elsewhere, and each endpoint has its own governing document: * ``/.well-known/mta-sts.txt`` — the policy file of RFC 8461, whose content the relay stages fetch and honour on the way out. * ``/.well-known/openpgpkey/…`` — the OpenPGP Web Key Directory (*draft-koch-openpgp-webkey-service*, an Internet-Draft rather than an RFC), in both the direct and advanced layouts. * ``/mail/config-v1.1.xml`` and ``/.well-known/autoconfig/mail/config-v1.1.xml`` — mail account autoconfiguration (*draft-ietf-mailmaint-autoconfig*), described below. * ``/secure/…`` — the secure-link portal (:doc:`../secure-link`), whose session cookie follows RFC 6265. * ``/api/v1/…`` and ``/ui`` — the administrative API and console (:doc:`../admin-api`, :doc:`../web-ui`), served **only** on listeners flagged ``ADMIN = yes``, authenticated with the RFC 6750 ``Bearer`` scheme or a session cookie. Features ======== * **Multiple listeners.** Each ``[pepsi-httpd-listener-]`` section binds one socket exactly like the ingress listeners: ``SERVE = tcp`` (``BIND_TO``/``PORT``, default port 443), ``unix`` (``UNIXPATH``) or ``systemd`` (socket activation), with ``MODE = plain`` or ``tls``. * **SNI certificate selection.** A TLS listener chooses its certificate per connection from the client's SNI host name: each ``[pepsi-httpd-cert-]`` section lists one or more ``SNI`` host names and a ``TLS_CERT``/``TLS_KEY`` pair. A listener's own ``TLS_CERT``/``TLS_KEY`` is the fallback for connections with no SNI match. (MTA-STS requires a certificate valid for ``mta-sts.``.) * **Key material via systemd credentials.** The server runs unprivileged and cannot read certbot's root-only ``/etc/letsencrypt``; under systemd it does not need to. ``pepsi-setup`` writes a ``LoadCredential=`` drop-in, systemd opens each certificate and key as root when it starts the unit, and the server reads the private copy handed to it under ``$CREDENTIALS_DIRECTORY``. A certificate that still cannot be loaded is skipped with an error rather than taking the whole server down — only a listener left with no usable certificate is fatal. * **Generic routing.** Requests are matched by method and URL shape: a route's path is a list of segments, each a literal, a named capture of one segment, or a capture of the remainder. Routes are tried in registration order, first match wins, so ``GET /.well-known/mta-sts.txt``, ``GET /.well-known/openpgpkey/$DOMAIN/hu/$HASH`` and a future ``GET /foo/$ID`` are one table entry each. * **MTA-STS policy.** ``GET /.well-known/mta-sts.txt`` returns the RFC 8461 policy (built from ``[pepsi] MTA_STS_*`` and the ingress ``HOSTNAME``) when the request ``Host`` is ``mta-sts.`` for a domain in ``ACCEPTED_DOMAINS``; any other host yields ``404``. * **Web Key Directory.** ``GET /.well-known/openpgpkey/...`` publishes our own users' OpenPGP keys, in both the direct and the advanced form, answered from the key store by an indexed ``(domain, local-part hash)`` lookup. The response is the binary key with ``Access-Control-Allow-Origin: *``; the policy file is a zero-length ``200`` (its absence makes GnuPG give up before asking for a key); an unknown hash is an empty-bodied ``404``. It serves **only** identities this deployment holds and has marked ``published`` — never a cached correspondent key — and it deliberately applies no rate limiting, because the protocol is a public oracle by design. See :doc:`../key-management`. * **Prometheus metrics.** ``GET /metrics`` exposes live per-stage gauges (active and paused messages, read straight from the queue) and counters written by ``pepsi-dispatch`` (per-stage timeouts, crashes, message count and total processing time for averages, and the global stage/message totals). It is **unauthenticated** — scrape it over a private/localhost listener. * **The administrative surface.** A listener flagged ``ADMIN = yes`` additionally serves the ``/api/v1`` administrative API (:doc:`../admin-api`) and the ``/ui`` administration console (:doc:`../web-ui`). On every other listener those paths answer the ordinary ``404``. The console is a client of the API — same authentication, same scopes, same audit log — and performs no service control. Mail client autoconfiguration ============================= Setting up a mail account by hand means answering eight questions — two hostnames, two ports, two notions of transport security, two authentication methods — that the person who owns the account is usually the least equipped to answer. *draft-ietf-mailmaint-autoconfig* fixes that by having the provider publish the answers, and the client fetch them from the address alone. Pepsi serves the two rungs of that chain a provider is expected to publish. The ``Host`` decides which is being asked for: .. code-block:: text https://autoconfig.example.org/mail/config-v1.1.xml (required) https://example.org/.well-known/autoconfig/mail/config-v1.1.xml (optional) Both return the same ``text/xml`` document, and both are **public**: the draft requires it, and the reason is not an oversight but a sequencing problem — a client has to learn which authentication mechanism to use before it can authenticate. The document therefore contains no secret, and Pepsi keeps it that way by naming ``%EMAILADDRESS%``, the placeholder the *client* substitutes, rather than any real address. The draft's optional ``?emailaddress=`` parameter is accepted and ignored, which also means no request-controlled text is ever interpolated into a document served from the mail domain's own origin. .. code-block:: xml example.org Example Mail Example mail.example.org 993 SSL password-cleartext %EMAILADDRESS% mail.example.org 587 STARTTLS password-cleartext %EMAILADDRESS% **The outgoing half is derived, not configured.** Its port, transport security and authentication are read off the ``[pepsi-ingress-listener-*]`` flagged ``SUBMISSION = yes``, so a deployment that moves submission from STARTTLS on 587 to implicit TLS on 465 gets a corrected document without touching it — the commonest way a published configuration goes wrong is that it stopped matching the server, and there is nothing here to forget to update. ``SMTP_HOST`` overrides the derivation for a deployment whose public endpoint is a load balancer rather than the listener. **The incoming half cannot be derived**, because Pepsi does not serve mailboxes; it hands them to an MDA (:doc:`pepsi-stage-relay-to-lmtp`). So ``IMAP_HOST`` or ``POP3_HOST`` must name whatever the deployment pairs with, and **until one of them does, both endpoints answer 404**. That is deliberate: a client that finds a configuration document stops walking the fallback chain, so an incomplete one leaves the user worse off than none at all. Publishing needs two things outside the configuration file: an ``autoconfig.`` DNS record pointing here, and a certificate covering that name — clients try the ``https://autoconfig.…`` URL first, and a certificate error there is simply a failed lookup. A deployment unwilling to add the name can rely on the ``/.well-known/`` form, at the cost of being found only by the clients that try it. Configuration ============= ``[pepsi-httpd]``: ``MAX_CONNECTIONS`` (default 256). Listeners and certificates live in ``[pepsi-httpd-listener-]`` and ``[pepsi-httpd-cert-]`` sections. The served domains, the MTA-STS ``mx`` host and the policy itself come from the shared ``[pepsi-ingress]`` and ``[pepsi]`` sections. See :doc:`../configuration`. The Web Key Directory needs no configuration of its own — it follows each identity's ``published`` flag — but the advanced method needs an ``openpgpkey.`` DNS record per served domain, and the HTTPS listener's certificate must cover that name. ``pepsi-setup run`` asks certbot for it and reports any that does not resolve. ``[pepsi-autoconfig]`` configures the autoconfiguration document: ``IMAP_HOST`` / ``POP3_HOST`` (either one enables the feature) with their ``_PORT``, ``_SOCKET`` and ``_AUTH``; the optional ``SMTP_*`` overrides; and ``DISPLAY_NAME``, ``DISPLAY_SHORT_NAME`` and ``DOCUMENTATION_URL``. See :manpage:`pepsi.conf(5)`. See also ======== :doc:`../architecture`, :doc:`../key-management`, :doc:`pepsi-dispatch`, :doc:`pepsi-keys`, :doc:`pepsi-setup`, :manpage:`pepsi.conf(5)`.