You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
9.2 KiB
383 lines
9.2 KiB
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "private-lib-core.h"
|
|
#include "private-lib-abstract.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 */
|
|
/* (server sends greeting) */
|
|
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 */
|
|
|
|
/*
|
|
* (server sends, eg, "250 Ok: queued as 12345")
|
|
* at this point we can return to LGSSMTP_SENT_HELO and send a
|
|
* new email, or continue below to QUIT, or just wait
|
|
*/
|
|
|
|
LGSSMTP_SENT_QUIT, /**< sent the session quit */
|
|
|
|
/* (server sends, eg, "221 Bye" and closes the connection) */
|
|
} lwsgs_smtp_states_t;
|
|
|
|
/** abstract protocol instance data */
|
|
|
|
typedef struct lws_smtp_client_protocol {
|
|
const struct lws_abs *abs;
|
|
lwsgs_smtp_states_t estate;
|
|
|
|
lws_smtp_email_t *e; /* the email we are trying to send */
|
|
const char *helo;
|
|
|
|
unsigned char send_pending:1;
|
|
} lws_smtpcp_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_smtpc_state_transition(lws_smtpcp_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 lws_smtp_email_t *
|
|
lws_smtpc_get_email(lws_smtpcp_t *c)
|
|
{
|
|
const lws_token_map_t *tm;
|
|
|
|
/* ... the email we want to send */
|
|
tm = lws_abs_get_token(c->abs->ap_tokens, LTMI_PSMTP_V_LWS_SMTP_EMAIL_T);
|
|
if (!tm) {
|
|
assert(0);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return (lws_smtp_email_t *)tm->u.value;
|
|
}
|
|
|
|
/*
|
|
* Called when something happened so that we know now the final disposition of
|
|
* the email send attempt, for good or ill.
|
|
*
|
|
* Inform the owner via the done callback and set up the next queued one if any.
|
|
*
|
|
* Returns nonzero if we queued a new one
|
|
*/
|
|
|
|
static int
|
|
lws_smtpc_email_disposition(lws_smtpcp_t *c, int disp, const void *buf,
|
|
size_t len)
|
|
{
|
|
lws_smtpcp_t *ch;
|
|
lws_abs_t *ach;
|
|
lws_dll2_t *d;
|
|
|
|
lws_smtpc_state_transition(c, LGSSMTP_SENT_HELO);
|
|
|
|
/* lifetime of the email object is handled by done callback */
|
|
c->e->done(c->e, c->e->data, disp, buf, len);
|
|
c->e = NULL;
|
|
|
|
/* this may not be the time to try to send anything else... */
|
|
|
|
if (disp == LWS_SMTP_DISPOSITION_FAILED_DESTROY)
|
|
return 0;
|
|
|
|
/* ... otherwise... do we have another queued? */
|
|
|
|
d = lws_dll2_get_tail(&c->abs->children_owner);
|
|
if (!d)
|
|
return 0;
|
|
|
|
ach = lws_container_of(d, lws_abs_t, bound);
|
|
ch = (lws_smtpcp_t *)ach->api;
|
|
|
|
c->e = lws_smtpc_get_email(ch);
|
|
|
|
/* since we took it on, remove it from the queue */
|
|
lws_dll2_remove(d);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* we became connected
|
|
*/
|
|
|
|
static int
|
|
lws_smtpc_abs_accept(lws_abs_protocol_inst_t *api)
|
|
{
|
|
lws_smtpcp_t *c = (lws_smtpcp_t *)api;
|
|
|
|
/* we have become connected in the tcp sense */
|
|
|
|
lws_smtpc_state_transition(c, LGSSMTP_CONNECTED);
|
|
|
|
/*
|
|
* From the accept(), the next thing that should happen is the SMTP
|
|
* server sends its greeting like "220 smtp2.example.com ESMTP Postfix",
|
|
* we'll hear about it in the rx callback, or time out
|
|
*/
|
|
|
|
c->abs->at->set_timeout(c->abs->ati,
|
|
PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, 3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtpc_abs_rx(lws_abs_protocol_inst_t *api, const uint8_t *buf, size_t len)
|
|
{
|
|
lws_smtpcp_t *c = (lws_smtpcp_t *)api;
|
|
char dotstar[96], at[5];
|
|
int n;
|
|
|
|
c->abs->at->set_timeout(c->abs->ati, NO_PENDING_TIMEOUT, 0);
|
|
|
|
lws_strncpy(at, (const char *)buf, sizeof(at));
|
|
n = atoi(at);
|
|
|
|
switch (c->estate) {
|
|
case LGSSMTP_CONNECTED:
|
|
if (n != 220) {
|
|
/*
|
|
* The server did not properly greet us... we can't
|
|
* even get started, so fail the transport connection
|
|
* (and anything queued on it)
|
|
*/
|
|
|
|
lws_strnncpy(dotstar, (const char *)buf, len, sizeof(dotstar));
|
|
lwsl_err("%s: server: %s\n", __func__, dotstar);
|
|
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case LGSSMTP_SENT_BODY:
|
|
/*
|
|
* We finished one way or another... let's prepare to send a
|
|
* new one... or wait until server hangs up on us
|
|
*/
|
|
if (!lws_smtpc_email_disposition(c,
|
|
n == 250 ? LWS_SMTP_DISPOSITION_SENT :
|
|
LWS_SMTP_DISPOSITION_FAILED,
|
|
"destroyed", 0))
|
|
return 0; /* become idle */
|
|
|
|
break; /* ask to send */
|
|
|
|
case LGSSMTP_SENT_QUIT:
|
|
lwsl_debug("%s: done\n", __func__);
|
|
lws_smtpc_state_transition(c, LGSSMTP_IDLE);
|
|
|
|
return 1;
|
|
|
|
default:
|
|
if (n != retcodes[c->estate]) {
|
|
lws_strnncpy(dotstar, buf, len, sizeof(dotstar));
|
|
lwsl_notice("%s: bad response: %d (state %d) %s\n",
|
|
__func__, n, c->estate, dotstar);
|
|
|
|
lws_smtpc_email_disposition(c,
|
|
LWS_SMTP_DISPOSITION_FAILED, buf, len);
|
|
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
c->send_pending = 1;
|
|
c->abs->at->ask_for_writeable(c->abs->ati);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtpc_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget)
|
|
{
|
|
char b[256 + LWS_PRE], *p = b + LWS_PRE;
|
|
lws_smtpcp_t *c = (lws_smtpcp_t *)api;
|
|
int n;
|
|
|
|
if (!c->send_pending || !c->e)
|
|
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_smtpc_state_transition(c, LGSSMTP_SENT_HELO);
|
|
break;
|
|
|
|
case LGSSMTP_SENT_HELO:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "MAIL FROM: <%s>\n",
|
|
c->e->from);
|
|
lws_smtpc_state_transition(c, LGSSMTP_SENT_FROM);
|
|
break;
|
|
|
|
case LGSSMTP_SENT_FROM:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE,
|
|
"RCPT TO: <%s>\n", c->e->to);
|
|
lws_smtpc_state_transition(c, LGSSMTP_SENT_TO);
|
|
break;
|
|
|
|
case LGSSMTP_SENT_TO:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "DATA\n");
|
|
lws_smtpc_state_transition(c, LGSSMTP_SENT_DATA);
|
|
break;
|
|
|
|
case LGSSMTP_SENT_DATA:
|
|
p = (char *)&c->e[1];
|
|
n = strlen(p);
|
|
lws_smtpc_state_transition(c, LGSSMTP_SENT_BODY);
|
|
break;
|
|
|
|
case LGSSMTP_SENT_BODY:
|
|
n = lws_snprintf(p, sizeof(b) - LWS_PRE, "quit\n");
|
|
lws_smtpc_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_smtpc_abs_closed(lws_abs_protocol_inst_t *api)
|
|
{
|
|
lws_smtpcp_t *c = (lws_smtpcp_t *)api;
|
|
|
|
if (c)
|
|
lws_smtpc_state_transition(c, LGSSMTP_IDLE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Creating for initial transport and for piggybacking on another transport
|
|
* both get created here the same. But piggybackers have ai->bound attached.
|
|
*/
|
|
|
|
static int
|
|
lws_smtpc_create(const lws_abs_t *ai)
|
|
{
|
|
lws_smtpcp_t *c = (lws_smtpcp_t *)ai->api;
|
|
|
|
memset(c, 0, sizeof(*c));
|
|
|
|
c->abs = ai;
|
|
c->e = lws_smtpc_get_email(c);
|
|
|
|
lws_smtpc_state_transition(c, lws_dll2_is_detached(&ai->bound) ?
|
|
LGSSMTP_CONNECTING : LGSSMTP_IDLE);
|
|
|
|
/* If we are initiating the transport, we will get an accept() next...
|
|
*
|
|
* If we are piggybacking, the parent will get a .child_bind() after
|
|
* this to give it a chance to act on us joining (eg, it was completely
|
|
* idle and we joined).
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
lws_smtpc_destroy(lws_abs_protocol_inst_t **_c)
|
|
{
|
|
lws_smtpcp_t *c = (lws_smtpcp_t *)*_c;
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
/* so if we are still holding on to c->e, we have failed to send it */
|
|
if (c->e)
|
|
lws_smtpc_email_disposition(c,
|
|
LWS_SMTP_DISPOSITION_FAILED_DESTROY, "destroyed", 0);
|
|
|
|
*_c = NULL;
|
|
}
|
|
|
|
static int
|
|
lws_smtpc_compare(lws_abs_t *abs1, lws_abs_t *abs2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_smtpc_child_bind(lws_abs_t *abs)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* events the transport invokes (handled by abstract protocol) */
|
|
|
|
const lws_abs_protocol_t lws_abs_protocol_smtp = {
|
|
.name = "smtp",
|
|
.alloc = sizeof(lws_smtpcp_t),
|
|
.flags = LWSABSPR_FLAG_PIPELINE,
|
|
|
|
.create = lws_smtpc_create,
|
|
.destroy = lws_smtpc_destroy,
|
|
.compare = lws_smtpc_compare,
|
|
|
|
.accept = lws_smtpc_abs_accept,
|
|
.rx = lws_smtpc_abs_rx,
|
|
.writeable = lws_smtpc_abs_writeable,
|
|
.closed = lws_smtpc_abs_closed,
|
|
.heartbeat = NULL,
|
|
|
|
.child_bind = lws_smtpc_child_bind,
|
|
};
|