1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/lib/abstract/protocols/smtp/smtp.c
2019-06-29 21:08:36 +01:00

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,
};