You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

321 lines
7.1 KiB

/*
* Abstract SMTP support for libwebsockets - SMTP sequencer
*
* 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
*
* This sequencer sits above the abstract protocol, and manages queueing,
* retrying mail transmission, and retry limits.
*
* Having the sequencer means that, eg, we can manage retries after complete
* connection failure.
*
* Connections to the smtp server are serialized
*/
#include "private-lib-core.h"
#include "private-lib-abstract-protocols-smtp.h"
typedef enum {
LSMTPSS_DISCONNECTED,
LSMTPSS_CONNECTING,
LSMTPSS_CONNECTED,
LSMTPSS_BUSY,
} smtpss_connstate_t;
typedef struct lws_smtp_sequencer {
struct lws_dll2_owner emails_owner; /* email queue */
lws_abs_t *abs, *instance;
lws_smtp_sequencer_args_t args;
struct lws_sequencer *seq;
smtpss_connstate_t connstate;
time_t email_connect_started;
/* holds the HELO for the smtp protocol to consume */
lws_token_map_t apt[3];
} lws_smtp_sequencer_t;
/* sequencer messages specific to this sequencer */
enum {
SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
SEQ_MSG_CLIENT_DONE,
};
/*
* We're going to bind to the raw-skt transport, so tell that what we want it
* to connect to
*/
static const lws_token_map_t smtp_rs_transport_tokens[] = {
{
.u = { .value = "127.0.0.1" },
.name_index = LTMI_PEER_V_DNS_ADDRESS,
}, {
.u = { .lvalue = 25 },
.name_index = LTMI_PEER_LV_PORT,
}, {
}
};
static void
lws_smtpc_kick_internal(lws_smtp_sequencer_t *s)
{
lws_smtp_email_t *e;
lws_dll2_t *d;
char buf[64];
int n;
lws_dll2_t *pd2;
pd2 = lws_dll2_get_head(&s->emails_owner);
if (!pd2)
return;
e = lws_container_of(pd2, lws_smtp_email_t, list);
if (!e)
return;
/* Is there something to do? If so, we need a connection... */
if (s->connstate == LSMTPSS_DISCONNECTED) {
s->apt[0].u.value = s->args.helo;
s->apt[0].name_index = LTMI_PSMTP_V_HELO;
s->apt[1].u.value = (void *)e;
s->apt[1].name_index = LTMI_PSMTP_V_LWS_SMTP_EMAIL_T;
/*
* create and connect the smtp protocol + transport
*/
s->abs = lws_abstract_alloc(s->args.vhost, NULL, "smtp.raw_skt",
s->apt, smtp_rs_transport_tokens,
s->seq, NULL);
if (!s->abs)
return;
s->instance = lws_abs_bind_and_create_instance(s->abs);
if (!s->instance) {
lws_abstract_free(&s->abs);
lwsl_err("%s: failed to create SMTP client\n", __func__);
goto bail1;
}
s->connstate = LSMTPSS_CONNECTING;
lws_seq_timeout_us(s->seq, 10 * LWS_USEC_PER_SEC);
return;
}
/* ask the transport if we have a connection to the server ongoing */
if (s->abs->at->state(s->abs->ati)) {
/*
* there's a connection, it could be still trying to connect
* or established
*/
s->abs->at->ask_for_writeable(s->abs->ati);
return;
}
/* there's no existing connection */
lws_smtpc_state_transition(c, LGSSMTP_CONNECTING);
if (s->abs->at->client_conn(s->abs)) {
lwsl_err("%s: failed to connect\n", __func__);
return;
}
e->tries++;
e->last_try = lws_now_secs();
}
/*
* The callback we get from the smtp protocol... we use this to drive
* decisions about destroy email, retry and fail.
*
* Sequencer will handle it via the event loop.
*/
static int
email_result(void *e, void *d, int disp, void *b, size_t l)
{
lws_smtp_sequencer_t *s = (lws_smtp_sequencer_t *)d;
lws_sequencer_event(s->seq, LWSSEQ_USER_BASE + disp, e);
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);
lws_dll2_remove(d);
lws_free(e);
return 0;
}
static lws_seq_cb_return_t
smtp_sequencer_cb(struct lws_sequencer *seq, void *user, int event, void *data)
{
struct lws_smtp_sequencer_t *s = (struct lws_smtp_sequencer_t *)user;
switch ((int)event) {
case LWSSEQ_CREATED: /* our sequencer just got started */
lwsl_notice("%s: %s: created\n", __func__,
lws_sequencer_name(seq));
s->connstate = LSMTPSS_DISCONNECTED;
s->state = 0; /* first thing we'll do is the first url */
goto step;
case LWSSEQ_DESTROYED:
lws_dll2_foreach_safe(&s->pending_owner, NULL, cleanup);
break;
case LWSSEQ_TIMED_OUT:
lwsl_notice("%s: LWSSEQ_TIMED_OUT\n", __func__);
break;
case LWSSEQ_USER_BASE + LWS_SMTP_DISPOSITION_SENT:
lws_smtpc_free_email(data);
break;
case LWSSEQ_WSI_CONNECTED:
s->connstate = LSMTPSS_CONNECTED;
lws_smtpc_kick_internal(s);
break;
case LWSSEQ_WSI_CONN_FAIL:
case LWSSEQ_WSI_CONN_CLOSE:
s->connstate = LSMTPSS_DISCONNECTED;
lws_smtpc_kick_internal(s);
break;
case SEQ_MSG_SENT:
break;
default:
break;
}
return LWSSEQ_RET_CONTINUE;
}
/*
* Creates an lws_sequencer to manage the test sequence
*/
lws_smtp_sequencer_t *
lws_smtp_sequencer_create(const lws_smtp_sequencer_args_t *args)
{
lws_smtp_sequencer_t *s;
struct lws_sequencer *seq;
/*
* Create a sequencer in the event loop to manage the SMTP queue
*/
seq = lws_sequencer_create(args->vhost->context, 0,
sizeof(lws_smtp_sequencer_t), (void **)&s,
smtp_sequencer_cb, "smtp-seq");
if (!seq) {
lwsl_err("%s: unable to create sequencer\n", __func__);
return NULL;
}
s->abs = *args->abs;
s->args = *args;
s->seq = seq;
/* set defaults in our copy of the args */
if (!s->args.helo[0])
strcpy(s->args.helo, "default-helo");
if (!s->args.email_queue_max)
s->args.email_queue_max = 8;
if (!s->args.retry_interval)
s->args.retry_interval = 15 * 60;
if (!s->args.delivery_timeout)
s->args.delivery_timeout = 12 * 60 * 60;
return s;
}
void
lws_smtp_sequencer_destroy(lws_smtp_sequencer_t *s)
{
/* sequencer destruction destroys all assets */
lws_sequencer_destroy(&s->seq);
}
int
lws_smtpc_add_email(lws_smtp_sequencer_t *s, const char *payload,
size_t payload_len, const char *sender,
const char *recipient, void *data, lws_smtp_cb_t done)
{
lws_smtp_email_t *e;
if (s->emails_owner.count > s->args.email_queue_max) {
lwsl_err("%s: email queue at limit of %d\n", __func__,
(int)s->args.email_queue_max);
return 1;
}
if (!done)
return 1;
e = malloc(sizeof(*e) + payload_len + 1);
if (!e)
return 1;
memset(e, 0, sizeof(*e));
e->data = data;
e->done = done;
lws_strncpy(e->from, sender, sizeof(e->from));
lws_strncpy(e->to, recipient, sizeof(e->to));
memcpy((char *)&e[1], payload, payload_len + 1);
e->added = lws_now_secs();
e->last_try = 0;
e->tries = 0;
lws_dll2_clear(&e->list);
lws_dll2_add_tail(&e->list, &s->emails_owner);
lws_smtpc_kick_internal(s);
return 0;
}