|
libwebsockets
Lightweight C library for HTML5 websockets
|
lws can send mail: queue it, and lws delivers it to an SMTP relay in the background and tells you how each mail went. The relay is usually the local MTA (postfix, exim...) on 127.0.0.1:25, which is the default, but it can be anywhere, with implicit tls or STARTTLS.
Build with -DLWS_WITH_EMAIL=1. It needs a build with client support; the auth server, and so LWS_WITH_DISTRO_RECOMMENDED, turns it on.
It comes in two layers:
| layer | header | source | what it is |
|---|---|---|---|
| lws_smtpc | lws-smtp-client.h | lib/system/smtp/ | the mail queue, the connection to the relay, retries and backoff, the outcome of each mail |
| the session | lws-smtp.h | lib/sansio/smtp/ | the SMTP client protocol, sansIO: reply bytes in, command and message bytes out |
Most code wants only lws_smtpc.
Every vhost has an SMTP client of its own, made the first time it is asked for:
Everything the mail points to is copied, so it can be on the stack. lws_smtpc_queue() never calls back from inside itself; if it returns 0, the callback is called exactly once later, with one of
| outcome | meaning |
|---|---|
| LWS_SMTPC_DELIVERED | the relay accepted the mail |
| LWS_SMTPC_REFUSED | the relay refused it with a 5xx |
| LWS_SMTPC_GAVE_UP | it was tried max_tries times without being done with, or the relay was unusable for the whole of the retry policy |
| LWS_SMTPC_ABANDONED | the client, or its vhost, was destroyed before it was done with |
res->code and res->text are the relay's last reply about that mail (0 and a short reason when there was none, eg, "connection lost").
To reach a different relay, or with different limits, make your own client with lws_smtpc_create(), and destroy it with lws_smtpc_destroy(). A client's callbacks may queue more mail, and may destroy a client you made.
The vhost's own client is configured by per-vhost options on the lws-smtp-client protocol, which every vhost has when LWS_WITH_EMAIL is built. In lwsws JSON:
| pvo | meaning | default |
|---|---|---|
| smtp-host | the relay to connect to | 127.0.0.1 |
| smtp-port | its port | 25 |
| smtp-tls | none, implicit (tls from the start) or starttls | none |
| smtp-tls-host | the name the relay's certificate is checked against | smtp-host |
| smtp-tls-skip-hostname-check | 1 to not check the certificate's name at all | 0 |
| smtp-helo | our name for EHLO | localhost |
With tls, the relay's certificate must be one the vhost's client tls context trusts, and must be for the relay: the name it is checked against is smtp-tls-host if given, else smtp-host itself, which may be an address literal if the certificate carries it as an IP SAN. A relay reached by an address its certificate does not carry, typically a local one, needs either smtp-tls-host naming what the certificate is for, or smtp-tls-skip-hostname-check, which leaves the certificate checked against the trusted CAs but not for its name: any certificate they issued is then accepted, so it is only for a relay on a path nothing else can get onto.
These were the options of the smtp client plugin this replaces, which the vhost's own client now reads; a config that used the plugin works unchanged, except that the plugin and the first library version skipped the name check whenever smtp-tls-host was not given. A config with smtp-tls on and no smtp-tls-host now has the certificate checked against smtp-host, and needs smtp-tls-skip-hostname-check to get the old behaviour.
Each mail has one sender and one recipient, plain local@domain addresses (no quoted local parts, address literals or UTF-8 addresses). lws_smtp_email_check() says if a mail is acceptable; lws_smtpc_queue() refuses one that is not, so nothing a caller puts in a mail can inject commands or headers.
The message is text/plain; charset=UTF-8 with Date:, From:, To:, Subject: and Message-ID: headers. lws_smtpc_queue() adds the date and a random message id at the sender's domain when the mail has none. A subject that is not plain ASCII, or is long, is sent as folded RFC 2047 encoded-words. Body line ends are sent as CRLF whatever they were, lines starting with . are dot-stuffed, and a body with 8-bit bytes is declared 8bit, with BODY=8BITMIME when the relay offers it. A body line may be at most 998 bytes once sent.
lws_smtp_session_t is the client side of one SMTP session with no transport under it and no clock, for code that has its own byte stream to a server (or for a port of it). The caller:
lws_smtpc is exactly that, over an lws raw client connection.
minimal-examples-lowlevel/api-tests/api-test-smtp-client tests the session alone, byte for byte, then lws_smtpc end to end against a fake relay in the same process: plaintext, implicit tls, STARTTLS, refusals, deferrals, timeouts, and destroying clients and vhosts with mail queued.