[Exim] TURN support

Top Page
Delete this message
Reply to this message
Author: Arne Georg Gleditsch
Date:  
To: exim-users
Subject: [Exim] TURN support
--
Hi,

Due to requirements beyond my control at a site where I'd like to
install Exim I've taken a shot at implementing TURN support in Exim
4.20. I know TURN is by many not considered part of the good SMTP
command family, but since we have SMTP AUTH these days I feel that the
insecurity inherent in TURN is less of an issue than it used to be.

(Yes, I know there's ATRN now. If my patch is acceptable, I suppose
ATRN could be implemented fairly quickly on top of that. Still, ATRN
is mostly just a subset of TURN, functionality-wise, and I'd like the
longest rope in the shop, please. :) )

Our Exchange 2k in the lab seems to be relatively happy with Exim's
behaviour under this patch, and that's my main concern at the moment.

The patch should not affect anyone not setting up the appropriate
acl-s, and should thus be pretty low impact.

I run this with the following configuration bits:

  acl_smtp_turn = check_turn
  turn_domains = ${tr{${lookup ldapm {ldap:///LDAPBASE?domain?one\
       ?(turnHost=${quote_ldap:$sender_host_address})}}}{\n}{:}}


  hold_domains = ldap;ldap::///LDAPBASE?domain?one\
       ?(&(turnHost=*)(domain=${quote_ldap:$domain}))


  [..]
  begin acl
  [..]
  check_turn:
      accept  authenticated = *
              condition = ${lookup ldap {ldap:///domain=${quote_ldap_dn:$authenticated_id},LDAPBASE\
                                       ?domain?base?turnHost=${quote_ldap:$sender_host_address}}{yes}{no}}
      deny    message = turn not permitted


  [..]
  begin routers
  [..]
  turnhosts:
       driver = manualroute
       transport = remote_smtp
       route_data = ${lookup ldap {ldap:///domain=${quote_ldap_dn:$domain},LDAPBASE\
                       ?turnHost?base?}}


As you can see, I have a domain -> ip mapping in LDAP indicating which
domains will run from the queue whenever a given IP successfully
turns. There's also an authenticator of course, but it's a pretty
straightforward one.


                            Arne.
--
#! /bin/sh -e
## 60_turn.dpatch by Arne Georg Gleditsch
##
## All lines beginning with `## DP:' are a description of the patch.
## DP:  Implement SMTP TURN.



if [ $# -ne 1 ]; then
    echo >&2 "`basename $0`: script expects -patch|-unpatch as argument"
    exit 1
fi
case "$1" in
    -patch) patch -f --no-backup-if-mismatch -p1 < $0;;
    -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;;
    *)
        echo >&2 "`basename $0`: script expects -patch|-unpatch as argument"
        exit 1;;
esac


exit 0

@DPATCH@
diff -ru exim-4.20-upstream-orig/src/acl.c exim-4.20-upstream-turn/src/acl.c
--- exim-4.20-upstream-orig/src/acl.c    Mon May 12 15:39:17 2003
+++ exim-4.20-upstream-turn/src/acl.c    Mon Jul 21 14:24:25 2003
@@ -106,6 +106,7 @@
   (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* control */
     (1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_TURN)|
     (1<<ACL_WHERE_STARTTLS)|(ACL_WHERE_VRFY),


   0,                                               /* delay */
@@ -115,6 +116,7 @@
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_DATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_TURN)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),


@@ -127,6 +129,7 @@
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_DATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_TURN)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),


@@ -137,17 +140,20 @@
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_DATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_TURN)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),


   (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* sender_domains */
     (1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_TURN)|
     (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),


   (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* senders */
     (1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_TURN)|
     (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),


   0,                                               /* set */
diff -ru exim-4.20-upstream-orig/src/globals.c exim-4.20-upstream-turn/src/globals.c
--- exim-4.20-upstream-orig/src/globals.c    Mon May 12 15:39:19 2003
+++ exim-4.20-upstream-turn/src/globals.c    Wed Jul 23 10:41:53 2003
@@ -104,6 +104,8 @@
 #endif



+uschar *turn_domains           = NULL;
+
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. The defaults use stdin. We never need these for any
 stand-alone tests. */
@@ -150,6 +152,7 @@
 uschar *acl_smtp_connect       = NULL;
 uschar *acl_smtp_data          = NULL;
 uschar *acl_smtp_etrn          = NULL;
+uschar *acl_smtp_turn          = NULL;
 uschar *acl_smtp_expn          = NULL;
 uschar *acl_smtp_helo          = NULL;
 uschar *acl_smtp_mail          = NULL;
@@ -175,6 +178,7 @@
                                    US"RCPT",
                                    US"STARTTLS",
                                    US"VRFY",
+                                   US"TURN",
                                    US"non-SMTP" };


 int     acl_wherecodes[]       = { 503,     /* AUTH */
@@ -187,6 +191,7 @@
                                    550,     /* RCPT */
                                    550,     /* STARTTLS */
                                    252,     /* VRFY */
+                                   502,     /* TURN */
                                    0 };     /* not SMTP; not relevant */


 BOOL    accept_8bitmime        = FALSE;
@@ -326,6 +331,9 @@
 BOOL    continue_more          = FALSE;
 int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;
+BOOL    continue_but_turn      = FALSE;
+int     continue_turn_fd       = -1;
+uschar *continue_turn_address  = NULL;


 BOOL    daemon_listen          = FALSE;
 BOOL    daemon_write_pid       = TRUE_UNSET;
@@ -551,7 +559,8 @@
   { US"smtp_syntax_error",            L_smtp_syntax_error },
   { US"subject",                      L_subject },
   { US"tls_cipher",                   L_tls_cipher },
-  { US"tls_peerdn",                   L_tls_peerdn }
+  { US"tls_peerdn",                   L_tls_peerdn },
+  { US"turn",                         L_turn }
 };


 int     log_options_count      = sizeof(log_options)/sizeof(bit_table);
