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