diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fbb1804..da62dfd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OF option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF) option(LWS_WITH_LEJP "With the Lightweight JSON Parser" OFF) option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" OFF) +option(LWS_WITH_SMTP "Provide SMTP support" OFF) if (LWS_WITH_LWSWS) message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV") @@ -115,6 +116,11 @@ message(STATUS "LWS_WITH_PLUGINS --> Enabling LWS_WITH_LIBUV") set(LWS_WITH_LIBUV 1) endif() +if (LWS_WITH_SMTP AND NOT LWS_WITH_LIBUV) +message(STATUS "LWS_WITH_SMTP --> Enabling LWS_WITH_LIBUV") + set(LWS_WITH_LIBUV 1) +endif() + if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING) set(LWS_WITH_SHARED OFF) @@ -592,6 +598,11 @@ if (LWS_WITH_LEJP_CONF) ) endif() +if (LWS_WITH_SMTP) + list(APPEND SOURCES + lib/smtp.c) +endif() + # Add helper files for Windows. if (WIN32) set(WIN32_HELPERS_PATH win32port/win32helpers) @@ -1514,6 +1525,7 @@ message(" LWS_WITH_ACCESS_LOG = ${LWS_WITH_ACCESS_LOG}") message(" LWS_WITH_SERVER_STATUS = ${LWS_WITH_SERVER_STATUS}") message(" LWS_WITH_LEJP = ${LWS_WITH_LEJP}") message(" LWS_WITH_LEJP_CONF = ${LWS_WITH_LEJP_CONF}") +message(" LWS_WITH_SMTP = ${LWS_WITH_SMTP}") message("---------------------------------------------------------------------") # These will be available to parent projects including libwebsockets using add_subdirectory() diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 0426a9b9..dcb3e642 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -2284,6 +2284,68 @@ lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi, LWS_VISIBLE LWS_EXTERN void lws_set_allocator(void *(*realloc)(void *ptr, size_t size)); +/* lws_email */ +#ifdef LWS_WITH_SMTP +enum lwsgs_smtp_states { + LGSSMTP_IDLE, + LGSSMTP_CONNECTING, + LGSSMTP_CONNECTED, + LGSSMTP_SENT_HELO, + LGSSMTP_SENT_FROM, + LGSSMTP_SENT_TO, + LGSSMTP_SENT_DATA, + LGSSMTP_SENT_BODY, + LGSSMTP_SENT_QUIT, +}; + +/* + * struct lws_email - abstract context for performing SMTP operations + * + * @data: opaque pointer set by user code and available to the callbacks + * @email_smtp_ip: IP address to access for SMTP server (usually 127.0.0.1) + * @email_helo: Server name to use when greeting SMTP server + * @on_next: called when idle, 0 = another email to send, nonzero is idle. + * If you return 0, all of the email_* char arrays must be set + * to something useful. + * @on_sent: called when transfer of the email to the SMTP server was + * successful, your callback would remove the current email + * from its queue + * @on_get_body: called when the body part of the queued email is about to be + * sent to the SMTP server. + */ +struct lws_email { + uv_timer_t timeout_email; + enum lwsgs_smtp_states estate; + uv_connect_t email_connect_req; + uv_tcp_t email_client; + time_t email_connect_started; + char email_buf[256]; + char *content; + void *data; /* Fill before init, useful in callbacks */ + uv_loop_t *loop; + + char email_smtp_ip[32]; /* Fill before init, eg, "127.0.0.1" */ + char email_helo[32]; /* Fill before init, eg, "myserver.com" */ + char email_from[100]; /* Fill before init or @on_next */ + char email_to[100]; /* Fill before init or @on_next */ + + unsigned int max_content_size; + + /* Fill all the callbacks before init */ + + int (*on_next)(struct lws_email *email); + int (*on_sent)(struct lws_email *email); + int (*on_get_body)(struct lws_email *email, char *buf, int len); +}; + +LWS_VISIBLE LWS_EXTERN int +lws_email_init(struct lws_email *email, uv_loop_t *loop, int max_content); +LWS_VISIBLE LWS_EXTERN void +lws_email_check(struct lws_email *email); +LWS_VISIBLE LWS_EXTERN void +lws_email_destroy(struct lws_email *email); +#endif + #ifdef __cplusplus } #endif diff --git a/lib/smtp.c b/lib/smtp.c new file mode 100644 index 00000000..1ccf6ed6 --- /dev/null +++ b/lib/smtp.c @@ -0,0 +1,266 @@ +/* + * SMTP support for libwebsockets + * + * Copyright (C) 2016 Andy Green + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 "private-libwebsockets.h" + +static unsigned int +lwsgs_now_secs(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + return tv.tv_sec; +} + +static void +ccb(uv_handle_t* handle) +{ + +} + +static void +alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) +{ + struct lws_email *email = (struct lws_email *)handle->data; + + *buf = uv_buf_init(email->email_buf, sizeof(email->email_buf) - 1); +} + +static void +on_write_end(uv_write_t *req, int status) { + lwsl_notice("%s\n", __func__); + if (status == -1) { + fprintf(stderr, "error on_write_end"); + return; + } +} + +static void +lwsgs_email_read(struct uv_stream_s *s, ssize_t nread, const uv_buf_t *buf) +{ + struct lws_email *email = (struct lws_email *)s->data; + static const short retcodes[] = { + 0, /* idle */ + 0, /* connecting */ + 220, /* connected */ + 250, /* helo */ + 250, /* from */ + 250, /* to */ + 354, /* data */ + 250, /* body */ + 221, /* quit */ + }; + uv_write_t write_req; + uv_buf_t wbuf; + int n; + + if (nread >= 0) + email->email_buf[nread] = '\0'; + lwsl_notice("%s: %s\n", __func__, buf->base); + if (nread == -1) { + lwsl_err("%s: failed\n", __func__); + return; + } + + n = atoi(buf->base); + if (n != retcodes[email->estate]) { + lwsl_err("%s: bad response from server\n", __func__); + goto close_conn; + } + + switch (email->estate) { + case LGSSMTP_CONNECTED: + n = sprintf(email->content, "HELO %s\n", email->email_helo); + email->estate = LGSSMTP_SENT_HELO; + break; + case LGSSMTP_SENT_HELO: + n = sprintf(email->content, "MAIL FROM: <%s>\n", email->email_from); + email->estate = LGSSMTP_SENT_FROM; + break; + case LGSSMTP_SENT_FROM: + n = sprintf(email->content, "RCPT TO: <%s>\n", email->email_to); + email->estate = LGSSMTP_SENT_TO; + break; + case LGSSMTP_SENT_TO: + n = sprintf(email->content, "DATA\n"); + email->estate = LGSSMTP_SENT_DATA; + break; + case LGSSMTP_SENT_DATA: + if (email->on_get_body(email, email->content, email->max_content_size)) + return; + n = strlen(email->content); + email->estate = LGSSMTP_SENT_BODY; + break; + case LGSSMTP_SENT_BODY: + n = sprintf(email->content, "quit\n"); + email->estate = LGSSMTP_SENT_QUIT; + break; + case LGSSMTP_SENT_QUIT: + lwsl_notice("%s: done\n", __func__); + email->on_sent(email); + email->estate = LGSSMTP_IDLE; + goto close_conn; + default: + return; + } + + puts(email->content); + wbuf = uv_buf_init(email->content, n); + uv_write(&write_req, s, &wbuf, 1, on_write_end); + + return; + +close_conn: + + uv_close((uv_handle_t *)s, ccb); +} + +static void +lwsgs_email_on_connect(uv_connect_t *req, int status) +{ + struct lws_email *email = (struct lws_email *)req->data; + + lwsl_notice("%s\n", __func__); + + if (status == -1) { + lwsl_err("%s: failed\n", __func__); + return; + } + + uv_read_start(req->handle, alloc_buffer, lwsgs_email_read); + email->estate = LGSSMTP_CONNECTED; +} + + +static void +uv_timeout_cb_email(uv_timer_t *w +#if UV_VERSION_MAJOR == 0 + , int status +#endif +) +{ + struct lws_email *email = lws_container_of(w, struct lws_email, + timeout_email); + time_t now = lwsgs_now_secs(); + struct sockaddr_in req_addr; + + switch (email->estate) { + case LGSSMTP_IDLE: + + if (email->on_next(email)) + break; + + email->estate = LGSSMTP_CONNECTING; + + uv_tcp_init(email->loop, &email->email_client); + if (uv_ip4_addr(email->email_smtp_ip, 25, &req_addr)) { + lwsl_err("Unable to convert mailserver ads\n"); + return; + } + + lwsl_notice("LGSSMTP_IDLE: connecting\n"); + + email->email_connect_started = now; + email->email_connect_req.data = email; + email->email_client.data = email; + uv_tcp_connect(&email->email_connect_req, &email->email_client, + (struct sockaddr *)&req_addr, + lwsgs_email_on_connect); + + uv_timer_start(&email->timeout_email, + uv_timeout_cb_email, 5000, 0); + + break; + + case LGSSMTP_CONNECTING: + if (email->email_connect_started - now > 5) { + lwsl_err("mail session timed out\n"); + /* !!! kill the connection */ + uv_close((uv_handle_t *) &email->email_connect_req, ccb); + email->estate = LGSSMTP_IDLE; + } + break; + + default: + break; + } +} + +/** + * lws_email_init() - Initialize a struct lws_email + * + * @email: struct lws_email to init + * @loop: libuv loop to use + * @max_content: max email content size + * + * Prepares a struct lws_email for use ending SMTP + */ + +LWS_VISIBLE LWS_EXTERN int +lws_email_init(struct lws_email *email, uv_loop_t *loop, int max_content) +{ + email->content = lws_malloc(max_content); + if (!email->content) + return 1; + email->max_content_size = max_content; + uv_timer_init(loop, &email->timeout_email); + + email->loop = loop; + + /* trigger him one time in a bit */ + uv_timer_start(&email->timeout_email, uv_timeout_cb_email, 2000, 0); + + return 0; +} + +/** + * lws_email_check() - Request check for new email + * + * @email: struct lws_email context to check + * + * Schedules a check for new emails in 1s... call this when you have queued an + * email for send. + */ + +LWS_VISIBLE LWS_EXTERN void +lws_email_check(struct lws_email *email) +{ + uv_timer_start(&email->timeout_email, uv_timeout_cb_email, 1000, 0); +} + +/** + * lws_email_destroy() - stop using the struct lws_email + * + * @email: the struct lws_email context + * + * Stop sending email using @email and free allocations + */ + +LWS_VISIBLE LWS_EXTERN void +lws_email_destroy(struct lws_email *email) +{ + if (email->content) + lws_free_set_NULL(email->content); + + uv_timer_stop(&email->timeout_email); +} + diff --git a/lws_config.h.in b/lws_config.h.in index 598969c2..21f1b536 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -102,4 +102,7 @@ /* Lightweight JSON Parser */ #cmakedefine LWS_WITH_LEJP +/* SMTP */ +#cmakedefine LWS_WITH_SMTP + ${LWS_SIZEOFPTR_CODE}