From 1be79df4d51fc2e497a73fc0163de08d406cc1f3 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Wed, 9 Jun 2010 21:51:25 +0400 Subject: [PATCH] * Implement basic SMTP dialog: - delay - helo - mail from - rcpt * Implement interaction with smtp upstream (with support of XCLIENT) --- CMakeLists.txt | 1 + src/buffer.c | 6 + src/buffer.h | 6 + src/smtp.c | 299 +++++++++++++++++++++---- src/smtp.h | 18 +- src/smtp_proto.c | 553 +++++++++++++++++++++++++++++++++++++++++++++++ src/smtp_proto.h | 46 ++++ 7 files changed, 884 insertions(+), 45 deletions(-) create mode 100644 src/smtp_proto.c create mode 100644 src/smtp_proto.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 75cd5083a..9e929ec1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,6 +426,7 @@ SET(RSPAMDSRC src/modules.c src/settings.c src/spf.c src/smtp.c + src/smtp_proto.c src/statfile.c src/statfile_sync.c src/symbols_cache.c diff --git a/src/buffer.c b/src/buffer.c index ec435bc83..7dd43d2ad 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -419,6 +419,12 @@ rspamd_dispatcher_pause (rspamd_io_dispatcher_t * d) event_del (d->ev); } +void +rspamd_dispatcher_restore (rspamd_io_dispatcher_t * d) +{ + event_add (d->ev, d->tv); +} + /* * vi:ts=4 */ diff --git a/src/buffer.h b/src/buffer.h index 04845814a..4cf9de555 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -92,6 +92,12 @@ gboolean rspamd_dispatcher_write (rspamd_io_dispatcher_t *d, */ void rspamd_dispatcher_pause (rspamd_io_dispatcher_t *d); +/** + * Restore IO events on dispatcher + * @param d pointer to dispatcher's object + */ +void rspamd_dispatcher_restore (rspamd_io_dispatcher_t *d); + /** * Frees dispatcher object * @param dispatcher pointer to dispatcher's object diff --git a/src/smtp.c b/src/smtp.c index fc2811bed..1bf135d30 100644 --- a/src/smtp.c +++ b/src/smtp.c @@ -27,14 +27,19 @@ #include "cfg_file.h" #include "util.h" #include "smtp.h" +#include "smtp_proto.h" #include "map.h" #include "evdns/evdns.h" /* Max line size as it is defined in rfc2822 */ #define OUTBUFSIZ 1000 -/* SMTP error messages */ +/* Upstream timeouts */ +#define DEFAULT_UPSTREAM_ERROR_TIME 10 +#define DEFAULT_UPSTREAM_DEAD_TIME 300 +#define DEFAULT_UPSTREAM_MAXERRORS 10 +static gboolean smtp_write_socket (void *arg); static sig_atomic_t wanna_die = 0; @@ -66,22 +71,6 @@ sig_handler (int signo, siginfo_t *info, void *unused) } } -char * -make_smtp_error (struct smtp_session *session, int error_code, const char *format, ...) -{ - va_list vp; - char *result = NULL, *p; - size_t len; - - va_start (vp, format); - len = g_printf_string_upper_bound (format, vp); - result = memory_pool_alloc (session->pool, len + sizeof ("65535 ")); - p = result + snprintf (result, len, "%d ", error_code); - vsnprintf (p, len - (p - result), format, vp); - va_end (vp); - - return result; -} static void free_smtp_session (gpointer arg) @@ -92,6 +81,9 @@ free_smtp_session (gpointer arg) if (session->task) { free_task (session->task, FALSE); } + if (session->rcpt) { + g_list_free (session->rcpt); + } if (session->dispatcher) { rspamd_remove_dispatcher (session->dispatcher); } @@ -122,11 +114,139 @@ sigusr_handler (int fd, short what, void *arg) return; } +static gboolean +create_smtp_upstream_connection (struct smtp_session *session) +{ + struct smtp_upstream *selected; + struct sockaddr_un *un; + + /* Try to select upstream */ + selected = (struct smtp_upstream *)get_upstream_round_robin (session->ctx->upstreams, + session->ctx->upstream_num, sizeof (struct smtp_upstream), + session->session_time, DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS); + if (selected == NULL) { + msg_err ("no upstreams suitable found"); + return FALSE; + } + + session->upstream = selected; + + /* Now try to create socket */ + if (selected->is_unix) { + un = alloca (sizeof (struct sockaddr_un)); + session->upstream_sock = make_unix_socket (selected->name, un, FALSE); + } + else { + session->upstream_sock = make_tcp_socket (&selected->addr, selected->port, FALSE, TRUE); + } + if (session->upstream_sock == -1) { + msg_err ("cannot make a connection to %s", selected->name); + upstream_fail (&selected->up, session->session_time); + return FALSE; + } + /* Create a dispatcher for upstream connection */ + session->upstream_dispatcher = rspamd_create_dispatcher (session->upstream_sock, BUFFER_LINE, + smtp_upstream_read_socket, NULL, smtp_upstream_err_socket, + &session->ctx->smtp_timeout, session); + session->state = SMTP_STATE_WAIT_UPSTREAM; + session->upstream_state = SMTP_STATE_GREETING; + register_async_event (session->s, (event_finalizer_t)smtp_upstream_finalize_connection, session, FALSE); + return TRUE; +} + static gboolean read_smtp_command (struct smtp_session *session, f_str_t *line) { /* XXX: write dialog implementation */ + struct smtp_command *cmd; + + if (! parse_smtp_command (session, line, &cmd)) { + session->error = SMTP_ERROR_BAD_COMMAND; + return FALSE; + } + + switch (cmd->command) { + case SMTP_COMMAND_HELO: + case SMTP_COMMAND_EHLO: + if (session->state == SMTP_STATE_GREETING || session->state == SMTP_STATE_HELO) { + if (parse_smtp_helo (session, cmd)) { + session->state = SMTP_STATE_FROM; + } + return TRUE; + } + else { + goto improper_sequence; + } + break; + case SMTP_COMMAND_QUIT: + session->state = SMTP_STATE_END; + break; + case SMTP_COMMAND_NOOP: + break; + case SMTP_COMMAND_MAIL: + if ((session->state == SMTP_STATE_GREETING || session->state == SMTP_STATE_HELO && !session->ctx->helo_required) + || session->state == SMTP_STATE_FROM) { + if (parse_smtp_from (session, cmd)) { + session->state = SMTP_STATE_RCPT; + } + else { + return FALSE; + } + } + else { + goto improper_sequence; + } + break; + case SMTP_COMMAND_RCPT: + if (session->state == SMTP_STATE_RCPT) { + if (parse_smtp_rcpt (session, cmd)) { + /* Make upstream connection */ + if (!create_smtp_upstream_connection (session)) { + session->error = SMTP_ERROR_UPSTREAM; + session->state = SMTP_STATE_CRITICAL_ERROR; + return FALSE; + } + session->state = SMTP_STATE_WAIT_UPSTREAM; + return TRUE; + } + else { + return FALSE; + } + } + else { + goto improper_sequence; + } + break; + case SMTP_COMMAND_RSET: + session->from = NULL; + if (session->rcpt) { + g_list_free (session->rcpt); + } + session->state = SMTP_STATE_GREETING; + break; + case SMTP_COMMAND_DATA: + if (session->state == SMTP_STATE_RCPT) { + if (session->rcpt == NULL) { + session->error = SMTP_ERROR_RECIPIENTS; + return FALSE; + } + session->error = SMTP_ERROR_DATA_OK; + } + else { + goto improper_sequence; + } + case SMTP_COMMAND_VRFY: + case SMTP_COMMAND_EXPN: + case SMTP_COMMAND_HELP: + session->error = SMTP_ERROR_UNIMPLIMENTED; + return FALSE; + } + + session->error = SMTP_ERROR_OK; + return TRUE; +improper_sequence: + session->error = SMTP_ERROR_SEQUENCE; return FALSE; } @@ -142,7 +262,7 @@ smtp_read_socket (f_str_t * in, void *arg) case SMTP_STATE_RESOLVE_REVERSE: case SMTP_STATE_RESOLVE_NORMAL: case SMTP_STATE_DELAY: - session->error = make_smtp_error (session, 550, "%s Improper use of SMTP command pipelining"); + session->error = make_smtp_error (session, 550, "%s Improper use of SMTP command pipelining", "5.5.0"); session->state = SMTP_STATE_ERROR; break; case SMTP_STATE_GREETING: @@ -150,14 +270,25 @@ smtp_read_socket (f_str_t * in, void *arg) case SMTP_STATE_FROM: case SMTP_STATE_RCPT: case SMTP_STATE_DATA: - return read_smtp_command (session, in); + read_smtp_command (session, in); + if (session->state != SMTP_STATE_WAIT_UPSTREAM) { + smtp_write_socket (session); + } break; default: - session->error = make_smtp_error (session, 550, "%s Internal error"); + session->error = make_smtp_error (session, 550, "%s Internal error", "5.5.0"); session->state = SMTP_STATE_ERROR; break; } + if (session->state == SMTP_STATE_END) { + destroy_session (session->s); + return FALSE; + } + else if (session->state == SMTP_STATE_WAIT_UPSTREAM) { + rspamd_dispatcher_pause (session->dispatcher); + } + return TRUE; } @@ -169,11 +300,19 @@ smtp_write_socket (void *arg) { struct smtp_session *session = arg; - if (session->state == SMTP_STATE_WRITE_ERROR) { - rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + if (session->state == SMTP_STATE_CRITICAL_ERROR) { + if (session->error != NULL) { + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + } destroy_session (session->s); return FALSE; } + else { + if (session->error != NULL) { + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + } + return TRUE; + } return TRUE; } @@ -215,7 +354,7 @@ smtp_delay_handler (int fd, short what, void *arg) write_smtp_greeting (session); } else { - session->state = SMTP_STATE_WRITE_ERROR; + session->state = SMTP_STATE_CRITICAL_ERROR; smtp_write_socket (session); } } @@ -228,12 +367,20 @@ smtp_make_delay (struct smtp_session *session) { struct event *tev; struct timeval *tv; + gint32 jitter; if (session->ctx->smtp_delay != 0 && session->state == SMTP_STATE_DELAY) { tev = memory_pool_alloc (session->pool, sizeof (struct event)); tv = memory_pool_alloc (session->pool, sizeof (struct timeval)); - tv->tv_sec = session->ctx->smtp_delay / 1000; - tv->tv_usec = session->ctx->smtp_delay - tv->tv_sec * 1000; + if (session->ctx->delay_jitter != 0) { + jitter = g_random_int_range (0, session->ctx->delay_jitter); + tv->tv_sec = (session->ctx->smtp_delay + jitter) / 1000; + tv->tv_usec = session->ctx->smtp_delay + jitter - tv->tv_sec * 1000; + } + else { + tv->tv_sec = session->ctx->smtp_delay / 1000; + tv->tv_usec = session->ctx->smtp_delay - tv->tv_sec * 1000; + } evtimer_set (tev, smtp_delay_handler, session); evtimer_add (tev, tv); @@ -341,7 +488,7 @@ accept_socket (int fd, short what, void *arg) return; } - session = g_malloc (sizeof (struct smtp_session)); + session = g_malloc0 (sizeof (struct smtp_session)); session->pool = memory_pool_new (memory_pool_get_size ()); if (su.ss.ss_family == AF_UNIX) { @@ -354,7 +501,9 @@ accept_socket (int fd, short what, void *arg) } session->sock = nfd; + session->worker = worker; session->ctx = worker->ctx; + session->session_time = time (NULL); worker->srv->stat->connections_count++; /* Resolve client's addr */ @@ -388,7 +537,7 @@ parse_smtp_banner (struct smtp_worker_ctx *ctx, const char *line) switch (*p) { case 'n': /* Assume %n as CRLF */ - banner_len += sizeof (CRLF) - 1 + sizeof ("220 -") - 1 - 2; + banner_len += sizeof (CRLF) - 1 + sizeof ("220 -") - 1; has_crlf = TRUE; break; case 'h': @@ -396,7 +545,7 @@ parse_smtp_banner (struct smtp_worker_ctx *ctx, const char *line) hostbuf = alloca (hostmax); gethostname (hostbuf, hostmax); hostbuf[hostmax - 1] = '\0'; - banner_len += strlen (hostbuf) - 2; + banner_len += strlen (hostbuf); break; case '%': banner_len += 1; @@ -407,12 +556,17 @@ parse_smtp_banner (struct smtp_worker_ctx *ctx, const char *line) } } else { - banner_len += 1; + banner_len ++; } p ++; } - banner_len += sizeof (CRLF); + if (has_crlf) { + banner_len += sizeof (CRLF "220 " CRLF); + } + else { + banner_len += sizeof (CRLF); + } ctx->smtp_banner = memory_pool_alloc (ctx->pool, banner_len + 1); t = ctx->smtp_banner; @@ -433,12 +587,15 @@ parse_smtp_banner (struct smtp_worker_ctx *ctx, const char *line) /* Assume %n as CRLF */ *t++ = CR; *t++ = LF; t = g_stpcpy (t, "220-"); + p ++; break; case 'h': t = g_stpcpy (t, hostbuf); + p ++; break; case '%': *t++ = '%'; + p ++; break; default: /* Copy all % to dest */ @@ -450,18 +607,23 @@ parse_smtp_banner (struct smtp_worker_ctx *ctx, const char *line) *t ++ = *p ++; } } - t = g_stpcpy (t, CRLF); + if (has_crlf) { + t = g_stpcpy (t, CRLF "220 " CRLF); + } + else { + t = g_stpcpy (t, CRLF); + } } static gboolean parse_upstreams_line (struct smtp_worker_ctx *ctx, const char *line) { - char **strv, *p, *t, *err_str; + char **strv, *p, *t, *tt, *err_str; uint32_t num, i; struct smtp_upstream *cur; char resolved_path[PATH_MAX]; - strv = g_strsplit (line, ",; ", 0); + strv = g_strsplit_set (line, ",; ", -1); num = g_strv_length (strv); if (num >= MAX_UPSTREAM) { @@ -472,7 +634,7 @@ parse_upstreams_line (struct smtp_worker_ctx *ctx, const char *line) for (i = 0; i < num; i ++) { p = strv[i]; cur = &ctx->upstreams[ctx->upstream_num]; - if ((t = strrchr (p, ':')) != NULL) { + if ((t = strrchr (p, ':')) != NULL && (tt = strchr (p, ':')) != t) { /* Assume that after last `:' we have weigth */ *t = '\0'; t ++; @@ -508,6 +670,50 @@ parse_upstreams_line (struct smtp_worker_ctx *ctx, const char *line) return TRUE; } +static void +make_capabilities (struct smtp_worker_ctx *ctx, const char *line) +{ + char **strv, *p, *result, *hostbuf; + uint32_t num, i, len, hostmax; + + strv = g_strsplit_set (line, ",;", -1); + num = g_strv_length (strv); + + hostmax = sysconf (_SC_HOST_NAME_MAX) + 1; + hostbuf = alloca (hostmax); + gethostname (hostbuf, hostmax); + hostbuf[hostmax - 1] = '\0'; + + len = sizeof ("250-") + strlen (hostbuf) + sizeof (CRLF) - 1; + + for (i = 0; i < num; i ++) { + p = strv[i]; + len += sizeof ("250-") + sizeof (CRLF) + strlen (p) - 2; + } + + result = memory_pool_alloc (ctx->pool, len); + ctx->smtp_capabilities = result; + + p = result; + if (num == 0) { + p += snprintf (p, len - (p - result), "250 %s" CRLF, hostbuf); + } + else { + p += snprintf (p, len - (p - result), "250-%s" CRLF, hostbuf); + for (i = 0; i < num; i ++) { + if (i != num - 1) { + p += snprintf (p, len - (p - result), "250-%s" CRLF, strv[i]); + } + else { + p += snprintf (p, len - (p - result), "250 %s" CRLF, strv[i]); + } + } + } + + g_strfreev (strv); +} + + static gboolean config_smtp_worker (struct rspamd_worker *worker) { @@ -530,9 +736,10 @@ config_smtp_worker (struct rspamd_worker *worker) } } else { + msg_err ("no upstreams defined, don't know what to do"); return FALSE; } - if ((value = g_hash_table_lookup (worker->cf->params, "banner")) != NULL) { + if ((value = g_hash_table_lookup (worker->cf->params, "smtp_banner")) != NULL) { parse_smtp_banner (ctx, value); } if ((value = g_hash_table_lookup (worker->cf->params, "smtp_timeout")) != NULL) { @@ -547,12 +754,15 @@ config_smtp_worker (struct rspamd_worker *worker) } } if ((value = g_hash_table_lookup (worker->cf->params, "smtp_delay")) != NULL) { - errno = 0; - ctx->smtp_delay = strtoul (value, &err_str, 10); - if (errno != 0 || (err_str && *err_str != '\0')) { - msg_warn ("cannot parse delay, invalid number: %s: %s", value, strerror (errno)); - } + ctx->smtp_delay = parse_seconds (value); + } + if ((value = g_hash_table_lookup (worker->cf->params, "smtp_jitter")) != NULL) { + ctx->delay_jitter = parse_seconds (value); + } + if ((value = g_hash_table_lookup (worker->cf->params, "smtp_capabilities")) != NULL) { + make_capabilities (ctx, value); } + /* Set ctx */ worker->ctx = ctx; @@ -573,6 +783,12 @@ start_smtp_worker (struct rspamd_worker *worker) worker->srv->pid = getpid (); + /* Set smtp options */ + if ( !config_smtp_worker (worker)) { + msg_err ("cannot configure smtp worker, exiting"); + exit (EXIT_SUCCESS); + } + event_init (); evdns_init (); @@ -590,9 +806,6 @@ start_smtp_worker (struct rspamd_worker *worker) /* Maps events */ start_map_watch (); - /* Set smtp options */ - config_smtp_worker (worker); - event_loop (0); close_log (); diff --git a/src/smtp.h b/src/smtp.h index 6d9d7555f..36319bb4c 100644 --- a/src/smtp.h +++ b/src/smtp.h @@ -23,11 +23,12 @@ struct smtp_worker_ctx { memory_pool_t *pool; char *smtp_banner; uint32_t smtp_delay; + uint32_t delay_jitter; struct timeval smtp_timeout; gboolean use_xclient; gboolean helo_required; - const char *smtp_capabilities; + char *smtp_capabilities; }; enum rspamd_smtp_state { @@ -38,10 +39,13 @@ enum rspamd_smtp_state { SMTP_STATE_HELO, SMTP_STATE_FROM, SMTP_STATE_RCPT, + SMTP_STATE_BEFORE_DATA, SMTP_STATE_DATA, SMTP_STATE_EOD, SMTP_STATE_END, + SMTP_STATE_WAIT_UPSTREAM, SMTP_STATE_ERROR, + SMTP_STATE_CRITICAL_ERROR, SMTP_STATE_WRITE_ERROR }; @@ -50,18 +54,28 @@ struct smtp_session { memory_pool_t *pool; enum rspamd_smtp_state state; + enum rspamd_smtp_state upstream_state; + struct rspamd_worker *worker; struct worker_task *task; struct in_addr client_addr; char *hostname; char *error; int sock; + int upstream_sock; + time_t session_time; + + gchar *helo; + GList *from; + GList *rcpt; + GList *cur_rcpt; struct rspamd_async_session *s; rspamd_io_dispatcher_t *dispatcher; + rspamd_io_dispatcher_t *upstream_dispatcher; struct smtp_upstream *upstream; - int upstream_sock; gboolean resolved; + gboolean esmtp; }; void start_smtp_worker (struct rspamd_worker *worker); diff --git a/src/smtp_proto.c b/src/smtp_proto.c new file mode 100644 index 000000000..82fffa690 --- /dev/null +++ b/src/smtp_proto.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2009, Rambler media + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY Rambler media ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Rambler BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "main.h" +#include "cfg_file.h" +#include "util.h" +#include "smtp.h" +#include "smtp_proto.h" + +char * +make_smtp_error (struct smtp_session *session, int error_code, const char *format, ...) +{ + va_list vp; + char *result = NULL, *p; + size_t len; + + va_start (vp, format); + len = g_printf_string_upper_bound (format, vp); + va_end (vp); + va_start (vp, format); + len += sizeof ("65535 ") + sizeof (CRLF) - 1; + result = memory_pool_alloc (session->pool, len); + p = result + snprintf (result, len, "%d ", error_code); + p += vsnprintf (p, len - (p - result), format, vp); + *p++ = CR; *p++ = LF; *p = '\0'; + va_end (vp); + + return result; +} + + +gboolean +parse_smtp_command (struct smtp_session *session, f_str_t *line, struct smtp_command **cmd) +{ + enum { + SMTP_PARSE_START = 0, + SMTP_PARSE_SPACES, + SMTP_PARSE_ARGUMENT, + SMTP_PARSE_DONE + } state; + gchar *p, *c, ch, cmd_buf[4]; + int i; + f_str_t *arg = NULL; + struct smtp_command *pcmd; + + if (line->len == 0) { + return FALSE; + } + + state = SMTP_PARSE_START; + c = line->begin; + p = c; + *cmd = memory_pool_alloc0 (session->pool, sizeof (struct smtp_command)); + pcmd = *cmd; + + for (i = 0; i < line->len; i ++, p ++) { + ch = *p; + switch (state) { + case SMTP_PARSE_START: + if (ch == ' ' || ch == ':' || ch == CR || ch == LF || i == line->len - 1) { + if (i == line->len - 1) { + p ++; + } + if (p - c == 4) { + cmd_buf[0] = g_ascii_toupper (c[0]); + cmd_buf[1] = g_ascii_toupper (c[1]); + cmd_buf[2] = g_ascii_toupper (c[2]); + cmd_buf[3] = g_ascii_toupper (c[3]); + + if (memcmp (cmd_buf, "HELO", 4) == 0) { + pcmd->command = SMTP_COMMAND_HELO; + } + else if (memcmp (cmd_buf, "EHLO", 4) == 0) { + pcmd->command = SMTP_COMMAND_EHLO; + } + else if (memcmp (cmd_buf, "MAIL", 4) == 0) { + pcmd->command = SMTP_COMMAND_MAIL; + } + else if (memcmp (cmd_buf, "RCPT", 4) == 0) { + pcmd->command = SMTP_COMMAND_RCPT; + } + else if (memcmp (cmd_buf, "DATA", 4) == 0) { + pcmd->command = SMTP_COMMAND_DATA; + } + else if (memcmp (cmd_buf, "QUIT", 4) == 0) { + pcmd->command = SMTP_COMMAND_QUIT; + } + else if (memcmp (cmd_buf, "NOOP", 4) == 0) { + pcmd->command = SMTP_COMMAND_NOOP; + } + else if (memcmp (cmd_buf, "EXPN", 4) == 0) { + pcmd->command = SMTP_COMMAND_EXPN; + } + else if (memcmp (cmd_buf, "RSET", 4) == 0) { + pcmd->command = SMTP_COMMAND_RSET; + } + else if (memcmp (cmd_buf, "HELP", 4) == 0) { + pcmd->command = SMTP_COMMAND_HELP; + } + else if (memcmp (cmd_buf, "VRFY", 4) == 0) { + pcmd->command = SMTP_COMMAND_VRFY; + } + else { + msg_info ("invalid command: %*s", 4, cmd_buf); + return FALSE; + } + } + else { + /* Invalid command */ + msg_info ("invalid command: %*s", 4, c); + return FALSE; + } + /* Now check what we have */ + if (ch == ' ' || ch == ':') { + state = SMTP_PARSE_SPACES; + } + else if (ch == CR) { + state = SMTP_PARSE_DONE; + } + else if (ch == LF) { + return TRUE; + } + } + else if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) { + msg_info ("invalid letter code in SMTP command: %d", (int)ch); + return FALSE; + } + break; + case SMTP_PARSE_SPACES: + if (ch == CR) { + state = SMTP_PARSE_DONE; + } + else if (ch == LF) { + goto end; + } + else if (ch != ' ' && ch != ':') { + state = SMTP_PARSE_ARGUMENT; + arg = memory_pool_alloc (session->pool, sizeof (f_str_t)); + c = p; + } + break; + case SMTP_PARSE_ARGUMENT: + if (ch == ' ' || ch == ':' || ch == CR || ch == LF || i == line->len - 1) { + if (i == line->len - 1) { + p ++; + } + arg->len = p - c; + arg->begin = memory_pool_alloc (session->pool, arg->len); + memcpy (arg->begin, c, arg->len); + pcmd->args = g_list_prepend (pcmd->args, arg); + if (ch == ' ' || ch == ':') { + state = SMTP_PARSE_SPACES; + } + else if (ch == CR) { + state = SMTP_PARSE_DONE; + } + else { + goto end; + } + } + break; + case SMTP_PARSE_DONE: + if (ch == LF) { + goto end; + } + msg_info ("CR without LF in SMTP command"); + return FALSE; + } + } + +end: + if (pcmd->args) { + pcmd->args = g_list_reverse (pcmd->args); + memory_pool_add_destructor (session->pool, (pool_destruct_func)g_list_free, pcmd->args); + } + return TRUE; +} + +static gboolean +check_smtp_path (f_str_t *path) +{ + int i; + char *p; + + p = path->begin; + if (*p != '<' || path->len < 2) { + return FALSE; + } + for (i = 0; i < path->len; i++, p ++) { + if (*p == '>' && i != path->len - 1) { + return FALSE; + } + } + + return *(p - 1) == '>'; +} + +gboolean +parse_smtp_helo (struct smtp_session *session, struct smtp_command *cmd) +{ + f_str_t *arg; + + if (cmd->args == NULL) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + arg = cmd->args->data; + session->helo = memory_pool_alloc (session->pool, arg->len + 1); + g_strlcpy (session->helo, arg->begin, arg->len + 1); + /* Now try to write reply */ + if (cmd->command == SMTP_COMMAND_HELO) { + /* No ESMTP */ + session->error = SMTP_ERROR_OK; + session->esmtp = FALSE; + return TRUE; + } + else { + /* Try to write all capabilities */ + session->esmtp = TRUE; + if (session->ctx->smtp_capabilities == NULL) { + session->error = SMTP_ERROR_OK; + return TRUE; + } + else { + session->error = session->ctx->smtp_capabilities; + return TRUE; + } + } + + return FALSE; +} + +gboolean +parse_smtp_from (struct smtp_session *session, struct smtp_command *cmd) +{ + f_str_t *arg; + GList *cur = cmd->args; + + if (cmd->args == NULL) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + arg = cur->data; + /* First argument MUST be FROM */ + if (arg->len != 4 || ( + g_ascii_toupper (arg->begin[0]) != 'F' || + g_ascii_toupper (arg->begin[1]) != 'R' || + g_ascii_toupper (arg->begin[2]) != 'O' || + g_ascii_toupper (arg->begin[3]) != 'M')) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + /* Next one is from address */ + cur = g_list_next (cur); + if (cur == NULL) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + arg = cur->data; + if (check_smtp_path (arg)) { + session->from = cur; + } + else { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + + return TRUE; +} + +gboolean +parse_smtp_rcpt (struct smtp_session *session, struct smtp_command *cmd) +{ + f_str_t *arg; + GList *cur = cmd->args; + + if (cmd->args == NULL) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + arg = cur->data; + /* First argument MUST be FROM */ + if (arg->len != 2 || ( + g_ascii_toupper (arg->begin[0]) != 'T' || + g_ascii_toupper (arg->begin[1]) != 'O')) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + /* Next one is from address */ + cur = g_list_next (cur); + if (cur == NULL) { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + arg = cur->data; + if (check_smtp_path (arg)) { + session->rcpt = g_list_prepend (session->rcpt, cur); + } + else { + session->error = SMTP_ERROR_BAD_ARGUMENTS; + return FALSE; + } + + return TRUE; + +} + +/* Return -1 if there are some error, 1 if all is ok and 0 in case of incomplete reply */ +static int +check_smtp_ustream_reply (f_str_t *in) +{ + char *p; + + /* Check for 250 at the begin of line */ + if (in->len >= sizeof ("220 ") - 1) { + p = in->begin; + if (p[0] == '2') { + /* Last reply line */ + if (p[3] == ' ') { + return 1; + } + else { + return 0; + } + } + else { + return -1; + } + } + + return -1; +} + +static size_t +smtp_upstream_write_list (GList *args, char *buf, size_t buflen) +{ + GList *cur = args; + size_t r = 0; + f_str_t *arg; + + while (cur && r < buflen - 3) { + arg = cur->data; + r += rspamd_snprintf (buf + r, buflen - r, " %V", arg); + cur = g_list_next (cur); + } + + buf[r++] = CR; + buf[r++] = LF; + buf[r] = '\0'; + + return r; +} + +gboolean +smtp_upstream_read_socket (f_str_t * in, void *arg) +{ + struct smtp_session *session = arg; + char outbuf[BUFSIZ]; + int r; + + switch (session->upstream_state) { + case SMTP_STATE_GREETING: + r = check_smtp_ustream_reply (in); + if (r == -1) { + session->error = memory_pool_alloc (session->pool, in->len + 3); + g_strlcpy (session->error, in->begin, in->len + 1); + /* XXX: assume upstream errors as critical errors */ + session->state = SMTP_STATE_CRITICAL_ERROR; + rspamd_dispatcher_restore (session->dispatcher); + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + destroy_session (session->s); + return FALSE; + } + else if (r == 1) { + if (session->ctx->use_xclient) { + r = snprintf (outbuf, sizeof (outbuf), "XCLIENT NAME=%s ADDR=%s" CRLF, + session->resolved ? session->hostname : "[UNDEFINED]", + inet_ntoa (session->client_addr)); + session->upstream_state = SMTP_STATE_HELO; + return rspamd_dispatcher_write (session->upstream_dispatcher, outbuf, r, FALSE, FALSE); + } + else { + session->upstream_state = SMTP_STATE_FROM; + if (session->helo) { + r = snprintf (outbuf, sizeof (outbuf), "%s %s" CRLF, + session->esmtp ? "EHLO" : "HELO", + session->helo); + } + else { + return smtp_upstream_read_socket (in, arg); + } + return rspamd_dispatcher_write (session->upstream_dispatcher, outbuf, r, FALSE, FALSE); + } + } + break; + case SMTP_STATE_HELO: + r = check_smtp_ustream_reply (in); + if (r == -1) { + session->error = memory_pool_alloc (session->pool, in->len + 1); + g_strlcpy (session->error, in->begin, in->len + 1); + /* XXX: assume upstream errors as critical errors */ + session->state = SMTP_STATE_CRITICAL_ERROR; + rspamd_dispatcher_restore (session->dispatcher); + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + destroy_session (session->s); + return FALSE; + } + else if (r == 1) { + session->upstream_state = SMTP_STATE_FROM; + if (session->helo) { + r = snprintf (outbuf, sizeof (outbuf), "%s %s" CRLF, + session->esmtp ? "EHLO" : "HELO", + session->helo); + } + else { + return smtp_upstream_read_socket (in, arg); + } + return rspamd_dispatcher_write (session->upstream_dispatcher, outbuf, r, FALSE, FALSE); + } + break; + case SMTP_STATE_FROM: + r = check_smtp_ustream_reply (in); + if (r == -1) { + session->error = memory_pool_alloc (session->pool, in->len + 1); + g_strlcpy (session->error, in->begin, in->len + 1); + /* XXX: assume upstream errors as critical errors */ + session->state = SMTP_STATE_CRITICAL_ERROR; + rspamd_dispatcher_restore (session->dispatcher); + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + destroy_session (session->s); + return FALSE; + } + else if (r == 1) { + r = snprintf (outbuf, sizeof (outbuf), "MAIL FROM: "); + r += smtp_upstream_write_list (session->from, outbuf + r, sizeof (outbuf) - r); + session->upstream_state = SMTP_STATE_RCPT; + return rspamd_dispatcher_write (session->upstream_dispatcher, outbuf, r, FALSE, FALSE); + } + break; + case SMTP_STATE_RCPT: + r = check_smtp_ustream_reply (in); + if (r == -1) { + session->error = memory_pool_alloc (session->pool, in->len + 1); + g_strlcpy (session->error, in->begin, in->len + 1); + /* XXX: assume upstream errors as critical errors */ + session->state = SMTP_STATE_CRITICAL_ERROR; + rspamd_dispatcher_restore (session->dispatcher); + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + destroy_session (session->s); + return FALSE; + } + else if (r == 1) { + r = snprintf (outbuf, sizeof (outbuf), "RCPT TO: "); + session->cur_rcpt = g_list_first (session->rcpt); + r += smtp_upstream_write_list (session->cur_rcpt->data, outbuf + r, sizeof (outbuf) - r); + session->cur_rcpt = g_list_next (session->cur_rcpt); + session->upstream_state = SMTP_STATE_BEFORE_DATA; + return rspamd_dispatcher_write (session->upstream_dispatcher, outbuf, r, FALSE, FALSE); + } + break; + case SMTP_STATE_BEFORE_DATA: + r = check_smtp_ustream_reply (in); + if (r == -1) { + session->error = memory_pool_alloc (session->pool, in->len + 1); + g_strlcpy (session->error, in->begin, in->len + 1); + rspamd_dispatcher_restore (session->dispatcher); + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + session->rcpt = g_list_delete_link (session->rcpt, session->cur_rcpt); + return TRUE; + } + else if (r == 1) { + if (session->cur_rcpt != NULL) { + r = snprintf (outbuf, sizeof (outbuf), "RCPT TO: "); + r += smtp_upstream_write_list (session->cur_rcpt, outbuf + r, sizeof (outbuf) - r); + session->cur_rcpt = g_list_next (session->cur_rcpt); + } + else { + session->upstream_state = SMTP_STATE_DATA; + rspamd_dispatcher_pause (session->upstream_dispatcher); + } + session->error = memory_pool_alloc (session->pool, in->len + 1); + g_strlcpy (session->error, in->begin, in->len + 1); + /* Write to client */ + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + if (session->state == SMTP_STATE_WAIT_UPSTREAM) { + rspamd_dispatcher_restore (session->dispatcher); + session->state = SMTP_STATE_RCPT; + } + return rspamd_dispatcher_write (session->upstream_dispatcher, outbuf, r, FALSE, FALSE); + } + break; + } + + return TRUE; +} + +void +smtp_upstream_err_socket (GError *err, void *arg) +{ + struct smtp_session *session = arg; + + msg_info ("abnormally closing connection with upstream %s, error: %s", session->upstream->name, err->message); + session->error = SMTP_ERROR_UPSTREAM; + session->state = SMTP_STATE_CRITICAL_ERROR; + /* XXX: assume upstream errors as critical errors */ + rspamd_dispatcher_restore (session->dispatcher); + rspamd_dispatcher_write (session->dispatcher, session->error, 0, FALSE, TRUE); + rspamd_dispatcher_write (session->dispatcher, CRLF, sizeof (CRLF) - 1, FALSE, TRUE); + upstream_fail (&session->upstream->up, session->session_time); + destroy_session (session->s); +} + +void +smtp_upstream_finalize_connection (gpointer data) +{ + struct smtp_session *session = data; + + if (session->state != SMTP_STATE_CRITICAL_ERROR) { + rspamd_dispatcher_write (session->upstream_dispatcher, "QUIT" CRLF, 0, FALSE, TRUE); + } + rspamd_remove_dispatcher (session->upstream_dispatcher); + session->upstream_dispatcher = NULL; + close (session->upstream_sock); + session->upstream_sock = -1; +} diff --git a/src/smtp_proto.h b/src/smtp_proto.h new file mode 100644 index 000000000..c78cfb094 --- /dev/null +++ b/src/smtp_proto.h @@ -0,0 +1,46 @@ +#ifndef RSPAMD_SMTP_PROTO_H +#define RSPAMD_SMTP_PROTO_H + +#include "config.h" +#include "smtp.h" + +/* SMTP errors */ +#define SMTP_ERROR_BAD_COMMAND "500 Syntax error, command unrecognized" CRLF +#define SMTP_ERROR_BAD_ARGUMENTS "501 Syntax error in parameters or arguments" CRLF +#define SMTP_ERROR_SEQUENCE "503 Bad sequence of commands" CRLF +#define SMTP_ERROR_RECIPIENTS "554 No valid recipients" CRLF +#define SMTP_ERROR_UNIMPLIMENTED "502 Command not implemented" CRLF +#define SMTP_ERROR_UPSTREAM "421 Service not available, closing transmission channel" CRLF +#define SMTP_ERROR_OK "250 Requested mail action okay, completed" CRLF +#define SMTP_ERROR_DATA_OK "354 Start mail input; end with ." CRLF + + +struct smtp_command { + enum { + SMTP_COMMAND_HELO, + SMTP_COMMAND_EHLO, + SMTP_COMMAND_QUIT, + SMTP_COMMAND_NOOP, + SMTP_COMMAND_MAIL, + SMTP_COMMAND_RCPT, + SMTP_COMMAND_RSET, + SMTP_COMMAND_DATA, + SMTP_COMMAND_VRFY, + SMTP_COMMAND_EXPN, + SMTP_COMMAND_HELP + } command; + GList *args; +}; + +char * make_smtp_error (struct smtp_session *session, int error_code, const char *format, ...); +gboolean parse_smtp_command (struct smtp_session *session, f_str_t *line, struct smtp_command **cmd); +gboolean parse_smtp_helo (struct smtp_session *session, struct smtp_command *cmd); +gboolean parse_smtp_from (struct smtp_session *session, struct smtp_command *cmd); +gboolean parse_smtp_rcpt (struct smtp_session *session, struct smtp_command *cmd); + +/* Upstream SMTP */ +gboolean smtp_upstream_read_socket (f_str_t * in, void *arg); +void smtp_upstream_err_socket (GError *err, void *arg); +void smtp_upstream_finalize_connection (gpointer data); + +#endif -- 2.39.5