Revert introduction of alloc_insecure_tainted_data
[exim.git] / src / src / acl.c
index ce8d2189f2331a39d2b376d40b9bdc6697cf669d..46615ce24c8671eac6896286b95833bc6a2984fe 100644 (file)
@@ -3,13 +3,14 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
 
 #include "exim.h"
 
+#ifndef MACRO_PREDEF
 
 /* Default callout timeout */
 
@@ -53,6 +54,8 @@ static int msgcond[] = {
   [ACL_WARN] =         BIT(OK)
   };
 
+#endif
+
 /* ACL condition and modifier codes - keep in step with the table that
 follows.
 down. */
@@ -103,6 +106,7 @@ enum { ACLC_ACL,
        ACLC_REGEX,
 #endif
        ACLC_REMOVE_HEADER,
+       ACLC_SEEN,
        ACLC_SENDER_DOMAINS,
        ACLC_SENDERS,
        ACLC_SET,
@@ -288,6 +292,7 @@ static condition_def conditions[] = {
                                    ACL_BIT_MIME | ACL_BIT_NOTSMTP |
                                    ACL_BIT_NOTSMTP_START),
   },
+  [ACLC_SEEN] =                        { US"seen",             TRUE, FALSE,    0 },
   [ACLC_SENDER_DOMAINS] =      { US"sender_domains",   FALSE, FALSE,
                                  ACL_BIT_AUTH | ACL_BIT_CONNECT |
                                    ACL_BIT_HELO |
@@ -338,6 +343,24 @@ static condition_def conditions[] = {
 };
 
 
+#ifdef MACRO_PREDEF
+# include "macro_predef.h"
+void
+features_acl(void)
+{
+for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
+  {
+  uschar buf[64], * p, * s;
+  int n = sprintf(CS buf, "_ACL_%s_", c->is_modifier ? "MOD" : "COND");
+  for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s);
+  *p = '\0';
+  builtin_macro_create(buf);
+  }
+}
+#endif
+
+
+#ifndef MACRO_PREDEF
 
 /* Return values from decode_control(); used as index so keep in step
 with the controls_list table that follows! */
