27#if defined(ARDUINO) && PC_ENABLE_SMTP_TLS
29#include <mbedtls/ssl.h>
34bool send_str(
SmtpSendFn send,
void *ctx,
const char *s)
39 return n == 0 || send(ctx, (
const uint8_t *)s, n) == (int)n;
45bool reply_complete(
const char *buf,
size_t len,
int *code)
48 for (
size_t i = 0; i + 1 < len; i++)
50 if (buf[i] !=
'\r' || buf[i + 1] !=
'\n')
54 size_t line_len = i - start;
55 if (line_len >= 3 && buf[start] >=
'0' && buf[start] <=
'9' && buf[start + 1] >=
'0' && buf[start + 1] <=
'9' &&
56 buf[start + 2] >=
'0' && buf[start + 2] <=
'9')
58 bool final_line = (line_len == 3) || buf[start + 3] ==
' ';
61 *code = (buf[start] -
'0') * 100 + (buf[start + 1] -
'0') * 10 + (buf[start + 2] -
'0');
72bool ieq(
const char *a,
const char *b,
size_t n)
74 for (
size_t i = 0; i < n; i++)
78 if (ca >=
'A' && ca <=
'Z')
80 ca = (char)(ca -
'A' +
'a');
84 if (cb >=
'A' && cb <=
'Z')
86 cb = (char)(cb -
'A' +
'a');
99bool reply_has_cap(
const char *buf,
size_t len,
const char *want)
101 size_t wlen = strnlen(want, len + 1);
103 for (
size_t i = 0; i + 1 < len; i++)
105 if (buf[i] !=
'\r' || buf[i + 1] !=
'\n')
109 size_t line_len = i - start;
112 const char *kw = buf + start + 4;
113 size_t klen = line_len - 4;
114 if (klen >= wlen && ieq(kw, want, wlen) && (klen == wlen || kw[wlen] ==
' '))
132 if (reply_complete(buf, len, code))
138 *found = reply_has_cap(buf, len, want);
142 if (len >=
sizeof(buf))
146 int n = recv(ctx, (uint8_t *)buf + len,
sizeof(buf) - len);
157 return read_reply_cap(recv, ctx, code,
nullptr,
nullptr);
164 if (!send_str(send, ctx, line))
178 size_t slen = strnlen(secret,
sizeof(b64));
179 if (((slen + 2) / 3) * 4 + 3 >=
sizeof(b64))
184 pc_sb sb_line = {line,
sizeof(line), 0,
true};
194 return command(send, recv, ctx, line);
201 pc_sb sb_out = {out, cap, 0,
true};
208 pc_sb_put(&sb_out,
"\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n");
216 size_t n = (size_t)hn;
218 const char *b = msg->
body ? msg->
body :
"";
219 bool at_line_start =
true;
220 for (
size_t i = 0; b[i]; i++)
235 at_line_start =
true;
238 if (at_line_start && c ==
'.')
251 at_line_start =
false;
257 if (!(n >= 2 && out[n - 2] ==
'\r' && out[n - 1] ==
'\n'))
280 int code = command(send, recv, ctx, line);
304 pc_sb sb_line2 = {line, cap, 0,
true};
313 if (!send_str(send, ctx, line))
360 int code = auth_send_b64(send, recv, ctx, cfg->
user);
369 code = auth_send_b64(send, recv, ctx, cfg->
pass ? cfg->
pass :
"");
379 char *line,
size_t cap)
381 pc_sb sb_line3 = {line, cap, 0,
true};
396 pc_sb sb_line4 = {line, cap, 0,
true};
405 int code = command(send, recv, ctx, line);
410 if (code != 250 && code != 251)
426 int mlen = build_message(body,
sizeof(body), cfg, msg);
431 if (send(ctx, (
const uint8_t *)body, (
size_t)mlen) != mlen)
447 if (!cfg || !msg || !send || !recv || !cfg->
host || !cfg->
from || !cfg->
from[0] || !msg->
to || !msg->
to[0])
453 bool has_starttls =
false;
454 SmtpResult r = greet_ehlo(cfg, send, recv, ctx, line,
sizeof(line), &has_starttls);
462 r = upgrade_starttls(send, recv, starttls, ctx, line, has_starttls);
471 r = auth_login(cfg, send, recv, ctx);
478 r = send_envelope(cfg, msg, send, recv, ctx, line,
sizeof(line));
484 r = send_data(cfg, msg, send, recv, ctx);
491 (void)command(send, recv, ctx,
"QUIT\r\n");
522static SmtpTlsCtx s_smtp_tls = {
nullptr};
525int cl_send(
void *ctx,
const uint8_t *data,
size_t len)
527 SmtpXport *x = (SmtpXport *)ctx;
531 size_t chunk = len - sent;
544int cl_recv(
void *ctx, uint8_t *buf,
size_t cap)
546 SmtpXport *x = (SmtpXport *)ctx;
547 while ((int32_t)(x->deadline - millis()) > 0)
563#if PC_ENABLE_SMTP_TLS
565int tls_bio_send(
void *ctx,
const unsigned char *buf,
size_t len)
568 SmtpXport *x = s_smtp_tls.xport;
571 return MBEDTLS_ERR_SSL_WANT_WRITE;
573 return pc_client_send(x->cid, buf, len) ? (int)len : MBEDTLS_ERR_SSL_WANT_WRITE;
575int tls_bio_recv(
void *ctx,
unsigned char *buf,
size_t len)
578 SmtpXport *x = s_smtp_tls.xport;
581 return MBEDTLS_ERR_SSL_WANT_READ;
591int tls_send(
void *ctx,
const uint8_t *data,
size_t len)
594 return pc_tls_client_session_write(data, len) == (int)len ? (
int)len : -1;
596int tls_recv(
void *ctx, uint8_t *buf,
size_t cap)
598 SmtpXport *x = (SmtpXport *)ctx;
599 while ((int32_t)(x->deadline - millis()) > 0)
601 int n = pc_tls_client_session_read(buf, cap);
606 if (n < 0 && n != MBEDTLS_ERR_SSL_WANT_READ && n != MBEDTLS_ERR_SSL_WANT_WRITE)
619int xp_send(
void *ctx,
const uint8_t *data,
size_t len)
621#if PC_ENABLE_SMTP_TLS
622 if (((SmtpXport *)ctx)->tls_active)
624 return tls_send(ctx, data, len);
627 return cl_send(ctx, data, len);
629int xp_recv(
void *ctx, uint8_t *buf,
size_t cap)
631#if PC_ENABLE_SMTP_TLS
632 if (((SmtpXport *)ctx)->tls_active)
634 return tls_recv(ctx, buf, cap);
637 return cl_recv(ctx, buf, cap);
641bool xp_starttls(
void *ctx)
643 SmtpXport *x = (SmtpXport *)ctx;
644#if PC_ENABLE_SMTP_TLS
645 if (!pc_tls_client_session_begin(x->host, tls_bio_send, tls_bio_recv))
654 while ((h = pc_tls_client_session_handshake()) == 0 && (int32_t)(x->deadline - millis()) > 0)
660 pc_tls_client_session_end();
663 x->tls_active =
true;
674 if (!cfg || !cfg->
host)
687 x.tls_active =
false;
688#if PC_ENABLE_SMTP_TLS
689 s_smtp_tls.xport = &x;
695#if PC_ENABLE_SMTP_TLS
696 if (!pc_tls_client_session_begin(cfg->
host, tls_bio_send, tls_bio_recv))
702 while ((h = pc_tls_client_session_handshake()) == 0 && (int32_t)(x.deadline - millis()) > 0)
708 pc_tls_client_session_end();
712 rc =
smtp_run(cfg, msg, tls_send, tls_recv,
nullptr, &x);
713 pc_tls_client_session_end();
721 rc =
smtp_run(cfg, msg, xp_send, xp_recv, xp_starttls, &x);
725#if PC_ENABLE_SMTP_TLS
726 s_smtp_tls.xport =
nullptr;
void pc_base64_encode(const uint8_t *src, size_t src_len, char *dst)
Encode src_len bytes of src as Base64.
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int pc_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 pc_client_available(int)
Wire bytes currently buffered and ready to read.
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
User-facing configuration for ProtoCore.
#define PC_SMTP_MSG_MAX
Max size of the assembled DATA payload (headers + dot-stuffed body), bytes.
#define PC_SMTP_REPLY_MAX
Max size of one (possibly multi-line) server reply held while parsing, bytes.
#define PC_SMTP_LINE_MAX
Max length of one SMTP command / address line (bytes, incl. CRLF).
#define PC_SMTP_TIMEOUT_MS
SMTP connect / per-reply timeout in milliseconds.
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 (pc_client, plus TLS when cfg->tls)....
SmtpResult smtp_run(const SmtpConfig *cfg, const SmtpMessage *msg, SmtpSendFn send, SmtpRecvFn recv, SmtpStartTlsFn starttls, void *ctx)
Drive the full SMTP exchange over send / recv. Pure - no lwIP or TLS - so it is host-testable with a ...
@ SMTP_STARTTLS
connect in the clear, then upgrade in band (submission, port 587).
@ SMTP_TLS
implicit TLS from the first byte (SMTPS, port 465).
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_NO_STARTTLS
STARTTLS was required but the server did not advertise it.
@ SMTP_ERR_CONNECT
could not open the transport (DNS / connect)
int(* SmtpRecvFn)(void *ctx, uint8_t *buf, size_t cap)
bool(* SmtpStartTlsFn)(void *ctx)
Upgrade the live connection to TLS in place (RFC 3207), after the server's 220.
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Server address + credentials for one send. Addresses are bare (no angle brackets).
const char * user
AUTH LOGIN username (null or empty => skip AUTH)
SmtpSecurity security
how to secure the connection
const char * pass
AUTH LOGIN password.
const char * from
envelope sender + From: header address
uint16_t port
25 (plain) / 587 (STARTTLS) / 465 (implicit TLS)
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)
Bump-append target; ok latches false once an append would overflow cap.
Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).