aboutsummaryrefslogtreecommitdiffstats
path: root/src/smtp_proxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/smtp_proxy.c')
-rw-r--r--src/smtp_proxy.c240
1 files changed, 226 insertions, 14 deletions
diff --git a/src/smtp_proxy.c b/src/smtp_proxy.c
index 1ada5ff1e..97e56a956 100644
--- a/src/smtp_proxy.c
+++ b/src/smtp_proxy.c
@@ -85,7 +85,10 @@ struct smtp_proxy_ctx {
enum rspamd_smtp_proxy_state {
SMTP_PROXY_STATE_RESOLVE_REVERSE = 0,
SMTP_PROXY_STATE_RESOLVE_NORMAL,
- SMTP_PROXY_STATE_DELAY
+ SMTP_PROXY_STATE_DELAY,
+ SMTP_PROXY_STATE_GREETING,
+ SMTP_PROXY_STATE_XCLIENT,
+ SMTP_PROXY_STATE_PROXY
};
struct smtp_proxy_session {
@@ -109,10 +112,13 @@ struct smtp_proxy_session {
struct smtp_upstream *upstream;
struct event *delay_timer;
+ struct event upstream_ev;
gboolean resolved;
struct rspamd_dns_resolver *resolver;
struct event_base *ev_base;
+
+ GString *upstream_greeting;
};
#ifndef HAVE_SA_SIGINFO
@@ -180,13 +186,31 @@ static void
free_smtp_proxy_session (gpointer arg)
{
struct smtp_proxy_session *session = arg;
+ static const char fatal_smtp_error[] = "521 5.2.1 Internal error" CRLF;
if (session) {
if (session->dispatcher) {
rspamd_remove_dispatcher (session->dispatcher);
}
+ if (session->upstream_greeting) {
+ g_string_free (session->upstream_greeting, TRUE);
+ }
+
+ if (session->state != SMTP_PROXY_STATE_PROXY) {
+ /* Send 521 fatal error */
+ write (session->sock, fatal_smtp_error, sizeof (fatal_smtp_error));
+ }
+
close (session->sock);
+
+ if (session->proxy) {
+ rspamd_proxy_close (session->proxy);
+ }
+ if (session->upstream_sock != -1) {
+ event_del (&session->upstream_ev);
+ close (session->upstream_sock);
+ }
memory_pool_delete (session->pool);
g_slice_free1 (sizeof (struct smtp_proxy_session), session);
}
@@ -205,11 +229,193 @@ smtp_proxy_err_proxy (GError * err, void *arg)
destroy_session (session->s);
}
+/**
+ * Check whether SMTP greeting is valid
+ * @param s
+ * @return
+ */
+static gint
+check_valid_smtp_greeting (GString *s)
+{
+ gchar *p;
+
+ p = s->str + s->len - 1;
+ if (s->len < 6 || (*p != '\n' || *(p - 1) != '\r')) {
+ return 1;
+ }
+ p -= 5;
+
+ while (p >= s->str) {
+ /* It is fast to use memcmp here as we compare only 4 bytes */
+ if (memcmp (p, "220 ", 4) == 0) {
+ /* Check position */
+ if (p == s->str || *(p - 1) == '\n') {
+ return 1;
+ }
+ return 0;
+ }
+ else if ((*p == '5' || *p == '4' || *p == '3') &&
+ g_ascii_isdigit (p[1]) && g_ascii_isdigit (p[2]) && p[3] == ' ') {
+ return -1;
+ }
+ p --;
+ }
+
+ return 1;
+}
+
+/*
+ * Handle upstream greeting
+ */
+
+static void
+smtp_proxy_greeting_handler (gint fd, short what, void *arg)
+{
+ struct smtp_proxy_session *session = arg;
+ gint r;
+ gchar read_buf[BUFSIZ];
+
+ if (what == EV_READ) {
+ if (session->state == SMTP_PROXY_STATE_GREETING) {
+ /* Fill greeting buffer with new portion of data */
+ r = read (fd, read_buf, sizeof (read_buf) - 1);
+ if (r > 0) {
+ g_string_append_len (session->upstream_greeting, read_buf, r);
+ /* Now search line with 220 */
+ r = check_valid_smtp_greeting (session->upstream_greeting);
+ if (r == 1) {
+ /* Send xclient */
+ if (session->ctx->use_xclient) {
+ r = rspamd_snprintf (read_buf, sizeof (read_buf), "XCLIENT NAME=%s ADDR=%s" CRLF,
+ session->hostname, inet_ntoa (session->client_addr));
+ r = write (session->upstream_sock, read_buf, r);
+
+ if (r < 0 && errno == EAGAIN) {
+ /* Add write event */
+ event_del (&session->upstream_ev);
+ event_set (&session->upstream_ev, session->upstream_sock,
+ EV_WRITE, smtp_proxy_greeting_handler, session);
+ event_base_set (session->ev_base, &session->upstream_ev);
+ event_add (&session->upstream_ev, NULL);
+ }
+ else if (r > 0) {
+ session->upstream_greeting->len = 0;
+ session->state = SMTP_PROXY_STATE_XCLIENT;
+ }
+ else {
+ msg_info ("connection with %s got write error: %s", inet_ntoa (session->client_addr), strerror (errno));
+ destroy_session (session->s);
+ }
+ }
+ else {
+ event_del (&session->upstream_ev);
+ /* Start direct proxy */
+ r = write (session->sock, session->upstream_greeting->str, session->upstream_greeting->len);
+ /* TODO: handle client's error here */
+ if (r > 0) {
+ session->proxy = rspamd_create_proxy (session->sock, session->upstream_sock, session->pool,
+ session->ev_base, session->ctx->proxy_buf_len,
+ &session->ctx->smtp_timeout, smtp_proxy_err_proxy, session);
+ session->state = SMTP_PROXY_STATE_PROXY;
+ }
+ else {
+ msg_info ("connection with %s got write error: %s", inet_ntoa (session->client_addr), strerror (errno));
+ destroy_session (session->s);
+ }
+ }
+ }
+ else if (r == -1) {
+ /* Proxy sent 500 error */
+ msg_info ("connection with %s got smtp error for greeting", session->upstream->name);
+ destroy_session (session->s);
+ }
+ }
+ else {
+ msg_info ("connection with %s got read error: %s", session->upstream->name, strerror (errno));
+ destroy_session (session->s);
+ }
+ }
+ else if (session->state == SMTP_PROXY_STATE_XCLIENT) {
+ /* Fill greeting buffer with new portion of data */
+ r = read (fd, read_buf, sizeof (read_buf) - 1);
+ if (r > 0) {
+ g_string_append_len (session->upstream_greeting, read_buf, r);
+ /* Now search line with 220 */
+ r = check_valid_smtp_greeting (session->upstream_greeting);
+ if (r == 1) {
+ event_del (&session->upstream_ev);
+ /* Start direct proxy */
+ r = write (session->sock, session->upstream_greeting->str, session->upstream_greeting->len);
+ /* TODO: handle client's error here */
+ if (r > 0) {
+ session->proxy = rspamd_create_proxy (session->sock, session->upstream_sock, session->pool,
+ session->ev_base, session->ctx->proxy_buf_len,
+ &session->ctx->smtp_timeout, smtp_proxy_err_proxy, session);
+ session->state = SMTP_PROXY_STATE_PROXY;
+ }
+ else {
+ msg_info ("connection with %s got write error: %s", inet_ntoa (session->client_addr), strerror (errno));
+ destroy_session (session->s);
+ }
+ }
+ else if (r == -1) {
+ /* Proxy sent 500 error */
+ msg_info ("connection with %s got smtp error for xclient", session->upstream->name);
+ destroy_session (session->s);
+ }
+ }
+ }
+ else {
+ msg_info ("connection with %s got read event at improper state: %d", session->upstream->name, session->state);
+ destroy_session (session->s);
+ }
+ }
+ else if (what == EV_WRITE) {
+ if (session->state == SMTP_PROXY_STATE_GREETING) {
+ /* Send xclient again */
+ r = rspamd_snprintf (read_buf, sizeof (read_buf), "XCLIENT NAME=%s ADDR=%s" CRLF,
+ session->hostname, inet_ntoa (session->client_addr));
+ r = write (session->upstream_sock, read_buf, r);
+
+ if (r < 0 && errno == EAGAIN) {
+ /* Add write event */
+ event_del (&session->upstream_ev);
+ event_set (&session->upstream_ev, session->upstream_sock,
+ EV_WRITE, smtp_proxy_greeting_handler, session);
+ event_base_set (session->ev_base, &session->upstream_ev);
+ event_add (&session->upstream_ev, NULL);
+ }
+ else if (r > 0) {
+ session->upstream_greeting->len = 0;
+ session->state = SMTP_PROXY_STATE_XCLIENT;
+ event_del (&session->upstream_ev);
+ event_set (&session->upstream_ev, session->upstream_sock,
+ EV_READ | EV_PERSIST, smtp_proxy_greeting_handler, session);
+ event_base_set (session->ev_base, &session->upstream_ev);
+ event_add (&session->upstream_ev, NULL);
+ }
+ else {
+ msg_info ("connection with %s got write error: %s", session->upstream->name, strerror (errno));
+ destroy_session (session->s);
+ }
+ }
+ else {
+ msg_info ("connection with %s got write event at improper state: %d", session->upstream->name, session->state);
+ destroy_session (session->s);
+ }
+ }
+ else {
+ /* Timeout */
+ msg_info ("connection with %s timed out", session->upstream->name);
+ destroy_session (session->s);
+ }
+}
+
static gboolean
create_smtp_proxy_upstream_connection (struct smtp_proxy_session *session)
{
- struct smtp_upstream *selected;
- struct sockaddr_un *un;
+ struct smtp_upstream *selected;
+ struct sockaddr_un *un;
/* Try to select upstream */
selected = (struct smtp_upstream *)get_upstream_round_robin (session->ctx->upstreams,
@@ -237,9 +443,14 @@ create_smtp_proxy_upstream_connection (struct smtp_proxy_session *session)
}
/* Create a proxy for upstream connection */
rspamd_dispatcher_pause (session->dispatcher);
- session->proxy = rspamd_create_proxy (session->sock, session->upstream_sock, session->pool,
- session->ev_base, session->ctx->proxy_buf_len,
- &session->ctx->smtp_timeout, smtp_proxy_err_proxy, session);
+ /* First of all get upstream's greeting */
+ session->state = SMTP_PROXY_STATE_GREETING;
+
+ event_set (&session->upstream_ev, session->upstream_sock, EV_READ | EV_PERSIST, smtp_proxy_greeting_handler, session);
+ event_base_set (session->ev_base, &session->upstream_ev);
+ event_add (&session->upstream_ev, &session->ctx->smtp_timeout);
+
+ session->upstream_greeting = g_string_sized_new (BUFSIZ);
return TRUE;
}
@@ -251,7 +462,7 @@ create_smtp_proxy_upstream_connection (struct smtp_proxy_session *session)
static void
smtp_delay_handler (gint fd, short what, void *arg)
{
- struct smtp_proxy_session *session = arg;
+ struct smtp_proxy_session *session = arg;
remove_normal_event (session->s, (event_finalizer_t) event_del,
session->delay_timer);
@@ -275,9 +486,9 @@ smtp_delay_handler (gint fd, short what, void *arg)
static void
smtp_make_delay (struct smtp_proxy_session *session)
{
- struct event *tev;
- struct timeval *tv;
- gint32 jitter;
+ struct event *tev;
+ struct timeval *tv;
+ gint32 jitter;
if (session->ctx->smtp_delay != 0 && session->state == SMTP_PROXY_STATE_DELAY) {
tev = memory_pool_alloc (session->pool, sizeof(struct event));
@@ -308,10 +519,10 @@ smtp_make_delay (struct smtp_proxy_session *session)
static void
smtp_dns_cb (struct rspamd_dns_reply *reply, void *arg)
{
- struct smtp_proxy_session *session = arg;
- gint res = 0;
- union rspamd_reply_element *elt;
- GList *cur;
+ struct smtp_proxy_session *session = arg;
+ gint res = 0;
+ union rspamd_reply_element *elt;
+ GList *cur;
switch (session->state)
{
@@ -470,6 +681,7 @@ accept_socket (gint fd, short what, void *arg)
session->ctx = ctx;
session->resolver = ctx->resolver;
session->ev_base = ctx->ev_base;
+ session->upstream_sock = -1;
worker->srv->stat->connections_count++;
/* Resolve client's addr */