private.h: rename to contain dir
[platform/upstream/libwebsockets.git] / lib / abstract / protocols / smtp / smtp.c
1 /*
2  * Abstract SMTP support for libwebsockets
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
22 #include "private-lib-core.h"
23 #include "private-lib-abstract.h"
24
25 /** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */
26 typedef enum lwsgs_smtp_states {
27         LGSSMTP_IDLE,           /**< awaiting new email */
28         LGSSMTP_CONNECTING,     /**< opening tcp connection to MTA */
29         LGSSMTP_CONNECTED,      /**< tcp connection to MTA is connected */
30         LGSSMTP_SENT_HELO,      /**< sent the HELO */
31         LGSSMTP_SENT_FROM,      /**< sent FROM */
32         LGSSMTP_SENT_TO,        /**< sent TO */
33         LGSSMTP_SENT_DATA,      /**< sent DATA request */
34         LGSSMTP_SENT_BODY,      /**< sent the email body */
35         LGSSMTP_SENT_QUIT,      /**< sent the session quit */
36 } lwsgs_smtp_states_t;
37
38 /** struct lws_email - abstract context for performing SMTP operations */
39 typedef struct lws_smtp_client {
40         struct lws_dll2_owner pending_owner;
41
42         const struct lws_abs *abs;
43
44         const char *helo;
45
46         lwsgs_smtp_states_t estate;
47         time_t email_connect_started;
48
49         time_t retry_interval;
50         time_t delivery_timeout;
51
52         size_t email_queue_max;
53         size_t max_content_size;
54
55         unsigned char send_pending:1;
56 } lws_smtp_client_t;
57
58 static const short retcodes[] = {
59         0,      /* idle */
60         0,      /* connecting */
61         220,    /* connected */
62         250,    /* helo */
63         250,    /* from */
64         250,    /* to */
65         354,    /* data */
66         250,    /* body */
67         221,    /* quit */
68 };
69
70 static void
71 lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s)
72 {
73         lwsl_debug("%s: cli %p: state %d -> %d\n", __func__, c, c->estate, s);
74         c->estate = s;
75 }
76
77 static void
78 lws_smtp_client_kick_internal(lws_smtp_client_t *c)
79 {
80         lws_smtp_email_t *e;
81         lws_dll2_t *d;
82         char buf[64];
83         int n;
84
85         if (c->estate != LGSSMTP_IDLE)
86                 return;
87
88         /* is there something to do? */
89
90 again:
91         d = lws_dll2_get_head(&c->pending_owner);
92         if (!d)
93                 return;
94
95         e = lws_container_of(d, lws_smtp_email_t, list);
96
97         /* do we need to time out this guy? */
98
99         if ((time_t)lws_now_secs() - e->added > (time_t)c->delivery_timeout) {
100                 lwsl_err("%s: timing out email\n", __func__);
101                 lws_dll2_remove(&e->list);
102                 n = lws_snprintf(buf, sizeof(buf), "0 Timed out retrying send");
103                 e->done(e, buf, n);
104
105                 if (lws_dll2_get_head(&c->pending_owner))
106                         goto again;
107
108                 return;
109         }
110
111         /* is it time for his retry yet? */
112
113         if (e->last_try &&
114             (time_t)lws_now_secs() - e->last_try < (time_t)c->retry_interval) {
115                 /* no... send him to the tail */
116                 lws_dll2_remove(&e->list);
117                 lws_dll2_add_tail(&e->list, &c->pending_owner);
118                 return;
119         }
120
121         /* ask the transport if we have a connection to the server ongoing */
122
123         if (c->abs->at->state(c->abs->ati)) {
124                 /*
125                  * there's a connection, it could be still trying to connect
126                  * or established
127                  */
128                 c->abs->at->ask_for_writeable(c->abs->ati);
129
130                 return;
131         }
132
133         /* there's no existing connection */
134
135         lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING);
136
137         if (c->abs->at->client_conn(c->abs)) {
138                 lwsl_err("%s: failed to connect\n", __func__);
139
140                 return;
141         }
142
143         e->tries++;
144         e->last_try = lws_now_secs();
145 }
146
147 /*
148  * we became connected
149  */
150
151 static int
152 lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api)
153 {
154         lws_smtp_client_t *c = (lws_smtp_client_t *)api;
155
156         lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED);
157
158         return 0;
159 }
160
161 static int
162 lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len)
163 {
164         lws_smtp_client_t *c = (lws_smtp_client_t *)api;
165         lws_smtp_email_t *e;
166         lws_dll2_t *pd2;
167         int n;
168
169         pd2 = lws_dll2_get_head(&c->pending_owner);
170         if (!pd2)
171                 return 0;
172
173         e = lws_container_of(pd2, lws_smtp_email_t, list);
174         if (!e)
175                 return 0;
176
177         n = atoi((char *)buf);
178         if (n != retcodes[c->estate]) {
179                 lwsl_notice("%s: bad response from server: %d (state %d) %.*s\n",
180                                 __func__, n, c->estate, (int)len, buf);
181
182                 lws_dll2_remove(&e->list);
183                 lws_dll2_add_tail(&e->list, &c->pending_owner);
184                 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
185                 lws_smtp_client_kick_internal(c);
186
187                 return 0;
188         }
189
190         if (c->estate == LGSSMTP_SENT_QUIT) {
191                 lwsl_debug("%s: done\n", __func__);
192                 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
193
194                 lws_dll2_remove(&e->list);
195                 if (e->done && e->done(e, "sent OK", 7))
196                         return 1;
197
198                 return 1;
199         }
200
201         c->send_pending = 1;
202         c->abs->at->ask_for_writeable(c->abs->ati);
203
204         return 0;
205 }
206
207 static int
208 lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget)
209 {
210         lws_smtp_client_t *c = (lws_smtp_client_t *)api;
211         char b[256 + LWS_PRE], *p = b + LWS_PRE;
212         lws_smtp_email_t *e;
213         lws_dll2_t *pd2;
214         int n;
215
216         pd2 = lws_dll2_get_head(&c->pending_owner);
217         if (!pd2)
218                 return 0;
219
220         e = lws_container_of(pd2, lws_smtp_email_t, list);
221         if (!e)
222                 return 0;
223
224
225         if (!c->send_pending)
226                 return 0;
227
228         c->send_pending = 0;
229
230         lwsl_debug("%s: writing response for state %d\n", __func__, c->estate);
231
232         switch (c->estate) {
233         case LGSSMTP_CONNECTED:
234                 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->helo);
235                 lws_smtp_client_state_transition(c, LGSSMTP_SENT_HELO);
236                 break;
237         case LGSSMTP_SENT_HELO:
238                 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "MAIL FROM: <%s>\n",
239                                  e->email_from);
240                 lws_smtp_client_state_transition(c, LGSSMTP_SENT_FROM);
241                 break;
242         case LGSSMTP_SENT_FROM:
243                 n = lws_snprintf(p, sizeof(b) - LWS_PRE,
244                                  "RCPT TO: <%s>\n", e->email_to);
245                 lws_smtp_client_state_transition(c, LGSSMTP_SENT_TO);
246                 break;
247         case LGSSMTP_SENT_TO:
248                 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "DATA\n");
249                 lws_smtp_client_state_transition(c, LGSSMTP_SENT_DATA);
250                 break;
251         case LGSSMTP_SENT_DATA:
252                 p = (char *)e->payload;
253                 n = strlen(e->payload);
254                 lws_smtp_client_state_transition(c, LGSSMTP_SENT_BODY);
255                 break;
256         case LGSSMTP_SENT_BODY:
257                 n = lws_snprintf(p, sizeof(b) - LWS_PRE, "quit\n");
258                 lws_smtp_client_state_transition(c, LGSSMTP_SENT_QUIT);
259                 break;
260         case LGSSMTP_SENT_QUIT:
261                 return 0;
262
263         default:
264                 return 0;
265         }
266
267         //puts(p);
268         c->abs->at->tx(c->abs->ati, (uint8_t *)p, n);
269
270         return 0;
271 }
272
273 static int
274 lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api)
275 {
276         lws_smtp_client_t *c = (lws_smtp_client_t *)api;
277
278         if (c)
279                 lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
280
281         return 0;
282 }
283
284 static int
285 lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api)
286 {
287         lws_smtp_client_t *c = (lws_smtp_client_t *)api;
288
289         lws_smtp_client_kick_internal(c);
290
291         return 0;
292 }
293
294 lws_smtp_email_t *
295 lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len,
296                                    const char *sender, const char *recipient,
297                                    const char *extra, size_t extra_len, void *data,
298                                    int (*done)(struct lws_smtp_email *e,
299                                                void *buf, size_t len))
300 {
301         size_t ls = strlen(sender), lr = strlen(recipient);
302         lws_smtp_email_t *em;
303         char *p;
304
305         em = malloc(sizeof(*em) + payload_len + ls + lr + extra_len + 4);
306         if (!em) {
307                 lwsl_err("OOM\n");
308                 return NULL;
309         }
310
311         p = (char *)&em[1];
312
313         memset(em, 0, sizeof(*em));
314
315         em->data = data;
316         em->done = done;
317
318         em->email_from = p;
319         memcpy(p, sender, ls + 1);
320         p += ls + 1;
321         em->email_to = p;
322         memcpy(p, recipient, lr + 1);
323         p += lr + 1;
324         em->payload = p;
325         memcpy(p, payload, payload_len + 1);
326         p += payload_len + 1;
327
328         if (extra) {
329                 em->extra = p;
330                 memcpy(p, extra, extra_len + 1);
331         }
332
333         return em;
334 }
335
336 int
337 lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e)
338 {
339         lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
340
341         if (c->pending_owner.count > c->email_queue_max) {
342                 lwsl_err("%s: email queue at limit of %d\n", __func__,
343                                 (int)c->email_queue_max);
344
345                 return 1;
346         }
347
348         e->added = lws_now_secs();
349         e->last_try = 0;
350         e->tries = 0;
351
352         lws_dll2_clear(&e->list);
353         lws_dll2_add_tail(&e->list, &c->pending_owner);
354
355         lws_smtp_client_kick_internal(c);
356
357         return 0;
358 }
359
360 void
361 lws_smtp_client_kick(lws_abs_t *instance)
362 {
363         lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
364
365         lws_smtp_client_kick_internal(c);
366 }
367 static int
368 lws_smtp_client_create(const lws_abs_t *ai)
369 {
370         lws_smtp_client_t *c = (lws_smtp_client_t *)ai->api;
371         const lws_token_map_t *tm;
372
373         memset(c, 0, sizeof(*c));
374
375         c->abs = ai;
376
377         tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_V_HELO);
378         if (!tm) {
379                 lwsl_err("%s: LTMI_PSMTP_V_HELO is required\n", __func__);
380
381                 return 1;
382         }
383         c->helo = tm->u.value;
384
385         c->email_queue_max      = 8;
386         c->retry_interval       = 15 * 60;
387         c->delivery_timeout     = 12 * 60 * 60;
388
389         tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_EMAIL_QUEUE_MAX);
390         if (tm)
391                 c->email_queue_max = tm->u.lvalue;
392         tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_RETRY_INTERVAL);
393         if (tm)
394                 c->retry_interval = tm->u.lvalue;
395         tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_DELIVERY_TIMEOUT);
396         if (tm)
397                 c->delivery_timeout = tm->u.lvalue;
398
399         lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
400
401         return 0;
402 }
403
404 static int
405 cleanup(struct lws_dll2 *d, void *user)
406 {
407         lws_smtp_email_t *e;
408
409         e = lws_container_of(d, lws_smtp_email_t, list);
410         if (e->done && e->done(e, "destroying", 10))
411                 return 1;
412
413         return 0;
414 }
415
416 static void
417 lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c)
418 {
419         lws_smtp_client_t *c = (lws_smtp_client_t *)*_c;
420
421         if (!c)
422                 return;
423
424         lws_dll2_foreach_safe(&c->pending_owner, NULL, cleanup);
425
426         /*
427          * We don't free anything because the abstract layer combined our
428          * allocation with that of the instance, and it will free the whole
429          * thing after this.
430          */
431
432         *_c = NULL;
433 }
434
435 /* events the transport invokes (handled by abstract protocol) */
436
437 const lws_abs_protocol_t lws_abs_protocol_smtp = {
438         .name           = "smtp",
439         .alloc          = sizeof(lws_smtp_client_t),
440
441         .create         = lws_smtp_client_create,
442         .destroy        = lws_smtp_client_destroy,
443
444         .accept         = lws_smtp_client_abs_accept,
445         .rx             = lws_smtp_client_abs_rx,
446         .writeable      = lws_smtp_client_abs_writeable,
447         .closed         = lws_smtp_client_abs_closed,
448         .heartbeat      = lws_smtp_client_abs_heartbeat,
449 };