aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/buffer.c6
-rw-r--r--src/buffer.h6
-rw-r--r--src/smtp.c299
-rw-r--r--src/smtp.h18
-rw-r--r--src/smtp_proto.c553
-rw-r--r--src/smtp_proto.h46
7 files changed, 884 insertions, 45 deletions
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
@@ -93,6 +93,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);
}
@@ -123,10 +115,138 @@ sigusr_handler (int fd, short what, void *arg)
}
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 %<char> 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>.<CRLF>" 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