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