diff options
Diffstat (limited to 'src/smtp_proxy.c')
-rw-r--r-- | src/smtp_proxy.c | 240 |
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 */ |