• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
36 typedef enum {
37 	LSMTPSS_DISCONNECTED,
38 	LSMTPSS_CONNECTING,
39 	LSMTPSS_CONNECTED,
40 	LSMTPSS_BUSY,
41 } smtpss_connstate_t;
42 
43 typedef 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 
60 enum {
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 
70 static 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 
81 static void
lws_smtpc_kick_internal(lws_smtp_sequencer_t * s)82 lws_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 
164 static int
email_result(void * e,void * d,int disp,void * b,size_t l)165 email_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 
174 static int
cleanup(struct lws_dll2 * d,void * user)175 cleanup(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 
189 static lws_seq_cb_return_t
smtp_sequencer_cb(struct lws_sequencer * seq,void * user,int event,void * data)190 smtp_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 
239 lws_smtp_sequencer_t *
lws_smtp_sequencer_create(const lws_smtp_sequencer_args_t * args)240 lws_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 
275 void
lws_smtp_sequencer_destroy(lws_smtp_sequencer_t * s)276 lws_smtp_sequencer_destroy(lws_smtp_sequencer_t *s)
277 {
278 	/* sequencer destruction destroys all assets */
279 	lws_sequencer_destroy(&s->seq);
280 }
281 
282 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)283 lws_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