]> source.dussan.org Git - rspamd.git/commitdiff
* Implement basic SMTP dialog:
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Wed, 9 Jun 2010 17:51:25 +0000 (21:51 +0400)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Wed, 9 Jun 2010 17:51:25 +0000 (21:51 +0400)
  - delay
  - helo
  - mail from
  - rcpt
* Implement interaction with smtp upstream (with support of XCLIENT)

CMakeLists.txt
src/buffer.c
src/buffer.h
src/smtp.c
src/smtp.h
src/smtp_proto.c [new file with mode: 0644]
src/smtp_proto.h [new file with mode: 0644]

index 75cd5083ad49e1b3bebce3ba616129bcc8a8bf66..9e929ec1e43efdb5b7097d5c6edbbf1ea8cd85d5 100644 (file)
@@ -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
index ec435bc836d248f7a92d7c7b1415f0b8bb978b88..7dd43d2ade9886b2d8c23678453b56447576ed16 100644 (file)
@@ -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 
  */
index 04845814a95e53f39302fd9a72e32902999cdfdb..4cf9de5558af80dbbddd87facede85012add9ec6 100644 (file)
@@ -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
index fc2811bed63c703dbe340a8836a8fac28eadc096..1bf135d3090ff6360c8b90e2b4c8f0be6b09f5b3 100644 (file)
 #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 %<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 ();
index 6d9d7555f0bfec78e46fcd596e8ea3f3e8888355..36319bb4c31c1d4bb34dfb7b0e2d2009b86f93f7 100644 (file)
@@ -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 (file)
index 0000000..82fffa6
--- /dev/null
@@ -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 (file)
index 0000000..c78cfb0
--- /dev/null
@@ -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