diff -ru exim-4.20-upstream-orig/src/globals.h exim-4.20-upstream-turn/src/globals.h
--- exim-4.20-upstream-orig/src/globals.h    Mon May 12 15:39:19 2003
+++ exim-4.20-upstream-turn/src/globals.h    Wed Jul 23 10:42:03 2003
@@ -68,6 +68,8 @@
 #endif



+extern uschar *turn_domains;        /* Domains fetched by TURN */
+
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. */


@@ -92,6 +94,7 @@
 extern uschar *acl_smtp_connect;       /* ACL run on SMTP connection */
 extern uschar *acl_smtp_data;          /* ACL run after DATA */
 extern uschar *acl_smtp_etrn;          /* ACL run after ETRN */
+extern uschar *acl_smtp_turn;          /* ACL run after TURN */
 extern uschar *acl_smtp_expn;          /* ACL run after EXPN */
 extern uschar *acl_smtp_helo;          /* ACL run after HELO/EHLO */
 extern uschar *acl_smtp_mail;          /* ACL run after MAIL */
@@ -167,6 +170,9 @@
 extern BOOL    continue_more;          /* Flag more addresses waiting */
 extern int     continue_sequence;      /* Sequence num for continued delivery */
 extern uschar *continue_transport;     /* Transport for continued delivery */
+extern BOOL    continue_but_turn; /* Use continue_hostname, but renegotiate HELO */
+extern int     continue_turn_fd;
+extern uschar *continue_turn_address;


 extern BOOL    daemon_listen;          /* True if listening required */
 extern BOOL    daemon_write_pid;       /* True if a pid file is to be written */
diff -ru exim-4.20-upstream-orig/src/macros.h exim-4.20-upstream-turn/src/macros.h
--- exim-4.20-upstream-orig/src/macros.h    Mon May 12 15:39:20 2003
+++ exim-4.20-upstream-turn/src/macros.h    Mon Jul 21 12:03:02 2003
@@ -349,6 +349,7 @@
 #define L_subject                    0x01000000
 #define L_tls_cipher                 0x02000000
 #define L_tls_peerdn                 0x04000000
+#define L_turn                       0x08000000


 #define L_all                        0xffffffff
 #define L_default                    (L_connection_reject        | \
@@ -362,7 +363,8 @@
                                       L_retry_defer              | \
                                       L_size_reject              | \
                                       L_skip_delivery            | \
-                                      L_tls_cipher)
+                                      L_tls_cipher         | \
+                                      L_turn)


/* Private error numbers for delivery failures, set negative so as not
to conflict with system errno values. */
@@ -676,7 +678,7 @@

 enum { ACL_WHERE_AUTH,     ACL_WHERE_CONNECT, ACL_WHERE_DATA, ACL_WHERE_ETRN,
        ACL_WHERE_EXPN,     ACL_WHERE_HELO,    ACL_WHERE_MAIL, ACL_WHERE_RCPT,
-       ACL_WHERE_STARTTLS, ACL_WHERE_VRFY,    ACL_WHERE_NOTSMTP };
+       ACL_WHERE_STARTTLS, ACL_WHERE_VRFY,    ACL_WHERE_TURN, ACL_WHERE_NOTSMTP };


/* Situations for spool_write_header() */

diff -ru exim-4.20-upstream-orig/src/queue.c exim-4.20-upstream-turn/src/queue.c
--- exim-4.20-upstream-orig/src/queue.c    Mon May 12 15:39:21 2003
+++ exim-4.20-upstream-turn/src/queue.c    Mon Jul 21 12:12:11 2003
@@ -604,7 +604,11 @@
     set_process_info("running queue: waiting for children of %d", pid);
     (void)read(pfd[pipe_read], buffer, sizeof(buffer));
     (void)close(pfd[pipe_read]);