@@ -486,7 +509,7 @@ static control_def controls_list[] = {
   { US"no_delay_flush",          FALSE,
          ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
-  
+
 [CONTROL_NO_ENFORCE_SYNC] =
   { US"no_enforce_sync",         FALSE,
          ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
@@ -744,7 +767,7 @@ while ((s = (*func)()))
   int v, c;
   BOOL negated = FALSE;
   uschar *saveline = s;
-  uschar name[64];
+  uschar name[EXIM_DRIVERNAME_MAX];
 
   /* Conditions (but not verbs) are allowed to be negated by an initial
   exclamation mark. */
@@ -1188,7 +1211,7 @@ int rc;
 
 /* Previous success */
 
-if (sender_host_name != NULL) return OK;
+if (sender_host_name) return OK;
 
 /* Previous failure */
 
@@ -2815,6 +2838,143 @@ return rc;
 
 
 
+/*************************************************
+*      Handle a check for previously-seen        *
+*************************************************/
+
+/*
+ACL clauses like:   seen = -5m / key=$foo / readonly
+
+Return is true for condition-true - but the semantics
+depend heavily on the actual use-case.
+
+Negative times test for seen-before, positive for seen-more-recently-than
+(the given interval before current time).
+
+All are subject to history not having been cleaned from the DB.
+
+Default for seen-before is to create if not present, and to
+update if older than 10d (with the seen-test time).
+Default for seen-since is to always create or update.
+
+Options:
+  key=value.  Default key is $sender_host_address
+  readonly
+  write
+  refresh=<interval>:  update an existing DB entry older than given
+                       amount.  Default refresh lacking this option is 10d.
+                       The update sets the record timestamp to the seen-test time.
+
+XXX do we need separate nocreate, noupdate controls?
+
+Arguments:
+  arg         the option string for seen=
+  where       ACL_WHERE_xxxx indicating which ACL this is
+  log_msgptr  for error messages
+
+Returns:       OK        - Condition is true
+               FAIL      - Condition is false
+               DEFER     - Problem opening history database
+               ERROR     - Syntax error in options
+*/
+
+static int
+acl_seen(const uschar * arg, int where, uschar ** log_msgptr)
+{
+enum { SEEN_DEFAULT, SEEN_READONLY, SEEN_WRITE };
+
+const uschar * list = arg;
+int slash = '/', interval, mode = SEEN_DEFAULT, yield = FAIL;
+BOOL before;
+int refresh = 10 * 24 * 60 * 60;       /* 10 days */
+const uschar * ele, * key = sender_host_address;
+open_db dbblock, * dbm;
+dbdata_seen * dbd;
+time_t now;
+
+/* Parse the first element, the time-relation. */
+
+if (!(ele = string_nextinlist(&list, &slash, NULL, 0)))
+  goto badparse;
+if ((before = *ele == '-'))
+  ele++;
+if ((interval = readconf_readtime(ele, 0, FALSE)) < 0)
+  goto badparse;
+
+/* Remaining elements are options */
+
+while ((ele = string_nextinlist(&list, &slash, NULL, 0)))
+  if (Ustrncmp(ele, "key=", 4) == 0)
+    key = ele + 4;
+  else if (Ustrcmp(ele, "readonly") == 0)
+    mode = SEEN_READONLY;
+  else if (Ustrcmp(ele, "write") == 0)
+    mode = SEEN_WRITE;
+  else if (Ustrncmp(ele, "refresh=", 8) == 0)
+    {
+    if ((refresh = readconf_readtime(ele + 8, 0, FALSE)) < 0)
+      goto badparse;
+    }
+  else
+    goto badopt;
+
+if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE)))
+  {
+  HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n");
+  *log_msgptr = US"database for 'seen' not available";
+  return DEFER;
+  }
+
+dbd = dbfn_read_with_length(dbm, key, NULL);
+now = time(NULL);
+if (dbd)               /* an existing record */
+  {
+  time_t diff = now - dbd->time_stamp; /* time since the record was written */
+
+  if (before ? diff >= interval : diff < interval)
+    yield = OK;
+
+  if (mode == SEEN_READONLY)
+    { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); }
+  else if (mode == SEEN_WRITE || !before)
+    {
+    dbd->time_stamp = now;
+    dbfn_write(dbm, key, dbd, sizeof(*dbd));
+    HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n");
+    }
+  else if (diff >= refresh)
+    {
+    dbd->time_stamp = now - interval;
+    dbfn_write(dbm, key, dbd, sizeof(*dbd));
+    HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n");
+    }
+  }
+else
+  {                    /* No record found, yield always FAIL */
+  if (mode != SEEN_READONLY)
+    {
+    dbdata_seen d = {.time_stamp = now};
+    dbfn_write(dbm, key, &d, sizeof(*dbd));
+    HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n");
+    }
+  else
+    HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n");
+  }
+
+dbfn_close(dbm);
+return yield;
+
+
+badparse:
+  *log_msgptr = string_sprintf("failed to parse '%s'", arg);
+  return ERROR;
+badopt:
+  *log_msgptr = string_sprintf("unrecognised option '%s' in '%s'", ele, arg);
+  return ERROR;
+}
+
+
+
 /*************************************************
 *            The udpsend ACL modifier            *
 *************************************************/
@@ -3235,13 +3395,15 @@ for (; cb; cb = cb->next)
 
        case CONTROL_FAKEREJECT:
          cancel_cutthrough_connection(TRUE, US"fakereject");
-         case CONTROL_FAKEDEFER:
+       case CONTROL_FAKEDEFER:
          fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
          if (*p == '/')
            {
            const uschar *pp = p + 1;
            while (*pp) pp++;
-           fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
+           /* The entire control= line was expanded at top so no need to expand
+           the part after the / */
+           fake_response_text = string_copyn(p+1, pp-p-1);
            p = pp;
            }
           else /* Explicitly reset to default string */
