ACL modifier
[exim.git] / src / src / acl.c
index e79c87f83daa5e8ff6ae0588b0527961f36cab63..ec1505bf0023f685ae7c5f301fae005479493b2f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
@@ -65,9 +65,6 @@ enum { ACLC_ACL,
        ACLC_DECODE,
 #endif
        ACLC_DELAY,
-#ifdef WITH_OLD_DEMIME
-       ACLC_DEMIME,
-#endif
 #ifndef DISABLE_DKIM
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
@@ -91,6 +88,7 @@ enum { ACLC_ACL,
 #ifdef WITH_CONTENT_SCAN
        ACLC_MIME_REGEX,
 #endif
+       ACLC_QUEUE,
        ACLC_RATELIMIT,
        ACLC_RECIPIENTS,
 #ifdef WITH_CONTENT_SCAN
@@ -111,7 +109,7 @@ enum { ACLC_ACL,
        ACLC_VERIFY };
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
-"message", "log_message", "log_reject_target", "logwrite", and "set" are
+"message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
 modifiers that look like conditions but always return TRUE. They are used for
 their side effects. */
 
@@ -132,9 +130,6 @@ static uschar *conditions[] = {
   US"decode",
 #endif
   US"delay",
-#ifdef WITH_OLD_DEMIME
-  US"demime",
-#endif
 #ifndef DISABLE_DKIM
   US"dkim_signers",
   US"dkim_status",
@@ -158,13 +153,16 @@ static uschar *conditions[] = {
 #ifdef WITH_CONTENT_SCAN
   US"mime_regex",
 #endif
+  US"queue",
   US"ratelimit",
   US"recipients",
 #ifdef WITH_CONTENT_SCAN
   US"regex",
 #endif
   US"remove_header",
-  US"sender_domains", US"senders", US"set",
+  US"sender_domains",
+  US"senders",
+  US"set",
 #ifdef WITH_CONTENT_SCAN
   US"spam",
 #endif
@@ -208,7 +206,7 @@ enum {
 #endif
   CONTROL_FAKEDEFER,
   CONTROL_FAKEREJECT,
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   CONTROL_UTF8_DOWNCONVERT,
 #endif
   CONTROL_NO_MULTILINE,
@@ -251,7 +249,7 @@ static uschar *controls[] = {
 #endif
   US"fakedefer",
   US"fakereject",
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   US"utf8_downconvert",
 #endif
   US"no_multiline_responses",
@@ -281,9 +279,6 @@ static uschar cond_expand_at_top[] = {
   TRUE,    /* decode */
 #endif
   TRUE,    /* delay */
-#ifdef WITH_OLD_DEMIME
-  TRUE,    /* demime */
-#endif
 #ifndef DISABLE_DKIM
   TRUE,    /* dkim_signers */
   TRUE,    /* dkim_status */
@@ -307,6 +302,7 @@ static uschar cond_expand_at_top[] = {
 #ifdef WITH_CONTENT_SCAN
   TRUE,    /* mime_regex */
 #endif
+  TRUE,    /* queue */
   TRUE,    /* ratelimit */
   FALSE,   /* recipients */
 #ifdef WITH_CONTENT_SCAN
@@ -346,9 +342,6 @@ static uschar cond_modifiers[] = {
   FALSE,   /* decode */
 #endif
   TRUE,    /* delay */
-#ifdef WITH_OLD_DEMIME
-  FALSE,   /* demime */
-#endif
 #ifndef DISABLE_DKIM
   FALSE,   /* dkim_signers */
   FALSE,   /* dkim_status */
@@ -372,6 +365,7 @@ static uschar cond_modifiers[] = {
 #ifdef WITH_CONTENT_SCAN
   FALSE,   /* mime_regex */
 #endif
+  TRUE,    /* queue */
   FALSE,   /* ratelimit */
   FALSE,   /* recipients */
 #ifdef WITH_CONTENT_SCAN
@@ -453,15 +447,6 @@ static unsigned int cond_forbids[] = {
 
   (1<<ACL_WHERE_NOTQUIT),                          /* delay */
 
-  #ifdef WITH_OLD_DEMIME
-  (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|                           /* demime */
-  #ifndef DISABLE_PRDR
-    (1<<ACL_WHERE_PRDR)|
-  #endif
-    (1<<ACL_WHERE_NOTSMTP)),
-  #endif
-
   #ifndef DISABLE_DKIM
   (unsigned int)
   ~(1<<ACL_WHERE_DKIM),                            /* dkim_signers */
@@ -475,11 +460,14 @@ static unsigned int cond_forbids[] = {
   ~(1<<ACL_WHERE_DATA),                            /* dmarc_status */
   #endif
 
-  (1<<ACL_WHERE_NOTSMTP)|                          /* dnslists */
-    (1<<ACL_WHERE_NOTSMTP_START),
+  /* Explicit key lookups can be made in non-smtp ACLs so pass
+  always and check in the verify processing itself. */
+
+  0,                                              /* dnslists */
 
   (unsigned int)
   ~((1<<ACL_WHERE_RCPT)                            /* domains */
+    |(1<<ACL_WHERE_VRFY)
   #ifndef DISABLE_PRDR
     |(1<<ACL_WHERE_PRDR)
   #endif
@@ -497,6 +485,7 @@ static unsigned int cond_forbids[] = {
 
   (unsigned int)
   ~((1<<ACL_WHERE_RCPT)                             /* local_parts */
+    |(1<<ACL_WHERE_VRFY)
   #ifndef DISABLE_PRDR
     |(1<<ACL_WHERE_PRDR)
   #endif
@@ -524,6 +513,7 @@ static unsigned int cond_forbids[] = {
   ~(1<<ACL_WHERE_MIME),                            /* mime_regex */
   #endif
 
+  0,                                               /* queue */
   0,                                               /* ratelimit */
 
   (unsigned int)
@@ -693,7 +683,7 @@ static unsigned int control_forbids[] = {
 #endif
     (1<<ACL_WHERE_MIME)),
 
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   0,                                               /* utf8_downconvert */
 #endif
 
@@ -750,7 +740,7 @@ static control_def controls_list[] = {
   { US"submission",              CONTROL_SUBMISSION,            TRUE },
   { US"suppress_local_fixups",   CONTROL_SUPPRESS_LOCAL_FIXUPS, FALSE },
   { US"cutthrough_delivery",     CONTROL_CUTTHROUGH_DELIVERY,   FALSE },
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   { US"utf8_downconvert",        CONTROL_UTF8_DOWNCONVERT,      TRUE }
 #endif
   };
@@ -1072,9 +1062,9 @@ while (*hstring == '\n') hstring++, hlen--;
 
 /* An empty string does nothing; ensure exactly one final newline. */
 if (hlen <= 0) return;
-if (hstring[--hlen] != '\n')
+if (hstring[--hlen] != '\n')           /* no newline */
   q = string_sprintf("%s\n", hstring);
-else if (hstring[hlen-1] == '\n')
+else if (hstring[hlen-1] == '\n')      /* double newline */
   {
   uschar * s = string_copy(hstring);
   while(s[--hlen] == '\n')
@@ -1097,7 +1087,7 @@ for (p = q; *p != 0; )
 
   for (;;)
     {
-    q = Ustrchr(q, '\n');
+    q = Ustrchr(q, '\n');              /* we know there was a newline */
     if (*(++q) != ' ' && *q != '\t') break;
     }
 
@@ -1176,11 +1166,11 @@ uschar *
 fn_hdrs_added(void)
 {
 uschar * ret = NULL;
+int size = 0;
+int ptr = 0;
 header_line * h = acl_added_headers;
 uschar * s;
 uschar * cp;
-int size = 0;
-int ptr = 0;
 
 if (!h) return NULL;
 
@@ -1192,13 +1182,13 @@ do
     if (cp[1] == '\0') break;
 
     /* contains embedded newline; needs doubling */
-    ret = string_cat(ret, &size, &ptr, s, cp-s+1);
-    ret = string_cat(ret, &size, &ptr, US"\n", 1);
+    ret = string_catn(ret, &size, &ptr, s, cp-s+1);
+    ret = string_catn(ret, &size, &ptr, US"\n", 1);
     s = cp+1;
     }
   /* last bit of header */
 
-  ret = string_cat(ret, &size, &ptr, s, cp-s+1);       /* newline-sep list */
+  ret = string_catn(ret, &size, &ptr, s, cp-s+1);      /* newline-sep list */
   }
 while((h = h->next));
 
@@ -1803,27 +1793,27 @@ switch(vp->value)
     test whether it was successful or not. (This is for optional verification; for
     mandatory verification, the connection doesn't last this long.) */
 
-      if (tls_in.certificate_verified) return OK;
-      *user_msgptr = US"no verified certificate";
-      return FAIL;
+    if (tls_in.certificate_verified) return OK;
+    *user_msgptr = US"no verified certificate";
+    return FAIL;
 
   case VERIFY_HELO:
     /* We can test the result of optional HELO verification that might have
     occurred earlier. If not, we can attempt the verification now. */
 
-      if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
-      return helo_verified? OK : FAIL;
+    if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
+    return helo_verified? OK : FAIL;
 
   case VERIFY_CSA:
     /* Do Client SMTP Authorization checks in a separate function, and turn the
     result code into user-friendly strings. */
 
-      rc = acl_verify_csa(list);
-      *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
+    rc = acl_verify_csa(list);
+    *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
                                               csa_reason_string[rc]);
-      csa_status = csa_status_string[rc];
-      DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status);
-      return csa_return_code[rc];
+    csa_status = csa_status_string[rc];
+    DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status);
+    return csa_return_code[rc];
 
   case VERIFY_HDR_SYNTAX:
     /* Check that all relevant header lines have the correct syntax. If there is
@@ -1832,8 +1822,11 @@ switch(vp->value)
     always). */
 
     rc = verify_check_headers(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
-      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+    if (rc != OK && *log_msgptr)
+      if (smtp_return_error_details)
+       *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+      else
+       acl_verify_message = *log_msgptr;
     return rc;
 
   case VERIFY_HDR_NAMES_ASCII:
@@ -2092,7 +2085,7 @@ else if (verify_sender_address != NULL)
     uschar *save_address_data = deliver_address_data;
 
     sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
     if ((sender_vaddr->prop.utf8_msg = message_smtputf8))
       {
       sender_vaddr->prop.utf8_downcvt =       message_utf8_downconvert == 1;
@@ -2369,17 +2362,13 @@ rate measurement as opposed to rate limiting. */
 
 sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0);
 if (sender_rate_limit == NULL)
-  {
-  limit = -1.0;
-  ss = NULL;   /* compiler quietening */
-  }
-else
-  {
-  limit = Ustrtod(sender_rate_limit, &ss);
-  if (tolower(*ss) == 'k') { limit *= 1024.0; ss++; }
-  else if (tolower(*ss) == 'm') { limit *= 1024.0*1024.0; ss++; }
-  else if (tolower(*ss) == 'g') { limit *= 1024.0*1024.0*1024.0; ss++; }
-  }
+  return ratelimit_error(log_msgptr, "sender rate limit not set");
+
+limit = Ustrtod(sender_rate_limit, &ss);
+if      (tolower(*ss) == 'k') { limit *= 1024.0; ss++; }
+else if (tolower(*ss) == 'm') { limit *= 1024.0*1024.0; ss++; }
+else if (tolower(*ss) == 'g') { limit *= 1024.0*1024.0*1024.0; ss++; }
+
 if (limit < 0.0 || *ss != '\0')
   return ratelimit_error(log_msgptr,
     "\"%s\" is not a positive number", sender_rate_limit);
@@ -3372,7 +3361,11 @@ for (; cb != NULL; cb = cb->next)
        break;
 
        case CONTROL_CUTTHROUGH_DELIVERY:
+#ifndef DISABLE_PRDR
        if (prdr_requested)
+#else
+       if (0)
+#endif
          /* Too hard to think about for now.  We might in future cutthrough
          the case where both sides handle prdr and this-node prdr acl
          is "accept" */
@@ -3395,7 +3388,7 @@ for (; cb != NULL; cb = cb->next)
          }
        return ERROR;
 
-    #ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
        case CONTROL_UTF8_DOWNCONVERT:
        if (*p == '/')
          {
@@ -3433,7 +3426,7 @@ for (; cb != NULL; cb = cb->next)
          break;
          }
        return ERROR;
-    #endif
+#endif
 
        }
       break;
@@ -3480,6 +3473,34 @@ for (; cb != NULL; cb = cb->next)
             debug_printf("delay skipped in -bh checking mode\n");
           }
 
+       /* NOTE 1: Remember that we may be
+        dealing with stdin/stdout here, in addition to TCP/IP connections.
+        Also, delays may be specified for non-SMTP input, where smtp_out and
+        smtp_in will be NULL. Whatever is done must work in all cases.
+
+        NOTE 2: The added feature of flushing the output before a delay must
+        apply only to SMTP input. Hence the test for smtp_out being non-NULL.
+        */
+
+        else
+          {
+          if (smtp_out != NULL && !disable_delay_flush)
+           mac_smtp_fflush();
+
+#if !defined(NO_POLL_H) && defined (POLLRDHUP)
+           {
+           struct pollfd p;
+           nfds_t n = 0;
+           if (smtp_out)
+             {
+             p.fd = fileno(smtp_out);
+             p.events = POLLRDHUP;
+             n = 1;
+             }
+           if (poll(&p, n, delay*1000) > 0)
+             HDEBUG(D_acl) debug_printf("delay cancelled by peer close\n");
+           }
+#else
         /* It appears to be impossible to detect that a TCP/IP connection has
         gone away without reading from it. This means that we cannot shorten
         the delay below if the client goes away, because we cannot discover
@@ -3489,38 +3510,22 @@ for (; cb != NULL; cb = cb->next)
         Exim process is not held up unnecessarily. However, it seems that we
         can't. The poll() function does not do the right thing, and in any case
         it is not always available.
-
-        NOTE 1: If ever this state of affairs changes, remember that we may be
-        dealing with stdin/stdout here, in addition to TCP/IP connections.
-        Also, delays may be specified for non-SMTP input, where smtp_out and
-        smtp_in will be NULL. Whatever is done must work in all cases.
-
-        NOTE 2: The added feature of flushing the output before a delay must
-        apply only to SMTP input. Hence the test for smtp_out being non-NULL.
         */
 
-        else
-          {
-          if (smtp_out != NULL && !disable_delay_flush) mac_smtp_fflush();
           while (delay > 0) delay = sleep(delay);
+#endif
           }
         }
       }
     break;
 
-    #ifdef WITH_OLD_DEMIME
-    case ACLC_DEMIME:
-      rc = demime(&arg);
-    break;
-    #endif
-
     #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
     if (dkim_cur_signer != NULL)
       rc = match_isinlist(dkim_cur_signer,
                           &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     else
-       rc = FAIL;
+      rc = FAIL;
     break;
 
     case ACLC_DKIM_STATUS:
@@ -3542,7 +3547,7 @@ for (; cb != NULL; cb = cb->next)
     #endif
 
     case ACLC_DNSLISTS:
-    rc = verify_check_dnsbl(&arg);
+    rc = verify_check_dnsbl(where, &arg, log_msgptr);
     break;
 
     case ACLC_DOMAINS:
@@ -3679,6 +3684,10 @@ for (; cb != NULL; cb = cb->next)
     break;
     #endif
 
+    case ACLC_QUEUE:
+    queue_name = string_copy_malloc(arg);
+    break;
+
     case ACLC_RATELIMIT:
     rc = acl_ratelimit(arg, where, log_msgptr);
     break;
@@ -3718,7 +3727,12 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_SET:
       {
       int old_pool = store_pool;
-      if (cb->u.varname[0] == 'c') store_pool = POOL_PERM;
+      if (  cb->u.varname[0] == 'c'
+#ifndef DISABLE_EVENT
+        || event_name          /* An event is being delivered */
+#endif
+        )
+        store_pool = POOL_PERM;
       acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
       store_pool = old_pool;
       }
@@ -3766,7 +3780,8 @@ for (; cb != NULL; cb = cb->next)
 
     case ACLC_VERIFY:
     rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
-    acl_verify_message = *user_msgptr;
+    if (*user_msgptr)
+      acl_verify_message = *user_msgptr;
     if (verb == ACL_WARN) *user_msgptr = NULL;
     break;
 
@@ -4265,7 +4280,7 @@ while (acl != NULL)
     case ACL_WARN:
     if (cond == OK)
       acl_warn(where, *user_msgptr, *log_msgptr);
-    else if (cond == DEFER && (log_extra_selector & LX_acl_warn_skipped) != 0)
+    else if (cond == DEFER && LOGGING(acl_warn_skipped))
       log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
         "condition test deferred%s%s", host_and_ident(TRUE),
         (*log_msgptr == NULL)? US"" : US": ",
@@ -4421,9 +4436,9 @@ ratelimiters_cmd = NULL;
 log_reject_target = LOG_MAIN|LOG_REJECT;
 
 #ifndef DISABLE_PRDR
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR)
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY || where==ACL_WHERE_PRDR)
 #else
-if (where == ACL_WHERE_RCPT)
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY)
 #endif
   {
   adb = address_defaults;
@@ -4434,7 +4449,7 @@ if (where == ACL_WHERE_RCPT)
     *log_msgptr = US"defer in percent_hack_domains check";
     return DEFER;
     }
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   if ((addr->prop.utf8_msg = message_smtputf8))
     {
     addr->prop.utf8_downcvt =       message_utf8_downconvert == 1;
@@ -4454,8 +4469,8 @@ and WHERE_RCPT and not yet opened conn as result of recipient-verify,
 and rcpt acl returned accept,
 and first recipient (cancel on any subsequents)
 open one now and run it up to RCPT acceptance.
-A failed verify should cancel cutthrough request.
-
+A failed verify should cancel cutthrough request,
+and will pass the fail to the originator.
 Initial implementation:  dual-write to spool.
 Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection.
 
@@ -4474,12 +4489,14 @@ case ACL_WHERE_RCPT:
 #ifndef DISABLE_PRDR
 case ACL_WHERE_PRDR:
 #endif
-  if (rc == OK  &&  cutthrough.delivery  && rcpt_count > cutthrough.nrcpt)
-    open_cutthrough_connection(addr);
+  if (host_checking_callout)   /* -bhc mode */
+    cancel_cutthrough_connection("host-checking mode");
+  else if (rc == OK && cutthrough.delivery && rcpt_count > cutthrough.nrcpt)
+    rc = open_cutthrough_connection(addr);
   break;
 
 case ACL_WHERE_PREDATA:
-  if( rc == OK )
+  if (rc == OK)
     cutthrough_predata();
   else
     cancel_cutthrough_connection("predata acl not ok");