2 * Abstract SMTP support for libwebsockets
4 * Copyright (C) 2016-2019 Andy Green <andy@warmcat.com>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation:
9 * version 2.1 of the License.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 #include "private-lib-core.h"
23 #include "private-lib-abstract.h"
25 /** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */
26 typedef enum lwsgs_smtp_states {
27 LGSSMTP_IDLE, /**< awaiting new email */
28 LGSSMTP_CONNECTING, /**< opening tcp connection to MTA */
29 LGSSMTP_CONNECTED, /**< tcp connection to MTA is connected */
30 LGSSMTP_SENT_HELO, /**< sent the HELO */
31 LGSSMTP_SENT_FROM, /**< sent FROM */
32 LGSSMTP_SENT_TO, /**< sent TO */
33 LGSSMTP_SENT_DATA, /**< sent DATA request */
34 LGSSMTP_SENT_BODY, /**< sent the email body */
35 LGSSMTP_SENT_QUIT, /**< sent the session quit */
36 } lwsgs_smtp_states_t;
38 /** struct lws_email - abstract context for performing SMTP operations */
39 typedef struct lws_smtp_client {
40 struct lws_dll2_owner pending_owner;
42 const struct lws_abs *abs;
46 lwsgs_smtp_states_t estate;
47 time_t email_connect_started;
49 time_t retry_interval;
50 time_t delivery_timeout;
52 size_t email_queue_max;
53 size_t max_content_size;
55 unsigned char send_pending:1;
58 static const short retcodes[] = {
71 lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s)
73 lwsl_debug("%s: cli %p: state %d -> %d\n", __func__, c, c->estate, s);
78 lws_smtp_client_kick_internal(lws_smtp_client_t *c)
85 if (c->estate != LGSSMTP_IDLE)
88 /* is there something to do? */
91 d = lws_dll2_get_head(&c->pending_owner);
95 e = lws_container_of(d, lws_smtp_email_t, list);
97 /* do we need to time out this guy? */
99 if ((time_t)lws_now_secs() - e->added > (time_t)c->delivery_timeout) {
100 lwsl_err("%s: timing out email\n", __func__);
101 lws_dll2_remove(&e->list);
102 n = lws_snprintf(buf, sizeof(buf), "0 Timed out retrying send");
105 if (lws_dll2_get_head(&c->pending_owner))
111 /* is it time for his retry yet? */
114 (time_t)lws_now_secs() - e->last_try < (time_t)c->retry_interval) {
115 /* no... send him to the tail */
116 lws_dll2_remove(&e->list);
117 lws_dll2_add_tail(&e->list, &c->pending_owner);
121 /* ask the transport if we have a connection to the server ongoing */
123 if (c->abs->at->state(c->abs->ati)) {
125 * there's a connection, it could be still trying to connect
128 c->abs->at->ask_for_writeable(c->abs->ati);
133 /* there's no existing connection */
135 lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING);
137 if (c->abs->at->client_conn(c->abs)) {
138 lwsl_err("%s: failed to connect\n", __func__);
144 e->last_try = lws_now_secs();
148 * we became connected
152 lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api)
154 lws_smtp_client_t *c = (lws_smtp_client_t *)api;
156 lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED);
162 lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len)
164 lws_smtp_client_t *c = (lws_smtp_client_t *)api;
169 pd2 = lws_dll2_get_head(&c->pending_owner);
173 e = lws_container_of(pd2, lws_smtp_email_t, list);
177 n = atoi((char *)buf);
178 if (n != retcodes[c->estate]) {
179 lwsl_notice("%s: bad response from server: %d (state %d) %.*s\n",
180 __func__, n, c->estate, (int)len, buf);
182 lws_dll2_remove(&e->list);
183 lws_dll2_add_tail(&e->list, &c->pending_owner);
184 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
185 lws_smtp_client_kick_internal(c);
190 if (c->estate == LGSSMTP_SENT_QUIT) {
191 lwsl_debug("%s: done\n", __func__);
192 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
194 lws_dll2_remove(&e->list);
195 if (e->done && e->done(e, "sent OK", 7))
202 c->abs->at->ask_for_writeable(c->abs->ati);
208 lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget)
210 lws_smtp_client_t *c = (lws_smtp_client_t *)api;
211 char b[256 + LWS_PRE], *p = b + LWS_PRE;
216 pd2 = lws_dll2_get_head(&c->pending_owner);
220 e = lws_container_of(pd2, lws_smtp_email_t, list);
225 if (!c->send_pending)
230 lwsl_debug("%s: writing response for state %d\n", __func__, c->estate);
233 case LGSSMTP_CONNECTED:
234 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->helo);
235 lws_smtp_client_state_transition(c, LGSSMTP_SENT_HELO);
237 case LGSSMTP_SENT_HELO:
238 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "MAIL FROM: <%s>\n",
240 lws_smtp_client_state_transition(c, LGSSMTP_SENT_FROM);
242 case LGSSMTP_SENT_FROM:
243 n = lws_snprintf(p, sizeof(b) - LWS_PRE,
244 "RCPT TO: <%s>\n", e->email_to);
245 lws_smtp_client_state_transition(c, LGSSMTP_SENT_TO);
247 case LGSSMTP_SENT_TO:
248 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "DATA\n");
249 lws_smtp_client_state_transition(c, LGSSMTP_SENT_DATA);
251 case LGSSMTP_SENT_DATA:
252 p = (char *)e->payload;
253 n = strlen(e->payload);
254 lws_smtp_client_state_transition(c, LGSSMTP_SENT_BODY);
256 case LGSSMTP_SENT_BODY:
257 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "quit\n");
258 lws_smtp_client_state_transition(c, LGSSMTP_SENT_QUIT);
260 case LGSSMTP_SENT_QUIT:
268 c->abs->at->tx(c->abs->ati, (uint8_t *)p, n);
274 lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api)
276 lws_smtp_client_t *c = (lws_smtp_client_t *)api;
279 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
285 lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api)
287 lws_smtp_client_t *c = (lws_smtp_client_t *)api;
289 lws_smtp_client_kick_internal(c);
295 lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len,
296 const char *sender, const char *recipient,
297 const char *extra, size_t extra_len, void *data,
298 int (*done)(struct lws_smtp_email *e,
299 void *buf, size_t len))
301 size_t ls = strlen(sender), lr = strlen(recipient);
302 lws_smtp_email_t *em;
305 em = malloc(sizeof(*em) + payload_len + ls + lr + extra_len + 4);
313 memset(em, 0, sizeof(*em));
319 memcpy(p, sender, ls + 1);
322 memcpy(p, recipient, lr + 1);
325 memcpy(p, payload, payload_len + 1);
326 p += payload_len + 1;
330 memcpy(p, extra, extra_len + 1);
337 lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e)
339 lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
341 if (c->pending_owner.count > c->email_queue_max) {
342 lwsl_err("%s: email queue at limit of %d\n", __func__,
343 (int)c->email_queue_max);
348 e->added = lws_now_secs();
352 lws_dll2_clear(&e->list);
353 lws_dll2_add_tail(&e->list, &c->pending_owner);
355 lws_smtp_client_kick_internal(c);
361 lws_smtp_client_kick(lws_abs_t *instance)
363 lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
365 lws_smtp_client_kick_internal(c);
368 lws_smtp_client_create(const lws_abs_t *ai)
370 lws_smtp_client_t *c = (lws_smtp_client_t *)ai->api;
371 const lws_token_map_t *tm;
373 memset(c, 0, sizeof(*c));
377 tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_V_HELO);
379 lwsl_err("%s: LTMI_PSMTP_V_HELO is required\n", __func__);
383 c->helo = tm->u.value;
385 c->email_queue_max = 8;
386 c->retry_interval = 15 * 60;
387 c->delivery_timeout = 12 * 60 * 60;
389 tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_EMAIL_QUEUE_MAX);
391 c->email_queue_max = tm->u.lvalue;
392 tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_RETRY_INTERVAL);
394 c->retry_interval = tm->u.lvalue;
395 tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_DELIVERY_TIMEOUT);
397 c->delivery_timeout = tm->u.lvalue;
399 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
405 cleanup(struct lws_dll2 *d, void *user)
409 e = lws_container_of(d, lws_smtp_email_t, list);
410 if (e->done && e->done(e, "destroying", 10))
417 lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c)
419 lws_smtp_client_t *c = (lws_smtp_client_t *)*_c;
424 lws_dll2_foreach_safe(&c->pending_owner, NULL, cleanup);
427 * We don't free anything because the abstract layer combined our
428 * allocation with that of the instance, and it will free the whole
435 /* events the transport invokes (handled by abstract protocol) */
437 const lws_abs_protocol_t lws_abs_protocol_smtp = {
439 .alloc = sizeof(lws_smtp_client_t),
441 .create = lws_smtp_client_create,
442 .destroy = lws_smtp_client_destroy,
444 .accept = lws_smtp_client_abs_accept,
445 .rx = lws_smtp_client_abs_rx,
446 .writeable = lws_smtp_client_abs_writeable,
447 .closed = lws_smtp_client_abs_closed,
448 .heartbeat = lws_smtp_client_abs_heartbeat,