-    set_process_info("running queue");
+    if (continue_but_turn == TRUE && queue_2stage == FALSE)
+      f = NULL;
+    else
+      set_process_info("running queue");
+
     }                                  /* End loop for list of messages */


   store_reset(reset_point1);           /* Scavenge list of messages */
diff -ru exim-4.20-upstream-orig/src/readconf.c exim-4.20-upstream-turn/src/readconf.c
--- exim-4.20-upstream-orig/src/readconf.c    Mon May 12 15:39:21 2003
+++ exim-4.20-upstream-turn/src/readconf.c    Mon Jul 21 12:03:02 2003
@@ -145,6 +145,7 @@
 #ifdef SUPPORT_TLS
   { "acl_smtp_starttls",        opt_stringptr,   &acl_smtp_starttls },
 #endif
+  { "acl_smtp_turn",            opt_stringptr,   &acl_smtp_turn },
   { "acl_smtp_vrfy",            opt_stringptr,   &acl_smtp_vrfy },
   { "admin_groups",             opt_gidlist,     &admin_groups },
   { "allow_domain_literals",    opt_bool,        &allow_domain_literals },
@@ -324,6 +325,7 @@
 #endif
   { "trusted_groups",           opt_gidlist,     &trusted_groups },
   { "trusted_users",            opt_uidlist,     &trusted_users },
+  { "turn_domains",             opt_stringptr,   &turn_domains },
   { "unknown_login",            opt_stringptr,   &unknown_login },
   { "unknown_username",         opt_stringptr,   &unknown_username },
   { "untrusted_set_sender",     opt_stringptr,   &untrusted_set_sender },
diff -ru exim-4.20-upstream-orig/src/smtp_in.c exim-4.20-upstream-turn/src/smtp_in.c
--- exim-4.20-upstream-orig/src/smtp_in.c    Mon May 12 15:39:22 2003
+++ exim-4.20-upstream-turn/src/smtp_in.c    Wed Jul 23 10:41:14 2003
@@ -60,7 +60,7 @@


   HELO_CMD, EHLO_CMD, DATA_CMD, /* These are listed in the pipelining */
   VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */
-  ETRN_CMD,                     /* This by analogy with TURN from the RFC */
+  ETRN_CMD, TURN_CMD,           /* This by analogy with TURN from the RFC */
   STARTTLS_CMD,                 /* Required by the STARTTLS RFC */


   /* This is a dummy to identify the non-sync commands when pipelining */
@@ -146,6 +146,7 @@
   { "quit",       sizeof("quit")-1,       QUIT_CMD, FALSE, TRUE  },
   { "noop",       sizeof("noop")-1,       NOOP_CMD, TRUE,  FALSE },
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
+  { "turn",       sizeof("turn")-1,       TURN_CMD, TRUE,  FALSE },
   { "vrfy",       sizeof("vrfy")-1,       VRFY_CMD, TRUE,  FALSE },
   { "expn",       sizeof("expn")-1,       EXPN_CMD, TRUE,  FALSE },
   { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE }
@@ -949,13 +950,14 @@
     break;



-    /* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
+    /* The VRFY, EXPN, HELP, ETRN, TURN and NOOP commands are ignored. */


     case VRFY_CMD:
     case EXPN_CMD:
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
+    case TURN_CMD:
     bsmtp_transaction_linecount = receive_linecount;
     break;


@@ -2219,6 +2221,14 @@
         s = string_cat(s, &size, &ptr, US"250-ETRN\r\n", 10);
         }


+      /* Advertise TURN if there's an ACL checking whether a host is
+      permitted to issue it; a check is made when any host actually tries. */
+
+      if (acl_smtp_turn != NULL)
+        {
+        s = string_cat(s, &size, &ptr, US"250-TURN\r\n", 10);
+        }
+
       /* Advertise EXPN if there's an ACL checking whether a host is
       permitted to issue it; a check is made when any host actually tries. */


@@ -2944,6 +2954,7 @@
       Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
       Ustrcat(buffer, " NOOP QUIT RSET HELP");
       if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
+      if (acl_smtp_turn != NULL) Ustrcat(buffer, " TURN");
       if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
       if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
       smtp_printf("214%s\r\n", buffer);
@@ -3124,6 +3135,110 @@
     break;



