Release process: script for cleaning changebars from .xfpt files
[exim.git] / src / src / acl.c
index 8431efc84cb70a47c57871c72536c14bdbe0ebf6..0aa789dbf820ef9b5a8d7781e40daa5f92326838 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -57,9 +57,7 @@ static int msgcond[] = {
 
 #endif
 
-/* ACL condition and modifier codes - keep in step with the table that
-follows.
-down. */
+/* ACL condition and modifier codes */
 
 enum { ACLC_ACL,
        ACLC_ADD_HEADER,
@@ -119,7 +117,8 @@ enum { ACLC_ACL,
        ACLC_SPF_GUESS,
 #endif
        ACLC_UDPSEND,
-       ACLC_VERIFY };
+       ACLC_VERIFY,
+};
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
@@ -149,7 +148,7 @@ static condition_def conditions[] = {
   [ACLC_ACL] =                 { US"acl",              FALSE, FALSE,   0 },
 
   [ACLC_ADD_HEADER] =          { US"add_header",       TRUE, TRUE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
@@ -188,7 +187,7 @@ static condition_def conditions[] = {
 
 #ifdef EXPERIMENTAL_DCC
   [ACLC_DCC] =                 { US"dcc",              TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
@@ -204,7 +203,7 @@ static condition_def conditions[] = {
 #ifndef DISABLE_DKIM
   [ACLC_DKIM_SIGNER] =         { US"dkim_signers",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
   [ACLC_DKIM_STATUS] =         { US"dkim_status",      TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
 # ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
@@ -221,7 +220,7 @@ static condition_def conditions[] = {
   [ACLC_DNSLISTS] =            { US"dnslists", TRUE, FALSE,    0 },
 
   [ACLC_DOMAINS] =             { US"domains",  FALSE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  |ACL_BIT_PRDR
@@ -239,7 +238,7 @@ static condition_def conditions[] = {
                                  ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
   },
   [ACLC_LOCAL_PARTS] =         { US"local_parts",      FALSE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
@@ -253,7 +252,7 @@ static condition_def conditions[] = {
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_MALWARE] =             { US"malware",  TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                    ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
@@ -280,7 +279,7 @@ static condition_def conditions[] = {
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_REGEX] =               { US"regex",            TRUE, FALSE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
@@ -291,7 +290,7 @@ static condition_def conditions[] = {
 
 #endif
   [ACLC_REMOVE_HEADER] =       { US"remove_header",    TRUE, TRUE,
-                                 (unsigned int)
+                                 (unsigned)
                                  ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
@@ -320,7 +319,7 @@ static condition_def conditions[] = {
 
 #ifdef WITH_CONTENT_SCAN
   [ACLC_SPAM] =                        { US"spam",             TRUE, FALSE,
-                                 (unsigned int) ~(ACL_BIT_DATA |
+                                 (unsigned) ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
@@ -370,8 +369,7 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
 
 #ifndef MACRO_PREDEF
 
-/* Return values from decode_control(); used as index so keep in step
-with the controls_list table that follows! */
+/* Return values from decode_control() */
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
@@ -411,6 +409,9 @@ enum {
 #ifdef SUPPORT_I18N
   CONTROL_UTF8_DOWNCONVERT,
 #endif
+#ifndef DISABLE_WELLKNOWN
+  CONTROL_WELLKNOWN,
+#endif
 };
 
 
@@ -564,7 +565,12 @@ static control_def controls_list[] = {
 #ifdef SUPPORT_I18N
 [CONTROL_UTF8_DOWNCONVERT] =
   { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
-  }
+  },
+#endif
+#ifndef DISABLE_WELLKNOWN
+[CONTROL_WELLKNOWN] =
+  { US"wellknown",               TRUE, (unsigned) ~ACL_BIT_WELLKNOWN
+  },
 #endif
 };
 
@@ -800,16 +806,16 @@ return TRUE;
 
 static BOOL
 acl_data_to_cond(const uschar * s, acl_condition_block * cond,
-  const uschar * name, uschar ** error)
+  const uschar * name, BOOL taint, uschar ** error)
 {
 if (*s++ != '=')
   {
   *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
     conditions[cond->type].is_modifier ? US"modifier" : US"condition");
-  return FALSE;;
+  return FALSE;
   }
 Uskip_whitespace(&s);
-cond->arg = string_copy(s);
+cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
 return TRUE;
 }
 
@@ -959,7 +965,7 @@ while ((s = (*func)()))
   "endpass" has no data */
 
   if (c != ACLC_ENDPASS)
-    if (!acl_data_to_cond(s, cond, name, error)) return NULL;
+    if (!acl_data_to_cond(s, cond, name, FALSE, error)) return NULL;
   }
 
 return yield;
@@ -1146,9 +1152,9 @@ Returns:         nothing
 */
 
 static void
-acl_warn(int where, uschar *user_message, uschar *log_message)
+acl_warn(int where, uschar * user_message, uschar * log_message)
 {
-if (log_message != NULL && log_message != user_message)
+if (log_message && log_message != user_message)
   {
   uschar *text;
   string_item *logged;
@@ -1159,9 +1165,9 @@ if (log_message != NULL && log_message != user_message)
   /* If a sender verification has failed, and the log message is "sender verify
   failed", add the failure message. */
 
-  if (sender_verified_failed != NULL &&
-      sender_verified_failed->message != NULL &&
-      strcmpic(log_message, US"sender verify failed") == 0)
+  if (  sender_verified_failed
+     && sender_verified_failed->message
+     && strcmpic(log_message, US"sender verify failed") == 0)
     text = string_sprintf("%s: %s", text, sender_verified_failed->message);
 
   /* Search previously logged warnings. They are kept in malloc
@@ -1366,7 +1372,7 @@ uschar * target = store_get(TARGET_SIZE, GET_TAINTED);
 client's HELO domain. If the client has not said HELO, use its IP address
 instead. If it's a local client (exim -bs), CSA isn't applicable. */
 
-while (isspace(*domain) && *domain != '\0') ++domain;
+while (isspace(*domain) && *domain) ++domain;
 if (*domain == '\0') domain = sender_helo_name;
 if (!domain) domain = sender_host_address;
 if (!sender_host_address) return CSA_UNKNOWN;
@@ -1441,6 +1447,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
 
   /* Extract the numerical SRV fields (p is incremented) */
 
+  if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue;
   GETSHORT(priority, p);
   GETSHORT(weight, p);
   GETSHORT(port, p);
@@ -1710,10 +1717,10 @@ BOOL no_details = FALSE;
 BOOL success_on_redirect = FALSE;
 BOOL quota = FALSE;
 int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
-address_item *sender_vaddr = NULL;
-uschar *verify_sender_address = NULL;
-uschar *pm_mailfrom = NULL;
-uschar *se_mailfrom = NULL;
+address_item * sender_vaddr = NULL;
+const uschar * verify_sender_address = NULL;
+uschar * pm_mailfrom = NULL;
+uschar * se_mailfrom = NULL;
 
 /* Some of the verify items have slash-separated options; some do not. Diagnose
 an error if options are given for items that don't expect them.
@@ -1866,9 +1873,10 @@ switch(vp->value)
       verify_sender_address = sender_address;
     else
       {
-      while (isspace(*s)) s++;
-      if (*s++ != '=') goto BAD_VERIFY;
-      while (isspace(*s)) s++;
+      if (Uskip_whitespace(&s) != '=')
+       goto BAD_VERIFY;
+      s++;
+      Uskip_whitespace(&s);
       verify_sender_address = string_copy(s);
       }
     }
@@ -1910,13 +1918,13 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
     callout = CALLOUT_TIMEOUT_DEFAULT;
     if (*(ss += 7))
       {
-      while (isspace(*ss)) ss++;
+      Uskip_whitespace(&ss);
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
 
-        while (isspace(*sublist)) sublist++;
+       Uskip_whitespace(&sublist);
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
           {
          callout_opt_t * op;
@@ -1930,14 +1938,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
          if (op->has_option)
            {
            opt += Ustrlen(op->name);
-            while (isspace(*opt)) opt++;
+            Uskip_whitespace(&opt);
             if (*opt++ != '=')
               {
               *log_msgptr = string_sprintf("'=' expected after "
                 "\"%s\" in ACL verify condition \"%s\"", op->name, arg);
               return ERROR;
               }
-            while (isspace(*opt)) opt++;
+            Uskip_whitespace(&opt);
            }
          if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
            return ERROR;
@@ -1980,14 +1988,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
     quota = TRUE;
     if (*(ss += 5))
       {
-      while (isspace(*ss)) ss++;
+      Uskip_whitespace(&ss);
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
        int period;
 
-        while (isspace(*sublist)) sublist++;
+        Uskip_whitespace(&sublist);
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
          if (Ustrncmp(opt, "cachepos=", 9) == 0)
            if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
@@ -3120,6 +3128,80 @@ return DEFER;
 
 
 
+#ifndef DISABLE_WELLKNOWN
+/*************************************************
+*   The "wellknown" ACL modifier                 *
+*************************************************/
+
+/* Called by acl_check_condition() below.
+
+Retrieve the given file and encode content as xtext.
+Prefix with a summary line giving the length of plaintext.
+Leave a global pointer to the whole, for output by
+the smtp verb handler code (smtp_in.c).
+
+Arguments:
+  arg          the option string for wellknown=
+  log_msgptr   for error messages
+
+Returns:       OK/FAIL
+*/
+
+static int
+wellknown_process(const uschar * arg, uschar ** log_msgptr)
+{
+struct stat statbuf;
+FILE * rf;
+gstring * g;
+
+wellknown_response = NULL;
+if (f.no_multiline_responses) return FAIL;
+
+/* Check for file existence */
+
+if (!*arg) return FAIL;
+if (Ustat(arg, &statbuf) != 0)
+  { *log_msgptr = US"stat"; goto fail; }
+
+/*XXX perhaps refuse to serve a group- or world-writeable file? */
+
+if (!(rf = Ufopen(arg, "r")))
+  { *log_msgptr = US"open"; goto fail; }
+
+/* Set up summary line for output */
+
+g = string_fmt_append(NULL, "SIZE=%lu\n", (long) statbuf.st_size);
+
+#define LINE_LIM 75
+for (int n = 0, ch; (ch = fgetc(rf)) != EOF; )
+  {
+  /* Xtext-encode, adding output linebreaks for input linebreaks
+  or when the line gets long enough */
+
+  if (ch == '\n')
+    { g = string_fmt_append(g, "+%02X", ch); n = LINE_LIM; }
+  else if (ch < 33 || ch > 126 || ch == '+' || ch == '=')
+    { g = string_fmt_append(g, "+%02X", ch); n += 3; }
+  else
+    { g = string_fmt_append(g, "%c", ch); n++; }
+
+  if (n >= LINE_LIM)
+    { g = string_catn(g, US"\n", 1); n = 0; }
+  }
+#undef LINE_LIM
+
+gstring_release_unused(g);
+wellknown_response = string_from_gstring(g);
+return OK;
+
+fail:
+  *log_msgptr = string_sprintf("wellknown: failed to %s file \"%s\": %s",
+                 *log_msgptr, arg, strerror(errno));
+  return FAIL;
+}
+#endif
+
+
 /*************************************************
 *   Handle conditions/modifiers on an ACL item   *
 *************************************************/
@@ -3309,7 +3391,7 @@ for (; cb; cb = cb->next)
 
     case ACLC_CONTROL:
       {
-      const uschar *p = NULL;
+      const uschar * p = NULL;
       control_type = decode_control(arg, &p, where, log_msgptr);
 
       /* Check if this control makes sense at this time */
@@ -3321,6 +3403,7 @@ for (; cb; cb = cb->next)
        return ERROR;
        }
 
+      /*XXX ought to sort these, just for sanity */
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
@@ -3666,8 +3749,13 @@ for (; cb; cb = cb->next)
            break;
            }
          return ERROR;
-#endif
+#endif /*I18N*/
 
+#ifndef DISABLE_WELLKNOWN
+       case CONTROL_WELLKNOWN:
+         rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL;
+         break;
+#endif
        }
       break;
       }
@@ -3891,7 +3979,7 @@ for (; cb; cb = cb->next)
           }
         s++;
         }
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
@@ -3954,11 +4042,11 @@ for (; cb; cb = cb->next)
        CUSS &recipient_data);
       break;
 
-    #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
     case ACLC_REGEX:
       rc = regex(&arg, textonly);
       break;
-    #endif
+#endif
 
     case ACLC_REMOVE_HEADER:
       setup_remove_header(arg);
@@ -4315,19 +4403,17 @@ if (!s)
 /* At top level, we expand the incoming string. At lower levels, it has already
 been expanded as part of condition processing. */
 
-if (acl_level == 0)
+if (acl_level != 0)
+  ss = s;
+else if (!(ss = expand_string(s)))
   {
-  if (!(ss = expand_string(s)))
-    {
-    if (f.expand_string_forcedfail) return OK;
-    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
-      expand_string_message);
-    return ERROR;
-    }
+  if (f.expand_string_forcedfail) return OK;
+  *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
+    expand_string_message);
+  return ERROR;
   }
-else ss = s;
 
-while (isspace(*ss)) ss++;
+Uskip_whitespace(&ss);
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
@@ -4450,7 +4536,7 @@ while ((acl_current = acl))
        verbs[acl->verb], acl_name);
       if (basic_errno != ERRNO_CALLOUTDEFER)
        {
-       if (search_error_message != NULL && *search_error_message != 0)
+       if (search_error_message && *search_error_message)
          *log_msgptr = search_error_message;
        if (smtp_return_error_details) f.acl_temp_details = TRUE;
        }
@@ -4616,8 +4702,8 @@ if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp)))
 
 for (i = 0; i < 9; i++)
   {
-  while (*s && isspace(*s)) s++;
-  if (!*s) break;
+  if (!Uskip_whitespace(&s))
+    break;
   if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp)))
     {
     tmp = name;
@@ -4712,8 +4798,8 @@ Returns:       OK         access is granted by an ACCEPT verb
 int acl_where = ACL_WHERE_UNKNOWN;
 
 int
-acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr,
-  uschar **log_msgptr)
+acl_check(int where, const uschar * recipient, uschar * s,
+  uschar ** user_msgptr, uschar ** log_msgptr)
 {
 int rc;
 address_item adb;
@@ -4940,7 +5026,7 @@ fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 
 
 uschar *
-acl_standalone_setvar(const uschar * s)
+acl_standalone_setvar(const uschar * s, BOOL taint)
 {
 acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
 uschar * errstr = NULL, * log_msg = NULL;
@@ -4950,7 +5036,7 @@ int e;
 cond->next = NULL;
 cond->type = ACLC_SET;
 if (!acl_varname_to_cond(&s, cond, &errstr)) return errstr;
-if (!acl_data_to_cond(s, cond, US"'-be'", &errstr)) return errstr;
+if (!acl_data_to_cond(s, cond, US"'-be'", taint, &errstr)) return errstr;
 
 if (acl_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN,
                            NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK)