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