24bool send_str(
SmtpSendFn send,
void *ctx,
const char *s)
27 return n == 0 || send(ctx, (
const uint8_t *)s, n) == (int)n;
33bool reply_complete(
const char *buf,
size_t len,
int *code)
36 for (
size_t i = 0; i + 1 < len; i++)
38 if (buf[i] !=
'\r' || buf[i + 1] !=
'\n')
40 size_t line_len = i - start;
41 if (line_len >= 3 && buf[start] >=
'0' && buf[start] <=
'9' && buf[start + 1] >=
'0' && buf[start + 1] <=
'9' &&
42 buf[start + 2] >=
'0' && buf[start + 2] <=
'9')
44 bool final_line = (line_len == 3) || buf[start + 3] ==
' ';
47 *code = (buf[start] -
'0') * 100 + (buf[start + 1] -
'0') * 10 + (buf[start + 2] -
'0');
63 if (reply_complete(buf, len, code))
65 if (len >=
sizeof(buf))
67 int n = recv(ctx, (uint8_t *)buf + len,
sizeof(buf) - len);
78 if (!send_str(send, ctx, line))
90 size_t slen = strnlen(secret,
sizeof(b64));
91 if (((slen + 2) / 3) * 4 + 3 >=
sizeof(b64))
94 int n = snprintf(line,
sizeof(line),
"%s\r\n", b64);
95 if (n < 0 || (
size_t)n >=
sizeof(line))
98 return command(send, recv, ctx, line);
105 int hn = snprintf(out, cap,
109 "MIME-Version: 1.0\r\n"
110 "Content-Type: text/plain; charset=UTF-8\r\n"
113 if (hn < 0 || (
size_t)hn >= cap)
115 size_t n = (size_t)hn;
117 const char *b = msg->
body ? msg->
body :
"";
118 bool at_line_start =
true;
119 for (
size_t i = 0; b[i]; i++)
130 at_line_start =
true;
133 if (at_line_start && c ==
'.')
142 at_line_start =
false;
145 if (!(n >= 2 && out[n - 2] ==
'\r' && out[n - 1] ==
'\n'))
163 if (!cfg || !msg || !send || !recv || !cfg->
host || !cfg->
from || !cfg->
from[0] || !msg->
to || !msg->
to[0])
176 int n = snprintf(line,
sizeof(line),
"EHLO %s\r\n", (cfg->
helo && cfg->
helo[0]) ? cfg->
helo :
"esp32");
177 if (n < 0 || (
size_t)n >=
sizeof(line))
179 code = command(send, recv, ctx, line);
188 code = command(send, recv, ctx,
"AUTH LOGIN\r\n");
193 code = auth_send_b64(send, recv, ctx, cfg->
user);
198 code = auth_send_b64(send, recv, ctx, cfg->
pass ? cfg->
pass :
"");
206 n = snprintf(line,
sizeof(line),
"MAIL FROM:<%s>\r\n", cfg->
from);
207 if (n < 0 || (
size_t)n >=
sizeof(line))
209 code = command(send, recv, ctx, line);
216 n = snprintf(line,
sizeof(line),
"RCPT TO:<%s>\r\n", msg->
to);
217 if (n < 0 || (
size_t)n >=
sizeof(line))
219 code = command(send, recv, ctx, line);
222 if (code != 250 && code != 251)
226 code = command(send, recv, ctx,
"DATA\r\n");
234 int mlen = build_message(body,
sizeof(body), cfg, msg);
237 if (send(ctx, (
const uint8_t *)body, (
size_t)mlen) != mlen)
245 (void)command(send, recv, ctx,
"QUIT\r\n");
270int cl_send(
void *ctx,
const uint8_t *data,
size_t len)
272 SmtpXport *x = (SmtpXport *)ctx;
276 size_t chunk = len - sent;
285int cl_recv(
void *ctx, uint8_t *buf,
size_t cap)
287 SmtpXport *x = (SmtpXport *)ctx;
288 while ((int32_t)(x->deadline - millis()) > 0)
302int tls_bio_send(
void *ctx,
const unsigned char *buf,
size_t len)
304 SmtpXport *x = (SmtpXport *)ctx;
305 return det_client_send(x->cid, buf, len) ? (int)len : MBEDTLS_ERR_SSL_WANT_WRITE;
307int tls_bio_recv(
void *ctx,
unsigned char *buf,
size_t len)
309 SmtpXport *x = (SmtpXport *)ctx;
316int tls_send(
void *ctx,
const uint8_t *data,
size_t len)
319 return det_tls_csess_write(data, len) == (int)len ? (
int)len : -1;
321int tls_recv(
void *ctx, uint8_t *buf,
size_t cap)
323 SmtpXport *x = (SmtpXport *)ctx;
324 while ((int32_t)(x->deadline - millis()) > 0)
326 int n = det_tls_csess_read(buf, cap);
329 if (n < 0 && n != MBEDTLS_ERR_SSL_WANT_READ && n != MBEDTLS_ERR_SSL_WANT_WRITE)
340 if (!cfg || !cfg->
host)
353 if (!det_tls_csess_begin(cfg->
host, tls_bio_send, tls_bio_recv))
359 while ((h = det_tls_csess_handshake()) == 0 && (int32_t)(x.deadline - millis()) > 0)
367 rc =
smtp_run(cfg, msg, tls_send, tls_recv, &x);
376 rc =
smtp_run(cfg, msg, cl_send, cl_recv, &x);
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_SMTP_TIMEOUT_MS
SMTP connect / per-reply timeout in milliseconds.
#define DETWS_SMTP_REPLY_MAX
Max size of one (possibly multi-line) server reply held while parsing, bytes.
#define DETWS_SMTP_LINE_MAX
Max length of one SMTP command / address line (bytes, incl. CRLF).
#define DETWS_SMTP_MSG_MAX
Max size of the assembled DATA payload (headers + dot-stuffed body), bytes.
void base64_encode(const uint8_t *src, size_t src_len, char *dst)
Encode src_len bytes of src as Base64.
size_t det_client_available(int)
Wire bytes currently buffered and ready to read.
bool det_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int det_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
size_t det_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
void det_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
bool det_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Outbound SMTP client (RFC 5321) - send a device email alert.
SmtpResult smtp_send(const SmtpConfig *cfg, const SmtpMessage *msg)
Blocking one-shot send over the real transport (det_client, plus TLS when cfg->tls)....
int(* SmtpSendFn)(void *ctx, const uint8_t *data, size_t len)
Transport seam for smtp_run(): the engine sends and receives raw bytes only through these,...
SmtpResult
Result of an SMTP send. 0 is success; every failure is a distinct negative code.
@ SMTP_ERR_IO
a send/recv failed or the reply timed out
@ SMTP_ERR_ARG
a required field (host / from / to) was null or empty
@ SMTP_ERR_AUTH
AUTH was rejected (bad user/password)
@ SMTP_ERR_OVERFLOW
a command line or the message exceeded its fixed buffer
@ SMTP_ERR_TLS
the TLS handshake failed (SMTPS)
@ SMTP_ERR_PROTOCOL
the server returned an unexpected reply code
@ SMTP_ERR_CONNECT
could not open the transport (DNS / connect)
int(* SmtpRecvFn)(void *ctx, uint8_t *buf, size_t cap)
SmtpResult smtp_run(const SmtpConfig *cfg, const SmtpMessage *msg, SmtpSendFn send, SmtpRecvFn recv, void *ctx)
Drive the full SMTP exchange over send / recv. Pure - no lwIP or TLS - so it is host-testable with a ...
Server address + credentials for one send. Addresses are bare (no angle brackets).
const char * user
AUTH LOGIN username (null or empty => skip AUTH)
bool tls
true = implicit TLS on connect (SMTPS); false = plaintext
const char * pass
AUTH LOGIN password.
const char * from
envelope sender + From: header address
uint16_t port
25 / 587 / 465
const char * host
server hostname (also the TLS SNI name)
const char * helo
EHLO domain to announce (null => "esp32")
const char * body
plain-text UTF-8 body; LF or CRLF line ends, dot-stuffed for you
const char * to
single recipient address (envelope + To: header)
const char * subject
Subject: header (null => empty)
Deterministic TLS engine: mbedTLS over a static memory pool (DETWS_ENABLE_TLS).