@@ -3319,7 +3481,7 @@ for (; cb; cb = cb->next)
          {
          uschar * debug_tag = NULL;
          uschar * debug_opts = NULL;
-         BOOL kill = FALSE;
+         BOOL kill = FALSE, stop = FALSE;
 
          while (*p == '/')
            {
@@ -3336,18 +3498,39 @@ for (; cb; cb = cb->next)
              }
            else if (Ustrncmp(pp, "kill", 4) == 0)
              {
-             for (pp += 4; *pp && *pp != '/';) pp++;
+             pp += 4;
              kill = TRUE;
              }
-           else
-             while (*pp && *pp != '/') pp++;
+           else if (Ustrncmp(pp, "stop", 4) == 0)
+             {
+             pp += 4;
+             stop = TRUE;
+             }
+           else if (Ustrncmp(pp, "pretrigger=", 11) == 0)
+               debug_pretrigger_setup(pp+11);
+           else if (Ustrncmp(pp, "trigger=", 8) == 0)
+             {
+             if (Ustrncmp(pp += 8, "now", 3) == 0)
+               {
+               pp += 3;
+               debug_trigger_fire();
+               }
+             else if (Ustrncmp(pp, "paniclog", 8) == 0)
+               {
+               pp += 8;
+               dtrigger_selector |= BIT(DTi_panictrigger);
+               }
+             }
+           while (*pp && *pp != '/') pp++;
            p = pp;
            }
 
-           if (kill)
-             debug_logging_stop();
-           else
-             debug_logging_activate(debug_tag, debug_opts);
+         if (kill)
+           debug_logging_stop(TRUE);
+         else if (stop)
+           debug_logging_stop(FALSE);
+         else if (debug_tag || debug_opts)
+           debug_logging_activate(debug_tag, debug_opts);
          break;
          }
 
@@ -3538,7 +3721,7 @@ for (; cb; cb = cb->next)
       }
     break;
 
-    #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
     if (dkim_cur_signer)
       rc = match_isinlist(dkim_cur_signer,
@@ -3551,7 +3734,7 @@ for (; cb; cb = cb->next)
     rc = match_isinlist(dkim_verify_status,
                         &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
     break;
-    #endif
+#endif
 
 #ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
@@ -3736,6 +3919,10 @@ for (; cb; cb = cb->next)
     setup_remove_header(arg);
     break;
 
+    case ACLC_SEEN:
+    rc = acl_seen(arg, where, log_msgptr);
+    break;
+
     case ACLC_SENDER_DOMAINS:
       {
       uschar *sdomain;
@@ -4088,16 +4275,6 @@ while (isspace(*ss)) ss++;
 
 acl_text = ss;
 
-#ifdef notyet_taintwarn
-if (  !f.running_in_test_harness
-   &&  is_tainted2(acl_text, LOG_MAIN|LOG_PANIC,
-                         "attempt to use tainted ACL text \"%s\"", acl_text))
-  {
-  /* Avoid leaking info to an attacker */
-  *log_msgptr = US"internal configuration error";
-  return ERROR;
-  }
-#else
 if (is_tainted(acl_text) && !f.running_in_test_harness)
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
@@ -4106,7 +4283,6 @@ if (is_tainted(acl_text) && !f.running_in_test_harness)
   *log_msgptr = US"internal configuration error";
   return ERROR;
   }
-#endi
 
 /* Handle the case of a string that does not contain any spaces. Look for a
 named ACL among those read from the configuration, or a previously read file.
@@ -4695,6 +4871,7 @@ if (is_tainted(value)) putc('-', f);
 fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 }
 
+#endif /* !MACRO_PREDEF */
 /* vi: aw ai sw=2
 */
 /* End of acl.c */