1/*
2 * Abstract SMTP support for libwebsockets - SMTP sequencer
3 *
4 * Copyright (C) 2016-2019 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 *
24 * This sequencer sits above the abstract protocol, and manages queueing,
25 * retrying mail transmission, and retry limits.
26 *
27 * Having the sequencer means that, eg, we can manage retries after complete
28 * connection failure.
29 *
30 * Connections to the smtp server are serialized
31 */
32
33#include "private-lib-core.h"
34#include "private-lib-abstract-protocols-smtp.h"
35
36typedef enum {
37	LSMTPSS_DISCONNECTED,
38	LSMTPSS_CONNECTING,
39	LSMTPSS_CONNECTED,
40	LSMTPSS_BUSY,
41} smtpss_connstate_t;
42
43typedef struct lws_smtp_sequencer {
44	struct lws_dll2_owner		emails_owner; /* email queue */
45
46	lws_abs_t			*abs, *instance;
47	lws_smtp_sequencer_args_t	args;
48	struct lws_sequencer		*seq;
49
50	smtpss_connstate_t		connstate;
51
52	time_t				email_connect_started;
53
54	/* holds the HELO for the smtp protocol to consume */
55	lws_token_map_t			apt[3];
56} lws_smtp_sequencer_t;
57
58/* sequencer messages specific to this sequencer */
59
60enum {
61	SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
62	SEQ_MSG_CLIENT_DONE,
63};
64
65/*
66 * We're going to bind to the raw-skt transport, so tell that what we want it
67 * to connect to
68 */
69
70static const lws_token_map_t smtp_rs_transport_tokens[] = {
71 {
72	.u = { .value = "127.0.0.1" },
73	.name_index = LTMI_PEER_V_DNS_ADDRESS,
74 }, {
75	.u = { .lvalue = 25 },
76	.name_index = LTMI_PEER_LV_PORT,
77 }, {
78 }
79};
80
81static void
82lws_smtpc_kick_internal(lws_smtp_sequencer_t *s)
83{
84	lws_smtp_email_t *e;
85	lws_dll2_t *d;
86	char buf[64];
87	int n;
88	lws_dll2_t *pd2;
89
90	pd2 = lws_dll2_get_head(&s->emails_owner);
91	if (!pd2)
92		return;
93
94	e = lws_container_of(pd2, lws_smtp_email_t, list);
95	if (!e)
96		return;
97
98	/* Is there something to do?  If so, we need a connection... */
99
100	if (s->connstate == LSMTPSS_DISCONNECTED) {
101
102		s->apt[0].u.value = s->args.helo;
103		s->apt[0].name_index = LTMI_PSMTP_V_HELO;
104		s->apt[1].u.value = (void *)e;
105		s->apt[1].name_index = LTMI_PSMTP_V_LWS_SMTP_EMAIL_T;
106
107		/*
108		 * create and connect the smtp protocol + transport
109		 */
110
111		s->abs = lws_abstract_alloc(s->args.vhost, NULL, "smtp.raw_skt",
112					    s->apt, smtp_rs_transport_tokens,
113					    s->seq, NULL);
114		if (!s->abs)
115			return;
116
117		s->instance = lws_abs_bind_and_create_instance(s->abs);
118		if (!s->instance) {
119			lws_abstract_free(&s->abs);
120			lwsl_err("%s: failed to create SMTP client\n", __func__);
121
122			goto bail1;
123		}
124
125		s->connstate = LSMTPSS_CONNECTING;
126		lws_seq_timeout_us(s->seq, 10 * LWS_USEC_PER_SEC);
127		return;
128	}
129
130	/* ask the transport if we have a connection to the server ongoing */
131
132	if (s->abs->at->state(s->abs->ati)) {
133		/*
134		 * there's a connection, it could be still trying to connect
135		 * or established
136		 */
137		s->abs->at->ask_for_writeable(s->abs->ati);
138
139		return;
140	}
141
142	/* there's no existing connection */
143
144	lws_smtpc_state_transition(c, LGSSMTP_CONNECTING);
145
146	if (s->abs->at->client_conn(s->abs)) {
147		lwsl_err("%s: failed to connect\n", __func__);
148
149		return;
150	}
151
152	e->tries++;
153	e->last_try = lws_now_secs();
154}
155
156
157/*
158 * The callback we get from the smtp protocol... we use this to drive
159 * decisions about destroy email, retry and fail.
160 *
161 * Sequencer will handle it via the event loop.
162 */
163
164static int
165email_result(void *e, void *d, int disp, void *b, size_t l)
166{
167	lws_smtp_sequencer_t *s = (lws_smtp_sequencer_t *)d;
168
169	lws_sequencer_event(s->seq, LWSSEQ_USER_BASE + disp, e);
170
171	return 0;
172}
173
174static int
175cleanup(struct lws_dll2 *d, void *user)
176{
177	lws_smtp_email_t *e;
178
179	e = lws_container_of(d, lws_smtp_email_t, list);
180	if (e->done)
181		e->done(e, "destroying", 10);
182
183	lws_dll2_remove(d);
184	lws_free(e);
185
186	return 0;
187}
188
189static lws_seq_cb_return_t
190smtp_sequencer_cb(struct lws_sequencer *seq, void *user, int event, void *data)
191{
192	struct lws_smtp_sequencer_t *s = (struct lws_smtp_sequencer_t *)user;
193
194	switch ((int)event) {
195	case LWSSEQ_CREATED: /* our sequencer just got started */
196		lwsl_notice("%s: %s: created\n", __func__,
197			    lws_sequencer_name(seq));
198		s->connstate = LSMTPSS_DISCONNECTED;
199		s->state = 0;  /* first thing we'll do is the first url */
200		goto step;
201
202	case LWSSEQ_DESTROYED:
203		lws_dll2_foreach_safe(&s->pending_owner, NULL, cleanup);
204		break;
205
206	case LWSSEQ_TIMED_OUT:
207		lwsl_notice("%s: LWSSEQ_TIMED_OUT\n", __func__);
208		break;
209
210	case LWSSEQ_USER_BASE + LWS_SMTP_DISPOSITION_SENT:
211		lws_smtpc_free_email(data);
212		break;
213
214	case LWSSEQ_WSI_CONNECTED:
215		s->connstate = LSMTPSS_CONNECTED;
216		lws_smtpc_kick_internal(s);
217		break;
218
219	case LWSSEQ_WSI_CONN_FAIL:
220	case LWSSEQ_WSI_CONN_CLOSE:
221		s->connstate = LSMTPSS_DISCONNECTED;
222		lws_smtpc_kick_internal(s);
223		break;
224
225	case SEQ_MSG_SENT:
226		break;
227
228	default:
229		break;
230	}
231
232	return LWSSEQ_RET_CONTINUE;
233}
234
235/*
236 * Creates an lws_sequencer to manage the test sequence
237 */
238
239lws_smtp_sequencer_t *
240lws_smtp_sequencer_create(const lws_smtp_sequencer_args_t *args)
241{
242	lws_smtp_sequencer_t *s;
243	struct lws_sequencer *seq;
244
245	/*
246	 * Create a sequencer in the event loop to manage the SMTP queue
247	 */
248
249	seq = lws_sequencer_create(args->vhost->context, 0,
250				   sizeof(lws_smtp_sequencer_t), (void **)&s,
251				   smtp_sequencer_cb, "smtp-seq");
252	if (!seq) {
253		lwsl_err("%s: unable to create sequencer\n", __func__);
254		return NULL;
255	}
256
257	s->abs = *args->abs;
258	s->args = *args;
259	s->seq = seq;
260
261	/* set defaults in our copy of the args */
262
263	if (!s->args.helo[0])
264		strcpy(s->args.helo, "default-helo");
265	if (!s->args.email_queue_max)
266		s->args.email_queue_max = 8;
267	if (!s->args.retry_interval)
268		s->args.retry_interval = 15 * 60;
269	if (!s->args.delivery_timeout)
270		s->args.delivery_timeout = 12 * 60 * 60;
271
272	return s;
273}
274
275void
276lws_smtp_sequencer_destroy(lws_smtp_sequencer_t *s)
277{
278	/* sequencer destruction destroys all assets */
279	lws_sequencer_destroy(&s->seq);
280}
281
282int
283lws_smtpc_add_email(lws_smtp_sequencer_t *s, const char *payload,
284		    size_t payload_len, const char *sender,
285		    const char *recipient, void *data, lws_smtp_cb_t done)
286{
287	lws_smtp_email_t *e;
288
289	if (s->emails_owner.count > s->args.email_queue_max) {
290		lwsl_err("%s: email queue at limit of %d\n", __func__,
291			 (int)s->args.email_queue_max);
292
293		return 1;
294	}
295
296	if (!done)
297		return 1;
298
299	e = malloc(sizeof(*e) + payload_len + 1);
300	if (!e)
301		return 1;
302
303	memset(e, 0, sizeof(*e));
304
305	e->data = data;
306	e->done = done;
307
308	lws_strncpy(e->from, sender, sizeof(e->from));
309	lws_strncpy(e->to, recipient, sizeof(e->to));
310
311	memcpy((char *)&e[1], payload, payload_len + 1);
312
313	e->added = lws_now_secs();
314	e->last_try = 0;
315	e->tries = 0;
316
317	lws_dll2_clear(&e->list);
318	lws_dll2_add_tail(&e->list, &s->emails_owner);
319
320	lws_smtpc_kick_internal(s);
321
322	return 0;
323}
324