diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c0e7cabf..3c9d53c3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1447,7 +1447,9 @@ endif() if (LWS_WITH_SMTP) list(APPEND SOURCES - lib/abstract/protocols/smtp/smtp.c) + lib/abstract/protocols/smtp/smtp.c + lib/abstract/protocols/smtp/smtp-sequencer.c + ) endif() if (LWS_WITH_RANGES) diff --git a/READMEs/README.lws_sequencer.md b/READMEs/README.lws_sequencer.md index fab057352..9a3b676d6 100644 --- a/READMEs/README.lws_sequencer.md +++ b/READMEs/README.lws_sequencer.md @@ -1,4 +1,4 @@ -# `lws_sequencer_t` introduction +# `struct lws_sequencer` introduction Often a single network action like a client GET is just part of a larger series of actions, perhaps involving different connections. @@ -10,7 +10,7 @@ loop thread. ![lws_sequencer](/doc-assets/lws_sequencer.svg) -`lws_sequencer_t` provides a generic way to stage multi-step +`struct lws_sequencer` provides a generic way to stage multi-step operations from inside the event loop. Because it participates in the event loop similar to a wsi, it always operates from the service thread context and can access structures that share the @@ -48,7 +48,7 @@ only sends the message. This allows the timeout to be used to, eg, wait out a retry cooloff period and then start the retry when the `LWSSEQ_TIMED_OUT` is received, according to the state of the sequencer. -## Creating an `lws_sequencer_t` +## Creating an `struct lws_sequencer` ``` typedef struct lws_seq_info { @@ -63,7 +63,7 @@ typedef struct lws_seq_info { ``` ``` -lws_sequencer_t * +struct lws_sequencer * lws_sequencer_create(lws_seq_info_t *info); ``` @@ -77,7 +77,7 @@ typedef int (*lws_seq_event_cb)(struct lws_sequencer *seq, void *user_data, lws_seq_events_t event, void *data); ``` -`lws_sequencer_t` objects are private to lws and opaque to the user. A small +`struct lws_sequencer` objects are private to lws and opaque to the user. A small set of apis lets you perform operations on the pointer returned by the create api. @@ -95,7 +95,7 @@ callback from inside the event loop at a rate of one per event loop wait. ## Destroying sequencers -`lws_sequencer_t` objects are cleaned up during context destruction if they are +`struct lws_sequencer` objects are cleaned up during context destruction if they are still around. Normally the sequencer callback receives a queued message that diff --git a/include/libwebsockets/abstract/abstract.h b/include/libwebsockets/abstract/abstract.h index c3b65bced..c312a1f5a 100644 --- a/include/libwebsockets/abstract/abstract.h +++ b/include/libwebsockets/abstract/abstract.h @@ -42,8 +42,7 @@ typedef struct lws_token_map { /* * The indvidual protocols and transports define their own name_index-es which * are meaningful to them. Define index 0 globally as the end of an array of - * them, and separate the ones used for protocols and transport so we can - * sanity check they are at least in the correct category. + * them, and provide bases so user protocol and transport ones don't overlap. */ enum { @@ -56,6 +55,7 @@ enum { struct lws_abs_transport; struct lws_abs_protocol; +typedef struct lws_abs lws_abs_t; LWS_VISIBLE LWS_EXTERN const lws_token_map_t * lws_abs_get_token(const lws_token_map_t *token_map, short name_index); @@ -67,28 +67,40 @@ lws_abs_get_token(const lws_token_map_t *token_map, short name_index); typedef void lws_abs_transport_inst_t; typedef void lws_abs_protocol_inst_t; -typedef struct lws_abs { - void *user; - struct lws_vhost *vh; +/** + * lws_abstract_alloc() - allocate and configure an lws_abs_t + * + * \param vhost: the struct lws_vhost to bind to + * \param user: opaque user pointer + * \param abstract_path: "protocol.transport" names + * \param ap_tokens: tokens for protocol options + * \param at_tokens: tokens for transport + * \param seq: optional sequencer we should bind to, or NULL + * \param opaque_user_data: data given in sequencer callback, if any + * + * Returns an allocated lws_abs_t pointer set up with the other arguments. + * + * Doesn't create a connection instance, just allocates the lws_abs_t and + * sets it up with the arguments. + * + * Returns NULL is there's any problem. + */ +LWS_VISIBLE LWS_EXTERN lws_abs_t * +lws_abstract_alloc(struct lws_vhost *vhost, void *user, + const char *abstract_path, const lws_token_map_t *ap_tokens, + const lws_token_map_t *at_tokens, struct lws_sequencer *seq, + void *opaque_user_data); - const struct lws_abs_protocol *ap; - const lws_token_map_t *ap_tokens; - const struct lws_abs_transport *at; - const lws_token_map_t *at_tokens; +/** + * lws_abstract_free() - free an allocated lws_abs_t + * + * \param pabs: pointer to the lws_abs_t * to free + * + * Frees and sets the pointer to NULL. + */ - struct lws_sequencer *seq; - void *opaque_user_data; - - /* - * These are filled in by lws_abs_bind_and_create_instance() in the - * instance copy. They do not need to be set when creating the struct - * for use by lws_abs_bind_and_create_instance() - */ - - struct lws_dll2 abstract_instances; - lws_abs_transport_inst_t *ati; - lws_abs_protocol_inst_t *api; -} lws_abs_t; +LWS_VISIBLE LWS_EXTERN void +lws_abstract_free(lws_abs_t **pabs); /** * lws_abs_bind_and_create_instance - use an abstract protocol and transport diff --git a/include/libwebsockets/abstract/protocols.h b/include/libwebsockets/abstract/protocols.h index 09afc2cef..77900203d 100644 --- a/include/libwebsockets/abstract/protocols.h +++ b/include/libwebsockets/abstract/protocols.h @@ -22,20 +22,51 @@ * IN THE SOFTWARE. */ +/* + * Information about how this protocol handles multiple use of connections. + * + * .flags of 0 indicates each connection must start with a fresh transport. + * + * Flags can be used to indicate the protocol itself supports different + * kinds of multiple use. However the actual use or not of these may depend on + * negotiation with the remote peer. + * + * LWS_AP_FLAG_PIPELINE_TRANSACTIONS: other instances can be queued on one + * with an existing connection and get a + * chance to "hot take over" the existing + * transport in turn, like h1 keepalive + * pipelining + * + * LWS_AP_FLAG_MUXABLE_STREAM: an existing connection can absorb more child + * connections and mux them as separate child + * streams ongoing, like h2 + */ + +enum { + LWS_AP_FLAG_PIPELINE_TRANSACTIONS = (1 << 0), + LWS_AP_FLAG_MUXABLE_STREAM = (1 << 1), +}; + typedef struct lws_abs_protocol { const char *name; int alloc; + int flags; - int (*create)(const struct lws_abs *ai); - void (*destroy)(lws_abs_protocol_inst_t **d); + int (*create)(const struct lws_abs *ai); + void (*destroy)(lws_abs_protocol_inst_t **d); + int (*compare)(lws_abs_t *abs1, lws_abs_t *abs2); /* events the transport invokes (handled by abstract protocol) */ - int (*accept)(lws_abs_protocol_inst_t *d); - int (*rx)(lws_abs_protocol_inst_t *d, uint8_t *buf, size_t len); - int (*writeable)(lws_abs_protocol_inst_t *d, size_t budget); - int (*closed)(lws_abs_protocol_inst_t *d); - int (*heartbeat)(lws_abs_protocol_inst_t *d); + int (*accept)(lws_abs_protocol_inst_t *d); + int (*rx)(lws_abs_protocol_inst_t *d, const uint8_t *b, size_t l); + int (*writeable)(lws_abs_protocol_inst_t *d, size_t budget); + int (*closed)(lws_abs_protocol_inst_t *d); + int (*heartbeat)(lws_abs_protocol_inst_t *d); + + /* as parent, we get a notification a new child / queue entry + * bound to us... this is the parent lws_abs_t as arg */ + int (*child_bind)(lws_abs_t *abs); } lws_abs_protocol_t; /** @@ -54,3 +85,4 @@ lws_abs_protocol_get_by_name(const char *name); */ #include + diff --git a/include/libwebsockets/abstract/protocols/smtp.h b/include/libwebsockets/abstract/protocols/smtp.h index 47f87fe71..90b69c6e8 100644 --- a/include/libwebsockets/abstract/protocols/smtp.h +++ b/include/libwebsockets/abstract/protocols/smtp.h @@ -33,65 +33,57 @@ * 25 and able to send email using the "mail" commandline app. Usually distro * MTAs are configured for this by default. * - * It runs via its own libuv events if initialized (which requires giving it - * a libuv loop to attach to). - * - * It operates using three callbacks, on_next() queries if there is a new email - * to send, on_get_body() asks for the body of the email, and on_sent() is - * called after the email is successfully sent. - * - * To use it - * - * - create an lws_email struct - * - * - initialize data, loop, the email_* strings, max_content_size and - * the callbacks - * - * - call lws_email_init() - * - * When you have at least one email to send, call lws_email_check() to - * schedule starting to send it. + * You can either use the abstract protocol layer directly, or instead use the + * provided smtp sequencer... this takes care of creating the protocol + * connections, and provides and email queue and retry management. */ //@{ + #if defined(LWS_WITH_SMTP) enum { LTMI_PSMTP_V_HELO = LTMI_PROTOCOL_BASE, /* u.value */ - LTMI_PSMTP_LV_RETRY_INTERVAL, /* u.lvalue */ - LTMI_PSMTP_LV_DELIVERY_TIMEOUT, /* u.lvalue */ - LTMI_PSMTP_LV_EMAIL_QUEUE_MAX, /* u.lvalue */ - LTMI_PSMTP_LV_MAX_CONTENT_SIZE, /* u.lvalue */ + + LTMI_PSMTP_V_LWS_SMTP_EMAIL_T, /* u.value */ }; -typedef struct lws_smtp_client lws_smtp_client_t; -typedef struct lws_abs lws_abs_t; +enum { + LWS_SMTP_DISPOSITION_SENT, + LWS_SMTP_DISPOSITION_FAILED, + LWS_SMTP_DISPOSITION_FAILED_DESTROY +}; -typedef struct lws_smtp_email { - struct lws_dll2 list; +typedef struct lws_smtp_sequencer_args { + const char helo[32]; + struct lws_vhost *vhost; + time_t retry_interval; + time_t delivery_timeout; + size_t email_queue_max; + size_t max_content_size; +} lws_smtp_sequencer_args_t; - void *data; - void *extra; +typedef struct lws_smtp_sequencer lws_smtp_sequencer_t; +typedef struct lws_smtp_email lws_smtp_email_t; - time_t added; - time_t last_try; +LWS_VISIBLE LWS_EXTERN lws_smtp_sequencer_t * +lws_smtp_sequencer_create(const lws_smtp_sequencer_args_t *args); - const char *email_from; - const char *email_to; - const char *payload; - - int (*done)(struct lws_smtp_email *e, void *buf, size_t len); - - int tries; -} lws_smtp_email_t; +LWS_VISIBLE LWS_EXTERN void +lws_smtp_sequencer_destroy(lws_smtp_sequencer_t *s); +typedef int (*lws_smtp_cb_t)(void *e, void *d, int disp, const void *b, size_t l); +typedef struct lws_smtp_email lws_smtp_email_t; /** - * lws_smtp_client_alloc_email_helper() - Allocates and inits an email object + * lws_smtpc_add_email() - Allocates and queues an email object * + * \param s: smtp sequencer to queue on * \param payload: the email payload string, with headers and terminating . * \param payload_len: size in bytes of the payload string * \param sender: the sender name and email * \param recipient: the recipient name and email + * \param data: opaque user data returned in the done callback + * \param done: callback called when the email send succeeded or failed * * Allocates an email object and copies the payload, sender and recipient into * it and initializes it. Returns NULL if OOM, otherwise the allocated email @@ -103,33 +95,21 @@ typedef struct lws_smtp_email { * The done() callback must free the email object. It doesn't have to free any * individual members. */ -LWS_VISIBLE LWS_EXTERN 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)); +LWS_VISIBLE LWS_EXTERN 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_client_add_email() - Add email to the list of ones being sent + * lws_smtpc_free_email() - Add email to the list of ones being sent * - * \param instance: smtp client + transport * \param e: email to queue for sending on \p c * * Adds an email to the linked-list of emails to send */ LWS_VISIBLE LWS_EXTERN int -lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e); +lws_smtpc_free_email(lws_smtp_email_t *e); -/** - * lws_smtp_client_kick() - Request check for new email - * - * \param instance: instance to kick - * - * Gives smtp client a chance to move things on - */ -LWS_VISIBLE LWS_EXTERN void -lws_smtp_client_kick(lws_abs_t *instance); #endif //@} diff --git a/include/libwebsockets/abstract/transports.h b/include/libwebsockets/abstract/transports.h index 20111952c..e9d7aa568 100644 --- a/include/libwebsockets/abstract/transports.h +++ b/include/libwebsockets/abstract/transports.h @@ -30,9 +30,12 @@ typedef struct lws_abs_transport { const char *name; int alloc; - int (*create)(struct lws_abs *abs); + int (*create)(lws_abs_t *abs); void (*destroy)(lws_abs_transport_inst_t **d); + /* check if the transport settings for these connections are the same */ + int (*compare)(lws_abs_t *abs1, lws_abs_t *abs2); + /* events the abstract protocol invokes (handled by transport) */ int (*tx)(lws_abs_transport_inst_t *d, uint8_t *buf, size_t len); diff --git a/lib/abstract/abstract.c b/lib/abstract/abstract.c index 9f40b8257..cbf3db2fe 100644 --- a/lib/abstract/abstract.c +++ b/lib/abstract/abstract.c @@ -30,18 +30,23 @@ extern const lws_abs_transport_t lws_abs_transport_cli_raw_skt, #if defined(LWS_WITH_SMTP) extern const lws_abs_protocol_t lws_abs_protocol_smtp; #endif +#if defined(LWS_WITH_MQTT) +extern const lws_abs_protocol_t lws_abs_protocol_mqttc; +#endif static const lws_abs_transport_t * const available_abs_transports[] = { &lws_abs_transport_cli_raw_skt, &lws_abs_transport_cli_unit_test, }; -/* HACK: microsoft compiler can't handle zero length array definition */ -#if defined(LWS_WITH_SMTP) +#if defined(LWS_WITH_ABSTRACT) static const lws_abs_protocol_t * const available_abs_protocols[] = { #if defined(LWS_WITH_SMTP) &lws_abs_protocol_smtp, #endif +#if defined(LWS_WITH_MQTT) + &lws_abs_protocol_mqttc, +#endif }; #endif @@ -62,7 +67,7 @@ lws_abs_transport_get_by_name(const char *name) const lws_abs_protocol_t * lws_abs_protocol_get_by_name(const char *name) { -#if defined(LWS_WITH_SMTP) +#if defined(LWS_WITH_ABSTRACT) int n; for (n = 0; n < (int)LWS_ARRAY_SIZE(available_abs_protocols); n++) @@ -89,20 +94,59 @@ lws_abs_get_token(const lws_token_map_t *token_map, short name_index) return NULL; } -void -lws_abs_destroy_instance(lws_abs_t **ai) +static int +lws_abstract_compare_connection(lws_abs_t *abs1, lws_abs_t *abs2) { - lws_abs_t *a = *ai; + /* it has to be using the same protocol */ + if (abs1->ap != abs2->ap) + return 1; - if (a->api) - a->ap->destroy(&a->api); - if (a->ati) - a->at->destroy(&a->ati); + /* protocol has to allow some kind of binding */ + if (!abs1->ap->flags) + return 1; - lws_dll2_remove(&a->abstract_instances); + /* it has to be using the same transport */ + if (abs1->at != abs2->at) + return 1; - *ai = NULL; - free(a); + /* + * The transport must feel the endpoint and conditions in use match the + * requested endpoint and conditions... and the transport type must be + * willing to allow it + */ + if (abs1->at->compare(abs1, abs2)) + return 1; + + /* + * The protocol must feel they are in compatible modes if any + * (and the protocol type must be willing to allow it) + */ + if (abs1->ap->compare(abs1, abs2)) + return 1; + + /* + * If no objection by now, we can say there's already a comparable + * connection and both the protocol and transport feel we can make + * use of it. + */ + + return 0; +} + +static int +find_compatible(struct lws_dll2 *d, void *user) +{ + lws_abs_t *ai1 = (lws_abs_t *)user, + *ai2 = lws_container_of(d, lws_abs_t, abstract_instances); + + if (!lws_abstract_compare_connection(ai1, ai2)) { + /* we can bind to it */ + lws_dll2_add_tail(&ai1->bound, &ai2->children_owner); + + return 1; + } + + return 0; } lws_abs_t * @@ -110,6 +154,7 @@ lws_abs_bind_and_create_instance(const lws_abs_t *abs) { size_t size = sizeof(lws_abs_t) + abs->ap->alloc + abs->at->alloc; lws_abs_t *ai; + int n; /* * since we know we will allocate the lws_abs_t, the protocol's @@ -124,10 +169,27 @@ lws_abs_bind_and_create_instance(const lws_abs_t *abs) ai->ati = NULL; ai->api = (char *)ai + sizeof(lws_abs_t); - if (ai->ap->create(ai)) { - ai->api = NULL; - goto bail; - } + + if (!ai->ap->flags) /* protocol only understands single connections */ + goto fresh; + + lws_vhost_lock(ai->vh); /* ----------------------------------- vh { */ + + /* + * Let's have a look for any already-connected transport we can use + */ + + n = lws_dll2_foreach_safe(&ai->vh->abstract_instances_owner, ai, + find_compatible); + + lws_vhost_unlock(ai->vh); /* } vh --------------------------------- */ + + if (n) + goto vh_list_add; + + /* there's no existing connection doing what we want */ + +fresh: ai->ati = (char *)ai->api + abs->ap->alloc; if (ai->at->create(ai)) { @@ -135,12 +197,36 @@ lws_abs_bind_and_create_instance(const lws_abs_t *abs) goto bail; } +vh_list_add: /* add us to the vhost's dll2 of instances */ lws_dll2_clear(&ai->abstract_instances); lws_dll2_add_head(&ai->abstract_instances, &ai->vh->abstract_instances_owner); + if (ai->ap->create(ai)) { + ai->api = NULL; + goto bail; + } + + if (ai->bound.owner) { /* we are a piggybacker */ + lws_abs_t *ai2 = lws_container_of(ai->bound.owner, lws_abs_t, + children_owner); + /* + * Provide an 'event' in the parent context to start handling + * the bind if it's otherwise idle. We give the parent abs + * because we don't know if we're "next" or whatever. Just that + * a child joined him and he should look into his child + * situation in case he was waiting for one to appear. + */ + if (ai2->ap->child_bind(ai2)) { + lwsl_info("%s: anticpated child bind fail\n", __func__); + lws_dll2_remove(&ai->bound); + + goto bail; + } + } + return ai; bail: @@ -148,3 +234,122 @@ bail: return NULL; } + +/* + * We get called to clean up each child that was still bound to a parent + * at the time the parent is getting destroyed. + */ + +static void +__lws_abs_destroy_instance2(lws_abs_t **ai) +{ + lws_abs_t *a = *ai; + + if (a->api) + a->ap->destroy(&a->api); + if (a->ati) + a->at->destroy(&a->ati); + + lws_dll2_remove(&a->abstract_instances); + + *ai = NULL; + free(a); +} + +static int +__reap_children(struct lws_dll2 *d, void *user) +{ + lws_abs_t *ac = lws_container_of(d, lws_abs_t, bound); + + lws_dll2_foreach_safe(&ac->children_owner, NULL, __reap_children); + + /* then destroy ourselves */ + + __lws_abs_destroy_instance2(&ac); + + return 0; +} + +void +lws_abs_destroy_instance(lws_abs_t **ai) +{ + lws_abs_t *a = *ai; + + /* destroy child instances that are bound to us first... */ + + lws_vhost_lock(a->vh); /* ----------------------------------- vh { */ + + lws_dll2_foreach_safe(&a->children_owner, NULL, __reap_children); + + /* ...then destroy ourselves */ + + __lws_abs_destroy_instance2(ai); + + lws_vhost_unlock(a->vh); /* } vh --------------------------------- */ +} + +lws_abs_t * +lws_abstract_alloc(struct lws_vhost *vhost, void *user, + const char *abstract_path, const lws_token_map_t *ap_tokens, + const lws_token_map_t *at_tokens, struct lws_sequencer *seq, + void *opaque_user_data) +{ + lws_abs_t *abs = lws_zalloc(sizeof(*abs), __func__); + struct lws_tokenize ts; + lws_tokenize_elem e; + char tmp[30]; + + if (!abs) + return NULL; + + lws_tokenize_init(&ts, abstract_path, LWS_TOKENIZE_F_MINUS_NONTERM); + + e = lws_tokenize(&ts); + if (e != LWS_TOKZE_TOKEN) + goto abs_path_problem; + + if (lws_tokenize_cstr(&ts, tmp, sizeof(tmp))) + goto abs_path_problem; + + abs->ap = lws_abs_protocol_get_by_name(tmp); + if (!abs->ap) + goto abs_path_problem; + + e = lws_tokenize(&ts); + if (e != LWS_TOKZE_DELIMITER) + goto abs_path_problem; + + e = lws_tokenize(&ts); + if (e != LWS_TOKZE_TOKEN) + goto abs_path_problem; + + if (lws_tokenize_cstr(&ts, tmp, sizeof(tmp))) + goto abs_path_problem; + + abs->at = lws_abs_transport_get_by_name(tmp); + if (!abs->at) + goto abs_path_problem; + + abs->vh = vhost; + abs->ap_tokens = ap_tokens; + abs->at_tokens = at_tokens; + abs->seq = seq; + abs->opaque_user_data = opaque_user_data; + + lwsl_info("%s: allocated %s\n", __func__, abstract_path); + + return abs; + +abs_path_problem: + lwsl_err("%s: bad abs path '%s'\n", __func__, abstract_path); + lws_free_set_NULL(abs); + + return NULL; +} + +void +lws_abstract_free(lws_abs_t **pabs) +{ + if (*pabs) + lws_free_set_NULL(*pabs); +} diff --git a/lib/abstract/private-lib-abstract.h b/lib/abstract/private-lib-abstract.h index 90afa2987..0204b5032 100644 --- a/lib/abstract/private-lib-abstract.h +++ b/lib/abstract/private-lib-abstract.h @@ -22,4 +22,34 @@ * IN THE SOFTWARE. */ +#if !defined(__PRIVATE_LIB_ABSTRACT_H__) +#define __PRIVATE_LIB_ABSTRACT_H__ + +typedef struct lws_token_map lws_token_map_t; +typedef void lws_abs_transport_inst_t; +typedef void lws_abs_protocol_inst_t; + +typedef struct lws_abs { + void *user; + struct lws_vhost *vh; + + const struct lws_abs_protocol *ap; + const lws_token_map_t *ap_tokens; + const struct lws_abs_transport *at; + const lws_token_map_t *at_tokens; + + struct lws_sequencer *seq; + void *opaque_user_data; + + /* vh lock */ + struct lws_dll2_owner children_owner; /* our children / queue */ + /* vh lock */ + struct lws_dll2 bound; /* parent or encapsulator */ + /* vh lock */ + struct lws_dll2 abstract_instances; + lws_abs_transport_inst_t *ati; + lws_abs_protocol_inst_t *api; +} lws_abs_t; + +#endif diff --git a/lib/abstract/protocols/smtp/private-lib-abstract-protocols-smtp.h b/lib/abstract/protocols/smtp/private-lib-abstract-protocols-smtp.h new file mode 100644 index 000000000..0498e08f1 --- /dev/null +++ b/lib/abstract/protocols/smtp/private-lib-abstract-protocols-smtp.h @@ -0,0 +1,24 @@ +#define LWS_SMTP_MAX_EMAIL_LEN 32 + + +/* + * These are allocated on to the heap with an over-allocation to hold the + * payload at the end + */ + +typedef struct lws_smtp_email { + struct lws_dll2 list; + void *data; + + char from[LWS_SMTP_MAX_EMAIL_LEN]; + char to[LWS_SMTP_MAX_EMAIL_LEN]; + + time_t added; + time_t last_try; + + lws_smtp_cb_t done; + + int tries; + + /* email payload follows */ +} lws_smtp_email_t; diff --git a/lib/abstract/protocols/smtp/smtp-sequencer.c b/lib/abstract/protocols/smtp/smtp-sequencer.c new file mode 100644 index 000000000..26751c5dd --- /dev/null +++ b/lib/abstract/protocols/smtp/smtp-sequencer.c @@ -0,0 +1,320 @@ +/* + * 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_sequencer_timeout(s->seq, 10); + 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; +} diff --git a/lib/abstract/protocols/smtp/smtp.c b/lib/abstract/protocols/smtp/smtp.c index 1d7d15424..78adf1a61 100644 --- a/lib/abstract/protocols/smtp/smtp.c +++ b/lib/abstract/protocols/smtp/smtp.c @@ -30,33 +30,36 @@ 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 */ + /* (server sends greeting) */ 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 */ + + /* + * (server sends, eg, "250 Ok: queued as 12345") + * at this point we can return to LGSSMTP_SENT_HELO and send a + * new email, or continue below to QUIT, or just wait + */ + LGSSMTP_SENT_QUIT, /**< sent the session quit */ + + /* (server sends, eg, "221 Bye" and closes the connection) */ } lwsgs_smtp_states_t; -/** struct lws_email - abstract context for performing SMTP operations */ -typedef struct lws_smtp_client { - struct lws_dll2_owner pending_owner; +/** abstract protocol instance data */ - const struct lws_abs *abs; +typedef struct lws_smtp_client_protocol { + const struct lws_abs *abs; + lwsgs_smtp_states_t estate; - const char *helo; + lws_smtp_email_t *e; /* the email we are trying to send */ + 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; + unsigned char send_pending:1; +} lws_smtpcp_t; static const short retcodes[] = { 0, /* idle */ @@ -71,80 +74,71 @@ static const short retcodes[] = { }; static void -lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s) +lws_smtpc_state_transition(lws_smtpcp_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) +static lws_smtp_email_t * +lws_smtpc_get_email(lws_smtpcp_t *c) { - lws_smtp_email_t *e; + const lws_token_map_t *tm; + + /* ... the email we want to send */ + tm = lws_abs_get_token(c->abs->ap_tokens, LTMI_PSMTP_V_LWS_SMTP_EMAIL_T); + if (!tm) { + assert(0); + + return NULL; + } + + return (lws_smtp_email_t *)tm->u.value; +} + +/* + * Called when something happened so that we know now the final disposition of + * the email send attempt, for good or ill. + * + * Inform the owner via the done callback and set up the next queued one if any. + * + * Returns nonzero if we queued a new one + */ + +static int +lws_smtpc_email_disposition(lws_smtpcp_t *c, int disp, const void *buf, + size_t len) +{ + lws_smtpcp_t *ch; + lws_abs_t *ach; lws_dll2_t *d; - char buf[64]; - int n; - if (c->estate != LGSSMTP_IDLE) - return; + lws_smtpc_state_transition(c, LGSSMTP_SENT_HELO); - /* is there something to do? */ + /* lifetime of the email object is handled by done callback */ + c->e->done(c->e, c->e->data, disp, buf, len); + c->e = NULL; -again: - d = lws_dll2_get_head(&c->pending_owner); + /* this may not be the time to try to send anything else... */ + + if (disp == LWS_SMTP_DISPOSITION_FAILED_DESTROY) + return 0; + + /* ... otherwise... do we have another queued? */ + + d = lws_dll2_get_tail(&c->abs->children_owner); if (!d) - return; + return 0; - e = lws_container_of(d, lws_smtp_email_t, list); + ach = lws_container_of(d, lws_abs_t, bound); + ch = (lws_smtpcp_t *)ach->api; - /* do we need to time out this guy? */ + c->e = lws_smtpc_get_email(ch); - 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); + /* since we took it on, remove it from the queue */ + lws_dll2_remove(d); - 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(); + return 1; } /* @@ -152,53 +146,82 @@ again: */ static int -lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api) +lws_smtpc_abs_accept(lws_abs_protocol_inst_t *api) { - lws_smtp_client_t *c = (lws_smtp_client_t *)api; + lws_smtpcp_t *c = (lws_smtpcp_t *)api; - lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED); + /* we have become connected in the tcp sense */ + + lws_smtpc_state_transition(c, LGSSMTP_CONNECTED); + + /* + * From the accept(), the next thing that should happen is the SMTP + * server sends its greeting like "220 smtp2.example.com ESMTP Postfix", + * we'll hear about it in the rx callback, or time out + */ + + c->abs->at->set_timeout(c->abs->ati, + PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, 3); return 0; } static int -lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len) +lws_smtpc_abs_rx(lws_abs_protocol_inst_t *api, const 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; + lws_smtpcp_t *c = (lws_smtpcp_t *)api; + char at[5]; int n; - pd2 = lws_dll2_get_head(&c->pending_owner); - if (!pd2) - return 0; + c->abs->at->set_timeout(c->abs->ati, NO_PENDING_TIMEOUT, 0); - e = lws_container_of(pd2, lws_smtp_email_t, list); - if (!e) - return 0; + lws_strncpy(at, (const char *)buf, sizeof(at)); + n = atoi(at); - 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); + switch (c->estate) { + case LGSSMTP_CONNECTED: + if (n != 220) { + /* + * The server did not properly greet us... we can't + * even get started, so fail the transport connection + * (and anything queued on it) + */ + lwsl_err("%s: server: %.*s\n", __func__, (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; + } + break; + + case LGSSMTP_SENT_BODY: + /* + * We finished one way or another... let's prepare to send a + * new one... or wait until server hangs up on us + */ + if (!lws_smtpc_email_disposition(c, + n == 250 ? LWS_SMTP_DISPOSITION_SENT : + LWS_SMTP_DISPOSITION_FAILED, + "destroyed", 0)) + return 0; /* become idle */ + + break; /* ask to send */ + + case LGSSMTP_SENT_QUIT: + lwsl_debug("%s: done\n", __func__); + lws_smtpc_state_transition(c, LGSSMTP_IDLE); return 1; + + default: + if (n != retcodes[c->estate]) { + lwsl_notice("%s: bad response: %d (state %d) %.*s\n", + __func__, n, c->estate, (int)len, buf); + + lws_smtpc_email_disposition(c, + LWS_SMTP_DISPOSITION_FAILED, buf, len); + + return 0; + } + break; } c->send_pending = 1; @@ -208,24 +231,13 @@ lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len) } static int -lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget) +lws_smtpc_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; + lws_smtpcp_t *c = (lws_smtpcp_t *)api; 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) + if (!c->send_pending || !c->e) return 0; c->send_pending = 0; @@ -235,31 +247,37 @@ lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget) 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); + lws_smtpc_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); + c->e->from); + lws_smtpc_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); + "RCPT TO: <%s>\n", c->e->to); + lws_smtpc_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); + lws_smtpc_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); + p = (char *)&c->e[1]; + n = strlen(p); + lws_smtpc_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); + lws_smtpc_state_transition(c, LGSSMTP_SENT_QUIT); break; + case LGSSMTP_SENT_QUIT: return 0; @@ -274,179 +292,88 @@ lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget) } static int -lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api) +lws_smtpc_abs_closed(lws_abs_protocol_inst_t *api) { - lws_smtp_client_t *c = (lws_smtp_client_t *)api; + lws_smtpcp_t *c = (lws_smtpcp_t *)api; if (c) - lws_smtp_client_state_transition(c, LGSSMTP_IDLE); + lws_smtpc_state_transition(c, LGSSMTP_IDLE); return 0; } +/* + * Creating for initial transport and for piggybacking on another transport + * both get created here the same. But piggybackers have ai->bound attached. + */ + static int -lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api) +lws_smtpc_create(const lws_abs_t *ai) { - 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; + lws_smtpcp_t *c = (lws_smtpcp_t *)ai->api; memset(c, 0, sizeof(*c)); c->abs = ai; + c->e = lws_smtpc_get_email(c); - 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__); + lws_smtpc_state_transition(c, lws_dll2_is_detached(&ai->bound) ? + LGSSMTP_CONNECTING : LGSSMTP_IDLE); - 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; + /* If we are initiating the transport, we will get an accept() next... + * + * If we are piggybacking, the parent will get a .child_bind() after + * this to give it a chance to act on us joining (eg, it was completely + * idle and we joined). + */ return 0; } static void -lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c) +lws_smtpc_destroy(lws_abs_protocol_inst_t **_c) { - lws_smtp_client_t *c = (lws_smtp_client_t *)*_c; + lws_smtpcp_t *c = (lws_smtpcp_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. - */ + /* so if we are still holding on to c->e, we have failed to send it */ + if (c->e) + lws_smtpc_email_disposition(c, + LWS_SMTP_DISPOSITION_FAILED_DESTROY, "destroyed", 0); *_c = NULL; } +static int +lws_smtpc_compare(lws_abs_t *abs1, lws_abs_t *abs2) +{ + return 0; +} + +static int +lws_smtpc_child_bind(lws_abs_t *abs) +{ + return 0; +} + /* 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), + .alloc = sizeof(lws_smtpcp_t), + .flags = LWSABSPR_FLAG_PIPELINE, - .create = lws_smtp_client_create, - .destroy = lws_smtp_client_destroy, + .create = lws_smtpc_create, + .destroy = lws_smtpc_destroy, + .compare = lws_smtpc_compare, - .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, + .accept = lws_smtpc_abs_accept, + .rx = lws_smtpc_abs_rx, + .writeable = lws_smtpc_abs_writeable, + .closed = lws_smtpc_abs_closed, + .heartbeat = NULL, + + .child_bind = lws_smtpc_child_bind, }; diff --git a/lib/abstract/transports/raw-skt.c b/lib/abstract/transports/raw-skt.c index 384807f29..fec2db73a 100644 --- a/lib/abstract/transports/raw-skt.c +++ b/lib/abstract/transports/raw-skt.c @@ -321,9 +321,9 @@ static void lws_atcrs_destroy(lws_abs_transport_inst_t **pati) { /* - * 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. + * For ourselves, we don't free anything because the abstract layer + * combined our allocation with that of the abs instance, and it will + * free the whole thing after this. */ *pati = NULL; } @@ -349,12 +349,51 @@ lws_atcrs_state(lws_abs_transport_inst_t *ati) return 1; } +static int +lws_atcrs_compare(lws_abs_t *abs1, lws_abs_t *abs2) +{ + const lws_token_map_t *tm1, *tm2; + + tm1 = lws_abs_get_token(abs1->at_tokens, LTMI_PEER_V_DNS_ADDRESS); + tm2 = lws_abs_get_token(abs2->at_tokens, LTMI_PEER_V_DNS_ADDRESS); + + /* Address token is mandatory and must match */ + if (!tm1 || !tm2 || strcmp(tm1->u.value, tm2->u.value)) + return 1; + + /* Port token is mandatory and must match */ + tm1 = lws_abs_get_token(abs1->at_tokens, LTMI_PEER_LV_PORT); + tm2 = lws_abs_get_token(abs2->at_tokens, LTMI_PEER_LV_PORT); + if (!tm1 || !tm2 || tm1->u.lvalue != tm2->u.lvalue) + return 1; + + /* TLS is optional... */ + tm1 = lws_abs_get_token(abs1->at_tokens, LTMI_PEER_LV_TLS_FLAGS); + tm2 = lws_abs_get_token(abs2->at_tokens, LTMI_PEER_LV_TLS_FLAGS); + + /* ... but both must have the same situation with it given or not... */ + if (!!tm1 != !!tm2) + return 1; + + /* if not using TLS, then that's enough to call it */ + if (!tm1) + return 0; + + /* ...and if there are tls flags, both must have the same tls flags */ + if (tm1->u.lvalue != tm2->u.lvalue) + return 1; + + /* ... and both must use the same client tls ctx / vhost */ + return abs1->vh != abs2->vh; +} + const lws_abs_transport_t lws_abs_transport_cli_raw_skt = { .name = "raw_skt", .alloc = sizeof(abs_raw_skt_priv_t), .create = lws_atcrs_create, .destroy = lws_atcrs_destroy, + .compare = lws_atcrs_compare, .tx = lws_atcrs_tx, #if !defined(LWS_WITH_CLIENT) diff --git a/lib/abstract/transports/unit-test.c b/lib/abstract/transports/unit-test.c index c5b0bc959..25052d815 100644 --- a/lib/abstract/transports/unit-test.c +++ b/lib/abstract/transports/unit-test.c @@ -513,12 +513,19 @@ lws_unit_test_result_name(int in) return dnames[in]; } +static int +lws_atcut_compare(lws_abs_t *abs1, lws_abs_t *abs2) +{ + return 0; +} + const lws_abs_transport_t lws_abs_transport_cli_unit_test = { .name = "unit_test", .alloc = sizeof(abs_unit_test_priv_t), .create = lws_atcut_create, .destroy = lws_atcut_destroy, + .compare = lws_atcut_compare, .tx = lws_atcut_tx, #if !defined(LWS_WITH_CLIENT) diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 377c5171c..72cd46ec8 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -528,7 +528,7 @@ struct lws_vhost { const struct lws_protocol_vhost_options *headers; struct lws_dll2_owner *same_vh_protocol_owner; struct lws_vhost *no_listener_vhost_list; - struct lws_dll2_owner abstract_instances_owner; + struct lws_dll2_owner abstract_instances_owner; /* vh lock */ #if defined(LWS_WITH_CLIENT) struct lws_dll2_owner dll_cli_active_conns_owner; diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index a2e29a1b5..64258e5cd 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -641,6 +641,10 @@ lws_context_destroy2(struct lws_context *context); #define PRIu64 "llu" #endif +#if defined(LWS_WITH_ABSTRACT) +#include "private-lib-abstract.h" +#endif + #ifdef __cplusplus }; #endif diff --git a/minimal-examples/abstract/protocols/smtp-client/main.c b/minimal-examples/abstract/protocols/smtp-client/main.c index 11d6b2001..e0aca4f30 100644 --- a/minimal-examples/abstract/protocols/smtp-client/main.c +++ b/minimal-examples/abstract/protocols/smtp-client/main.c @@ -21,7 +21,7 @@ sigint_handler(int sig) } static int -email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) +done_cb(struct lws_smtp_email *email, void *buf, size_t len) { /* you could examine email->data here */ if (buf) @@ -39,39 +39,16 @@ email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) return 0; } -/* - * 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_raw_skt_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 const lws_token_map_t smtp_protocol_tokens[] = { - { - .u = { .value = "lws-test-client" }, - .name_index = LTMI_PSMTP_V_HELO, - }, { - } -}; - - int main(int argc, const char **argv) { int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; struct lws_context_creation_info info; + lws_smtp_sequencer_args_t ss_args; struct lws_context *context; - lws_abs_t abs, *instance; - lws_smtp_email_t email; + lws_smtp_sequencer_t *sseq; + lws_smtp_email_t *email; struct lws_vhost *vh; + char payload[2048]; const char *p; /* the normal lws init */ @@ -107,56 +84,30 @@ int main(int argc, const char **argv) goto bail1; } - /* - * create an smtp client that's hooked up to real sockets - */ + memset(&ss_args, 0, sizeof(ss_args)); + ss_args.helo = "lws-abs-smtp-test"; + ss_args.vhost = vh; - memset(&abs, 0, sizeof(abs)); - abs.vh = vh; - - /* select the protocol and bind its tokens */ - - abs.ap = lws_abs_protocol_get_by_name("smtp"); - if (!abs.ap) - goto bail1; - abs.ap_tokens = smtp_protocol_tokens; - - /* select the transport and bind its tokens */ - - abs.at = lws_abs_transport_get_by_name("raw_skt"); - if (!abs.at) - goto bail1; - abs.at_tokens = smtp_raw_skt_transport_tokens; - - instance = lws_abs_bind_and_create_instance(&abs); - if (!instance) { - lwsl_err("%s: failed to create SMTP client\n", __func__); + sseq = lws_smtp_sequencer_create(&ss_args); + if (!sseq) { + lwsl_err("%s: smtp sequencer create failed\n", __func__); goto bail1; } /* attach an email to it */ - memset(&email, 0, sizeof(email)); - email.data = NULL /* email specific user data */; - email.email_from = "andy@warmcat.com"; - email.email_to = recip; - email.payload = malloc(2048); - if (!email.payload) { - goto bail1; - } - - lws_snprintf((char *)email.payload, 2048, + n = lws_snprintf(payload, sizeof(payload), "From: noreply@example.com\n" "To: %s\n" "Subject: Test email for lws smtp-client\n" "\n" "Hello this was an api test for lws smtp-client\n" "\r\n.\r\n", recip); - email.done = email_sent_or_failed; - if (lws_smtp_client_add_email(instance, &email)) { + if (lws_smtpc_add_email(sseq, payload, n, "testserver", + "andy@warmcat.com", recip, NULL, done_cb)) { lwsl_err("%s: failed to add email\n", __func__); - goto bail; + goto bail1; } /* the usual lws event loop */ @@ -164,8 +115,6 @@ int main(int argc, const char **argv) while (n >= 0 && !interrupted) n = lws_service(context, 0); -bail: - bail1: lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); diff --git a/minimal-examples/api-tests/api-test-smtp_client/main.c b/minimal-examples/api-tests/api-test-smtp_client/main.c index df7adac2b..c32e5ac40 100644 --- a/minimal-examples/api-tests/api-test-smtp_client/main.c +++ b/minimal-examples/api-tests/api-test-smtp_client/main.c @@ -55,7 +55,7 @@ smtp_test_instance_init(lws_abs_t *instance) "\r\n.\r\n", "andy@warmcat.com"); email->done = email_sent_or_failed; - if (lws_smtp_client_add_email(instance, email)) { + if (lws_smtpc_add_email(instance, email)) { lwsl_err("%s: failed to add email\n", __func__); return 1; } @@ -163,7 +163,7 @@ sigint_handler(int sig) * set the HELO our SMTP client will use */ -static const lws_token_map_t smtp_protocol_tokens[] = { +static const lws_token_map_t smtp_ap_tokens[] = { { .u = { .value = "lws-test-client" }, .name_index = LTMI_PSMTP_V_HELO, @@ -183,8 +183,8 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; lws_test_sequencer_args_t args; struct lws_context *context; + lws_abs_t *abs = NULL; struct lws_vhost *vh; - lws_abs_t abs, *instance; const char *p; /* the normal lws init */ @@ -213,32 +213,16 @@ int main(int argc, const char **argv) goto bail1; } - /* create the smtp client */ + /* create the abs used to create connections */ - memset(&abs, 0, sizeof(abs)); - abs.vh = vh; - - /* select the protocol and bind its tokens */ - - abs.ap = lws_abs_protocol_get_by_name("smtp"); - if (!abs.ap) - goto bail1; - - abs.ap_tokens = smtp_protocol_tokens; - - /* select the transport and bind its tokens */ - - abs.at = lws_abs_transport_get_by_name("unit_test"); - if (!abs.at) - goto bail1; - - instance = lws_abs_bind_and_create_instance(&abs); - if (!instance) + abs = lws_abstract_alloc(vh, NULL, "smtp.unit_test", + &smtp_ap_tokens[0], NULL); + if (!abs) goto bail1; /* configure the test sequencer */ - args.abs = &abs; + args.abs = abs; args.tests = tests; args.results = results; args.results_max = LWS_ARRAY_SIZE(results); @@ -271,5 +255,7 @@ bail1: lws_context_destroy(context); + lws_abstract_free(&abs); + return !count_tests || count_passes != count_tests; } diff --git a/plugins/generic-sessions/handlers.c b/plugins/generic-sessions/handlers.c index 8ae7a56fd..7924116ca 100644 --- a/plugins/generic-sessions/handlers.c +++ b/plugins/generic-sessions/handlers.c @@ -510,12 +510,12 @@ lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd, puts(s); - em = lws_smtp_client_alloc_email_helper(s, n, vhd->email_from, u.email, + em = lws_smtpc_alloc_email_helper(s, n, vhd->email_from, u.email, u.username, strlen(u.username), vhd, lwsgs_smtp_client_done); if (!em) return 1; - if (lws_smtp_client_add_email(vhd->smtp_client, em)) + if (lws_smtpc_add_email(vhd->smtp_client, em)) return 1; return 0; @@ -636,7 +636,7 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd, vhd->email_confirm_url, hash.id, vhd->email_contact_person); - em = lws_smtp_client_alloc_email_helper(s, n, vhd->email_from, + em = lws_smtpc_alloc_email_helper(s, n, vhd->email_from, lws_spa_get_string(pss->spa, FGS_EMAIL), lws_spa_get_string(pss->spa, FGS_USERNAME), strlen(lws_spa_get_string(pss->spa, FGS_USERNAME)), @@ -644,7 +644,7 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd, if (!em) return 1; - if (lws_smtp_client_add_email(vhd->smtp_client, em)) + if (lws_smtpc_add_email(vhd->smtp_client, em)) return 1; return 0; diff --git a/plugins/generic-sessions/protocol_generic_sessions.c b/plugins/generic-sessions/protocol_generic_sessions.c index c37cebcbc..570eb2ca1 100644 --- a/plugins/generic-sessions/protocol_generic_sessions.c +++ b/plugins/generic-sessions/protocol_generic_sessions.c @@ -562,7 +562,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, goto reg_done; } /* get the email monitor to take a look */ - lws_smtp_client_kick(vhd->smtp_client); + lws_smtpc_kick(vhd->smtp_client); n = FGS_FORGOT_GOOD; goto reg_done; } @@ -584,7 +584,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, n = FGS_REG_GOOD; /* get the email monitor to take a look */ - lws_smtp_client_kick(vhd->smtp_client); + lws_smtpc_kick(vhd->smtp_client); } reg_done: lws_snprintf(pss->onward, sizeof(pss->onward),