text: /*
   * Abstract SMTP support for libwebsockets
   *
   * Copyright (C) 2016-2019 Andy Green <andy@warmcat.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation:
   * version 2.1 of the License.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * General Public License for more details.
   *
   * You should have received a copy of the GNU General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   * MA  02110-1301  USA
   */
  
  #include "core/private.h"
  #include "abstract/private.h"
  
  /** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */
  typedef enum lwsgs_smtp_states {
  	LGSSMTP_IDLE,		/**< awaiting new email */
  	LGSSMTP_CONNECTING,	/**< opening tcp connection to MTA */
  	LGSSMTP_CONNECTED,	/**< tcp connection to MTA is connected */
  	LGSSMTP_SENT_HELO,	/**< sent the HELO */
  	LGSSMTP_SENT_FROM,	/**< sent FROM */
  	LGSSMTP_SENT_TO,	/**< sent TO */
  	LGSSMTP_SENT_DATA,	/**< sent DATA request */
  	LGSSMTP_SENT_BODY,	/**< sent the email body */
  	LGSSMTP_SENT_QUIT,	/**< sent the session quit */
  } lwsgs_smtp_states_t;
  
  /** struct lws_email - abstract context for performing SMTP operations */
  typedef struct lws_smtp_client {
  	struct lws_dll2_owner pending_owner;
  
  	const struct lws_abs *abs;
  
  	const char *helo;
  
  	lwsgs_smtp_states_t estate;
  	time_t email_connect_started;
  
  	time_t retry_interval;
  	time_t delivery_timeout;
  
  	size_t email_queue_max;
  	size_t max_content_size;
  
  	unsigned char send_pending:1;
  } lws_smtp_client_t;
  
  static const short retcodes[] = {
  	0,	/* idle */
  	0,	/* connecting */
  	220,	/* connected */
  	250,	/* helo */
  	250,	/* from */
  	250,	/* to */
  	354,	/* data */
  	250,	/* body */
  	221,	/* quit */
  };
  
  static void
  lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s)
  {
  	lwsl_debug("%s: cli %p: state %d -> %d\n", __func__, c, c->estate, s);
  	c->estate = s;
  }
  
  static void
  lws_smtp_client_kick_internal(lws_smtp_client_t *c)
  {
  	lws_smtp_email_t *e;
  	lws_dll2_t *d;
  	char buf[64];
  	int n;
  
  	if (c->estate != LGSSMTP_IDLE)
  		return;
  
  	/* is there something to do? */
  
  again:
  	d = lws_dll2_get_head(&c->pending_owner);
  	if (!d)
  		return;
  
  	e = lws_container_of(d, lws_smtp_email_t, list);
  
  	/* do we need to time out this guy? */
  
  	if ((time_t)lws_now_secs() - e->added > (time_t)c->delivery_timeout) {
  		lwsl_err("%s: timing out email\n", __func__);
  		lws_dll2_remove(&e->list);
  		n = lws_snprintf(buf, sizeof(buf), "0 Timed out retrying send");
  		e->done(e, buf, n);
  
  		if (lws_dll2_get_head(&c->pending_owner))
  			goto again;
  
  		return;
  	}
  
  	/* is it time for his retry yet? */
  
  	if (e->last_try &&
  	    (time_t)lws_now_secs() - e->last_try < (time_t)c->retry_interval) {
  		/* no... send him to the tail */
  		lws_dll2_remove(&e->list);
  		lws_dll2_add_tail(&e->list, &c->pending_owner);
  		return;
  	}
  
  	/* ask the transport if we have a connection to the server ongoing */
  
  	if (c->abs->at->state(c->abs->ati)) {
  		/*
  		 * there's a connection, it could be still trying to connect
  		 * or established
  		 */
  		c->abs->at->ask_for_writeable(c->abs->ati);
  
  		return;
  	}
  
  	/* there's no existing connection */
  
  	lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING);
  
  	if (c->abs->at->client_conn(c->abs)) {
  		lwsl_err("%s: failed to connect\n", __func__);
  
  		return;
  	}
  
  	e->tries++;
  	e->last_try = lws_now_secs();
  }
  
  /*
   * we became connected
   */
  
  static int
  lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)api;
  
  	lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED);
  
  	return 0;
  }
  
  static int
  lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)api;
  	lws_smtp_email_t *e;
  	lws_dll2_t *pd2;
  	int n;
  
  	pd2 = lws_dll2_get_head(&c->pending_owner);
  	if (!pd2)
  		return 0;
  
  	e = lws_container_of(pd2, lws_smtp_email_t, list);
  	if (!e)
  		return 0;
  
  	n = atoi((char *)buf);
  	if (n != retcodes[c->estate]) {
  		lwsl_notice("%s: bad response from server: %d (state %d) %.*s\n",
  				__func__, n, c->estate, (int)len, buf);
  
  		lws_dll2_remove(&e->list);
  		lws_dll2_add_tail(&e->list, &c->pending_owner);
  		lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
  		lws_smtp_client_kick_internal(c);
  
  		return 0;
  	}
  
  	if (c->estate == LGSSMTP_SENT_QUIT) {
  		lwsl_debug("%s: done\n", __func__);
  		lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
  
  		lws_dll2_remove(&e->list);
  		if (e->done && e->done(e, "sent OK", 7))
  			return 1;
  
  		return 1;
  	}
  
  	c->send_pending = 1;
  	c->abs->at->ask_for_writeable(c->abs->ati);
  
  	return 0;
  }
  
  static int
  lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)api;
  	char b[256 + LWS_PRE], *p = b + LWS_PRE;
  	lws_smtp_email_t *e;
  	lws_dll2_t *pd2;
  	int n;
  
  	pd2 = lws_dll2_get_head(&c->pending_owner);
  	if (!pd2)
  		return 0;
  
  	e = lws_container_of(pd2, lws_smtp_email_t, list);
  	if (!e)
  		return 0;
  
  
  	if (!c->send_pending)
  		return 0;
  
  	c->send_pending = 0;
  
  	lwsl_debug("%s: writing response for state %d\n", __func__, c->estate);
  
  	switch (c->estate) {
  	case LGSSMTP_CONNECTED:
  		n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->helo);
  		lws_smtp_client_state_transition(c, LGSSMTP_SENT_HELO);
  		break;
  	case LGSSMTP_SENT_HELO:
  		n = lws_snprintf(p, sizeof(b) - LWS_PRE, "MAIL FROM: <%s>\n",
  				 e->email_from);
  		lws_smtp_client_state_transition(c, LGSSMTP_SENT_FROM);
  		break;
  	case LGSSMTP_SENT_FROM:
  		n = lws_snprintf(p, sizeof(b) - LWS_PRE,
  				 "RCPT TO: <%s>\n", e->email_to);
  		lws_smtp_client_state_transition(c, LGSSMTP_SENT_TO);
  		break;
  	case LGSSMTP_SENT_TO:
  		n = lws_snprintf(p, sizeof(b) - LWS_PRE, "DATA\n");
  		lws_smtp_client_state_transition(c, LGSSMTP_SENT_DATA);
  		break;
  	case LGSSMTP_SENT_DATA:
  		p = (char *)e->payload;
  		n = strlen(e->payload);
  		lws_smtp_client_state_transition(c, LGSSMTP_SENT_BODY);
  		break;
  	case LGSSMTP_SENT_BODY:
  		n = lws_snprintf(p, sizeof(b) - LWS_PRE, "quit\n");
  		lws_smtp_client_state_transition(c, LGSSMTP_SENT_QUIT);
  		break;
  	case LGSSMTP_SENT_QUIT:
  		return 0;
  
  	default:
  		return 0;
  	}
  
  	//puts(p);
  	c->abs->at->tx(c->abs->ati, (uint8_t *)p, n);
  
  	return 0;
  }
  
  static int
  lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)api;
  
  	if (c)
  		lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
  
  	return 0;
  }
  
  static int
  lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)api;
  
  	lws_smtp_client_kick_internal(c);
  
  	return 0;
  }
  
  lws_smtp_email_t *
  lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len,
  				   const char *sender, const char *recipient,
  				   const char *extra, size_t extra_len, void *data,
  				   int (*done)(struct lws_smtp_email *e,
  					       void *buf, size_t len))
  {
  	size_t ls = strlen(sender), lr = strlen(recipient);
  	lws_smtp_email_t *em;
  	char *p;
  
  	em = malloc(sizeof(*em) + payload_len + ls + lr + extra_len + 4);
  	if (!em) {
  		lwsl_err("OOM\n");
  		return NULL;
  	}
  
  	p = (char *)&em[1];
  
  	memset(em, 0, sizeof(*em));
  
  	em->data = data;
  	em->done = done;
  
  	em->email_from = p;
  	memcpy(p, sender, ls + 1);
  	p += ls + 1;
  	em->email_to = p;
  	memcpy(p, recipient, lr + 1);
  	p += lr + 1;
  	em->payload = p;
  	memcpy(p, payload, payload_len + 1);
  	p += payload_len + 1;
  
  	if (extra) {
  		em->extra = p;
  		memcpy(p, extra, extra_len + 1);
  	}
  
  	return em;
  }
  
  int
  lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
  
  	if (c->pending_owner.count > c->email_queue_max) {
  		lwsl_err("%s: email queue at limit of %d\n", __func__,
  				(int)c->email_queue_max);
  
  		return 1;
  	}
  
  	e->added = lws_now_secs();
  	e->last_try = 0;
  	e->tries = 0;
  
  	lws_dll2_clear(&e->list);
  	lws_dll2_add_tail(&e->list, &c->pending_owner);
  
  	lws_smtp_client_kick_internal(c);
  
  	return 0;
  }
  
  void
  lws_smtp_client_kick(lws_abs_t *instance)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
  
  	lws_smtp_client_kick_internal(c);
  }
  static int
  lws_smtp_client_create(const lws_abs_t *ai)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)ai->api;
  	const lws_token_map_t *tm;
  
  	memset(c, 0, sizeof(*c));
  
  	c->abs = ai;
  
  	tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_V_HELO);
  	if (!tm) {
  		lwsl_err("%s: LTMI_PSMTP_V_HELO is required\n", __func__);
  
  		return 1;
  	}
  	c->helo = tm->u.value;
  
  	c->email_queue_max	= 8;
  	c->retry_interval	= 15 * 60;
  	c->delivery_timeout	= 12 * 60 * 60;
  
  	tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_EMAIL_QUEUE_MAX);
  	if (tm)
  		c->email_queue_max = tm->u.lvalue;
  	tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_RETRY_INTERVAL);
  	if (tm)
  		c->retry_interval = tm->u.lvalue;
  	tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_DELIVERY_TIMEOUT);
  	if (tm)
  		c->delivery_timeout = tm->u.lvalue;
  
  	lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
  
  	return 0;
  }
  
  static int
  cleanup(struct lws_dll2 *d, void *user)
  {
  	lws_smtp_email_t *e;
  
  	e = lws_container_of(d, lws_smtp_email_t, list);
  	if (e->done && e->done(e, "destroying", 10))
  		return 1;
  
  	return 0;
  }
  
  static void
  lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c)
  {
  	lws_smtp_client_t *c = (lws_smtp_client_t *)*_c;
  
  	if (!c)
  		return;
  
  	lws_dll2_foreach_safe(&c->pending_owner, NULL, cleanup);
  
  	/*
  	 * We don't free anything because the abstract layer combined our
  	 * allocation with that of the instance, and it will free the whole
  	 * thing after this.
  	 */
  
  	*_c = NULL;
  }
  
  /* events the transport invokes (handled by abstract protocol) */
  
  const lws_abs_protocol_t lws_abs_protocol_smtp = {
  	.name		= "smtp",
  	.alloc		= sizeof(lws_smtp_client_t),
  
  	.create		= lws_smtp_client_create,
  	.destroy	= lws_smtp_client_destroy,
  
  	.accept		= lws_smtp_client_abs_accept,
  	.rx		= lws_smtp_client_abs_rx,
  	.writeable	= lws_smtp_client_abs_writeable,
  	.closed		= lws_smtp_client_abs_closed,
  	.heartbeat	= lws_smtp_client_abs_heartbeat,
  };
  