#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;
}
}
-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)
if (session->task) {
free_task (session->task, FALSE);
}
+ if (session->rcpt) {
+ g_list_free (session->rcpt);
+ }
if (session->dispatcher) {
rspamd_remove_dispatcher (session->dispatcher);
}
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;
}
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:
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;
}
{
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;
}
write_smtp_greeting (session);
}
else {
- session->state = SMTP_STATE_WRITE_ERROR;
+ session->state = SMTP_STATE_CRITICAL_ERROR;
smtp_write_socket (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);
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) {
}
session->sock = nfd;
+ session->worker = worker;
session->ctx = worker->ctx;
+ session->session_time = time (NULL);
worker->srv->stat->connections_count++;
/* Resolve client's addr */
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':
hostbuf = alloca (hostmax);
gethostname (hostbuf, hostmax);
hostbuf[hostmax - 1] = '\0';
- banner_len += strlen (hostbuf) - 2;
+ banner_len += strlen (hostbuf);
break;
case '%':
banner_len += 1;
}
}
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;
/* 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 */
*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) {
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 ++;
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)
{
}
}
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) {
}
}
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;
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 ();
/* Maps events */
start_map_watch ();
- /* Set smtp options */
- config_smtp_worker (worker);
-
event_loop (0);
close_log ();
--- /dev/null
+/*
+ * 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;
+}