mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00
449 lines
10 KiB
C
449 lines
10 KiB
C
/*
|
|
* Abstract SMTP support for libwebsockets
|
|
*
|
|
* Copyright (C) 2016-2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation:
|
|
* version 2.1 of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "core/private.h"
|
|
#include "abstract/private.h"
|
|
|
|
/** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */
|
|
typedef enum lwsgs_smtp_states {
|
|
LGSSMTP_IDLE, /**< awaiting new email */
|
|
LGSSMTP_CONNECTING, /**< opening tcp connection to MTA */
|
|
LGSSMTP_CONNECTED, /**< tcp connection to MTA is connected */
|
|
LGSSMTP_SENT_HELO, /**< sent the HELO */
|
|
LGSSMTP_SENT_FROM, /**< sent FROM */
|
|
LGSSMTP_SENT_TO, /**< sent TO */
|
|
LGSSMTP_SENT_DATA, /**< sent DATA request */
|
|
LGSSMTP_SENT_BODY, /**< sent the email body */
|
|
LGSSMTP_SENT_QUIT, /**< sent the session quit */
|
|
} lwsgs_smtp_states_t;
|
|
|
|
/** struct lws_email - abstract context for performing SMTP operations */
|
|
typedef struct lws_smtp_client {
|
|
struct lws_dll2_owner pending_owner;
|
|
|
|
const struct lws_abs *abs;
|
|
|
|
const char *helo;
|
|
|
|
lwsgs_smtp_states_t estate;
|
|
time_t email_connect_started;
|
|
|
|
time_t retry_interval;
|
|
time_t delivery_timeout;
|
|
|
|
size_t email_queue_max;
|
|
size_t max_content_size;
|
|
|
|
unsigned char send_pending:1;
|
|
} lws_smtp_client_t;
|
|
|
|
static const short retcodes[] = {
|
|
0, /* idle */
|
|
0, /* connecting */
|
|
220, /* connected */
|
|
250, /* helo */
|
|
250, /* from */
|
|
250, /* to */
|
|
354, /* data */
|
|
250, /* body */
|
|
221, /* quit */
|
|
};
|
|
|
|
static void
|
|
lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s)
|
|
{
|
|
lwsl_debug("%s: cli %p: state %d -> %d\n", __func__, c, c->estate, s);
|
|
c->estate = s;
|
|
}
|
|
|
|
static void
|
|
lws_smtp_client_kick_internal(lws_smtp_client_t *c)
|
|
{
|
|
lws_smtp_email_t *e;
|
|
lws_dll2_t *d;
|
|
char buf[64];
|
|
int n;
|
|
|
|
if (c->estate != LGSSMTP_IDLE)
|
|
return;
|
|
|
|
/* is there something to do? */
|
|
|
|
again:
|
|
d = lws_dll2_get_head(&c->pending_owner);
|
|
if (!d)
|
|
return;
|
|
|
|
e = lws_container_of(d, lws_smtp_email_t, list);
|
|
|
|
/* do we need to time out this guy? */
|
|
|
|
if ((time_t)lws_now_secs() - e->added > (time_t)c->delivery_timeout) {
|
|
lwsl_err("%s: timing out email\n", __func__);
|
|
lws_dll2_remove(&e->list);
|
|
n = lws_snprintf(buf, sizeof(buf), "0 Timed out retrying send");
|
|
e->done(e, buf, n);
|
|
|
|
if (lws_dll2_get_head(&c->pending_owner))
|
|
goto again;
|
|
|
|
return;
|
|
}
|
|
|
|
/* is it time for his retry yet? */
|
|
|
|
if (e->last_try &&
|
|
(time_t)lws_now_secs() - e->last_try < (time_t)c->retry_interval) {
|
|
/* no... send him to the tail */
|
|
lws_dll2_remove(&e->list);
|
|
lws_dll2_add_tail(&e->list, &c->pending_owner);
|
|
return;
|
|
}
|
|
|
|
/* ask the transport if we have a connection to the server ongoing */
|
|
|
|
if (c->abs->at->state(c->abs->ati)) {
|
|
/*
|
|
* there's a connection, it could be still trying to connect
|
|
* or established
|
|
*/
|
|
c->abs->at->ask_for_writeable(c->abs->ati);
|
|
|
|
return;
|
|
}
|
|
|
|
/* there's no existing connection */
|
|
|
|
lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING);
|
|
|
|
if (c->abs->at->client_conn(c->abs)) {
|
|
lwsl_err("%s: failed to connect\n", __func__);
|
|
|
|
return;
|
|
}
|
|
|
|
e->tries++;
|
|
e->last_try = lws_now_secs();
|
|
}
|
|
|
|
/*
|
|
* we became connected
|
|
*/
|
|
|
|
static int
|
|
lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)api;
|
|
|
|
lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)api;
|
|
lws_smtp_email_t *e;
|
|
lws_dll2_t *pd2;
|
|
int n;
|
|
|
|
pd2 = lws_dll2_get_head(&c->pending_owner);
|
|
if (!pd2)
|
|
return 0;
|
|
|
|
e = lws_container_of(pd2, lws_smtp_email_t, list);
|
|
if (!e)
|
|
return 0;
|
|
|
|
n = atoi((char *)buf);
|
|
if (n != retcodes[c->estate]) {
|
|
lwsl_notice("%s: bad response from server: %d (state %d) %.*s\n",
|
|
__func__, n, c->estate, (int)len, buf);
|
|
|
|
lws_dll2_remove(&e->list);
|
|
lws_dll2_add_tail(&e->list, &c->pending_owner);
|
|
lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
|
|
lws_smtp_client_kick_internal(c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (c->estate == LGSSMTP_SENT_QUIT) {
|
|
lwsl_debug("%s: done\n", __func__);
|
|
lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
|
|
|
|
lws_dll2_remove(&e->list);
|
|
if (e->done && e->done(e, "sent OK", 7))
|
|
return 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
c->send_pending = 1;
|
|
c->abs->at->ask_for_writeable(c->abs->ati);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)api;
|
|
char b[256 + LWS_PRE], *p = b + LWS_PRE;
|
|
lws_smtp_email_t *e;
|
|
lws_dll2_t *pd2;
|
|
int n;
|
|
|
|
pd2 = lws_dll2_get_head(&c->pending_owner);
|
|
if (!pd2)
|
|
return 0;
|
|
|
|
e = lws_container_of(pd2, lws_smtp_email_t, list);
|
|
if (!e)
|
|
return 0;
|
|
|
|
|
|
if (!c->send_pending)
|
|
return 0;
|
|
|
|
c->send_pending = 0;
|
|
|
|
lwsl_debug("%s: writing response for state %d\n", __func__, c->estate);
|
|
|
|
switch (c->estate) {
|
|
case LGSSMTP_CONNECTED:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->helo);
|
|
lws_smtp_client_state_transition(c, LGSSMTP_SENT_HELO);
|
|
break;
|
|
case LGSSMTP_SENT_HELO:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "MAIL FROM: <%s>\n",
|
|
e->email_from);
|
|
lws_smtp_client_state_transition(c, LGSSMTP_SENT_FROM);
|
|
break;
|
|
case LGSSMTP_SENT_FROM:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE,
|
|
"RCPT TO: <%s>\n", e->email_to);
|
|
lws_smtp_client_state_transition(c, LGSSMTP_SENT_TO);
|
|
break;
|
|
case LGSSMTP_SENT_TO:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "DATA\n");
|
|
lws_smtp_client_state_transition(c, LGSSMTP_SENT_DATA);
|
|
break;
|
|
case LGSSMTP_SENT_DATA:
|
|
p = (char *)e->payload;
|
|
n = strlen(e->payload);
|
|
lws_smtp_client_state_transition(c, LGSSMTP_SENT_BODY);
|
|
break;
|
|
case LGSSMTP_SENT_BODY:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "quit\n");
|
|
lws_smtp_client_state_transition(c, LGSSMTP_SENT_QUIT);
|
|
break;
|
|
case LGSSMTP_SENT_QUIT:
|
|
return 0;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
//puts(p);
|
|
c->abs->at->tx(c->abs->ati, (uint8_t *)p, n);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)api;
|
|
|
|
if (c)
|
|
lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)api;
|
|
|
|
lws_smtp_client_kick_internal(c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
lws_smtp_email_t *
|
|
lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len,
|
|
const char *sender, const char *recipient,
|
|
const char *extra, size_t extra_len, void *data,
|
|
int (*done)(struct lws_smtp_email *e,
|
|
void *buf, size_t len))
|
|
{
|
|
size_t ls = strlen(sender), lr = strlen(recipient);
|
|
lws_smtp_email_t *em;
|
|
char *p;
|
|
|
|
em = malloc(sizeof(*em) + payload_len + ls + lr + extra_len + 4);
|
|
if (!em) {
|
|
lwsl_err("OOM\n");
|
|
return NULL;
|
|
}
|
|
|
|
p = (char *)&em[1];
|
|
|
|
memset(em, 0, sizeof(*em));
|
|
|
|
em->data = data;
|
|
em->done = done;
|
|
|
|
em->email_from = p;
|
|
memcpy(p, sender, ls + 1);
|
|
p += ls + 1;
|
|
em->email_to = p;
|
|
memcpy(p, recipient, lr + 1);
|
|
p += lr + 1;
|
|
em->payload = p;
|
|
memcpy(p, payload, payload_len + 1);
|
|
p += payload_len + 1;
|
|
|
|
if (extra) {
|
|
em->extra = p;
|
|
memcpy(p, extra, extra_len + 1);
|
|
}
|
|
|
|
return em;
|
|
}
|
|
|
|
int
|
|
lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
|
|
|
|
if (c->pending_owner.count > c->email_queue_max) {
|
|
lwsl_err("%s: email queue at limit of %d\n", __func__,
|
|
(int)c->email_queue_max);
|
|
|
|
return 1;
|
|
}
|
|
|
|
e->added = lws_now_secs();
|
|
e->last_try = 0;
|
|
e->tries = 0;
|
|
|
|
lws_dll2_clear(&e->list);
|
|
lws_dll2_add_tail(&e->list, &c->pending_owner);
|
|
|
|
lws_smtp_client_kick_internal(c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_smtp_client_kick(lws_abs_t *instance)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
|
|
|
|
lws_smtp_client_kick_internal(c);
|
|
}
|
|
static int
|
|
lws_smtp_client_create(const lws_abs_t *ai)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)ai->api;
|
|
const lws_token_map_t *tm;
|
|
|
|
memset(c, 0, sizeof(*c));
|
|
|
|
c->abs = ai;
|
|
|
|
tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_V_HELO);
|
|
if (!tm) {
|
|
lwsl_err("%s: LTMI_PSMTP_V_HELO is required\n", __func__);
|
|
|
|
return 1;
|
|
}
|
|
c->helo = tm->u.value;
|
|
|
|
c->email_queue_max = 8;
|
|
c->retry_interval = 15 * 60;
|
|
c->delivery_timeout = 12 * 60 * 60;
|
|
|
|
tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_EMAIL_QUEUE_MAX);
|
|
if (tm)
|
|
c->email_queue_max = tm->u.lvalue;
|
|
tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_RETRY_INTERVAL);
|
|
if (tm)
|
|
c->retry_interval = tm->u.lvalue;
|
|
tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_DELIVERY_TIMEOUT);
|
|
if (tm)
|
|
c->delivery_timeout = tm->u.lvalue;
|
|
|
|
lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cleanup(struct lws_dll2 *d, void *user)
|
|
{
|
|
lws_smtp_email_t *e;
|
|
|
|
e = lws_container_of(d, lws_smtp_email_t, list);
|
|
if (e->done && e->done(e, "destroying", 10))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c)
|
|
{
|
|
lws_smtp_client_t *c = (lws_smtp_client_t *)*_c;
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
lws_dll2_foreach_safe(&c->pending_owner, NULL, cleanup);
|
|
|
|
/*
|
|
* We don't free anything because the abstract layer combined our
|
|
* allocation with that of the instance, and it will free the whole
|
|
* thing after this.
|
|
*/
|
|
|
|
*_c = NULL;
|
|
}
|
|
|
|
/* events the transport invokes (handled by abstract protocol) */
|
|
|
|
const lws_abs_protocol_t lws_abs_protocol_smtp = {
|
|
.name = "smtp",
|
|
.alloc = sizeof(lws_smtp_client_t),
|
|
|
|
.create = lws_smtp_client_create,
|
|
.destroy = lws_smtp_client_destroy,
|
|
|
|
.accept = lws_smtp_client_abs_accept,
|
|
.rx = lws_smtp_client_abs_rx,
|
|
.writeable = lws_smtp_client_abs_writeable,
|
|
.closed = lws_smtp_client_abs_closed,
|
|
.heartbeat = lws_smtp_client_abs_heartbeat,
|
|
};
|