/* * Abstract SMTP support for libwebsockets - SMTP sequencer * * Copyright (C) 2016-2019 Andy Green * * 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; }