+    case TURN_CMD:
+    if (sender_address != NULL)
+      {
+      synprot_error(L_smtp_protocol_error, 503, NULL,
+    US"TURN is not permitted inside a transaction");
+      break;
+      }
+
+    log_write(L_turn, LOG_MAIN, "TURN received from %s",
+      host_and_ident(FALSE));
+
+    rc = acl_check(ACL_WHERE_TURN, smtp_data, acl_smtp_turn, &user_msg,
+      &log_msg);
+    if (rc != OK)
+      {
+      done = smtp_handle_acl_fail(ACL_WHERE_TURN, rc, user_msg, log_msg);
+      break;
+      }
+
+    if (smtp_inptr < smtp_inend)
+      {
+      /* Outstanding input */
+      synprot_error(L_smtp_protocol_error, 503, NULL,
+        US"TURN not properly synchronized");
+      break;
+      }
+
+    smtp_printf("250 OK, You be the server.\r\n");
+    fflush(smtp_out);
+
+    #ifdef SUPPORT_TLS
+    /* Exchange drops out of TLS mode when roles are TURNed.  It is
+    not really obvious if this is correct or not, but for the sake of
+    interoperability we follow suit. */
+    tls_active = -1;
+    #endif
+
+    /* No way back.  If we fall out, pretend eof and drop connection */
+    done = 1;
+
+      {
+      smtp_inblock inblock;
+      char buf[4000];
+      char *domains;
+      inblock.buffer     = smtp_inbuffer;
+      inblock.buffersize = sizeof(smtp_inbuffer);
+      inblock.ptr        = smtp_inptr;
+      inblock.ptrend     = smtp_inend;
+      inblock.sock       = fileno(smtp_in);
+
+      domains = expand_string(turn_domains);
+      deliver_selectstring = (char *)malloc(strlen(domains) + 7);
+      strcpy(deliver_selectstring, ".*@(");
+      strcat(deliver_selectstring, domains);
+      strcat(deliver_selectstring, ")$");
+      while (strchr(deliver_selectstring, ':') != NULL)
+        strchr(deliver_selectstring, ':')[0] = '|';
+      deliver_selectstring_regex = TRUE;
+
+      continue_but_turn     = TRUE;
+      continue_turn_fd      = fileno(smtp_out);
+      continue_turn_address = string_copy_malloc(sender_host_address);
+      queue_2stage          = TRUE;
+      queue_run_force       = TRUE;
+      queue_run_in_order    = TRUE;
+
+      log_write(L_turn, LOG_MAIN, "TURN accepted, running queue for %s",
+        deliver_selectstring);
+
+      search_tidyup();
+      queue_run_pipe = -1;
+      queue_run(NULL, NULL, FALSE);
+
+      /* If any actual deliveries are made, smtp renegotiation and
+      shutdown will be handled by the transports invoked.  If the
+      queue run did not result in any deliveries, we'll do a pro forma
+      job of it here.  We check queue_run_pipe to determine which is
+      the case.  (This is somewhat magical, and perhaps it would be
+      better if queue_run itself could tell us this by returning an
+      appropriate value. */
+
+      if (queue_run_pipe < 0)
+        {
+    if (!smtp_read_response(&inblock, buf, sizeof(buf), '2', 50))
+          {
+      log_write(0, LOG_MAIN|LOG_REJECT,
+        "Bad SMTP greeting after TURN: \"%s\"", buf);
+      break;
+      }
+    smtp_printf("QUIT\r\n");
+    }
+
+      fflush(smtp_out);
+
+      if (!smtp_read_response(&inblock, buf, sizeof(buf), '2', 50))
+        {
+    log_write(0, LOG_MAIN|LOG_REJECT,
+      "QUIT in TURNed state not accepted: \"%s\"", buf);
+    break;
+      }
+    }
+    break;
+
+
     case BADARG_CMD:
     synprot_error(L_smtp_syntax_error, 501, NULL, US"unexpected argument data");
     break;
diff -ru exim-4.20-upstream-orig/src/transports/smtp.c exim-4.20-upstream-turn/src/transports/smtp.c
--- exim-4.20-upstream-orig/src/transports/smtp.c    Mon May 12 15:39:23 2003
+++ exim-4.20-upstream-turn/src/transports/smtp.c    Wed Jul 23 10:43:09 2003
@@ -781,9 +781,20 @@


 if (continue_hostname == NULL)
   {
-  inblock.sock = outblock.sock =
-    smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-      ob->keepalive);
+  if (continue_but_turn == FALSE ||
+      strcmp(continue_turn_address, host->address) != 0)
+    {
+    inblock.sock = outblock.sock =
+      smtp_connect(host, host_af, port, interface, ob->connect_timeout,
+    ob->keepalive);
+    }
+  else
+    {
+    /* The tls_active logic requires these to be identical. */
+    inblock.sock  = continue_turn_fd;
+    outblock.sock = continue_turn_fd;
+    }
+
   if (inblock.sock < 0)
     {
     set_errno(addrlist, errno, NULL, DEFER);
--