* 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 */
#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,
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
typedef struct condition_def {
uschar *name;
-/* Flag to indicate the condition/modifier has a string expansion done
-at the outer level. In the other cases, expansion already occurs in the
-checking functions. */
- BOOL expand_at_top:1;
-
- BOOL is_modifier:1;
+ /* Flags for actions or checks to do during readconf for this condition */
+ unsigned flags;
+#define ACD_EXP BIT(0) /* do expansion at outer level*/
+#define ACD_MOD BIT(1) /* is a modifier */
+#define ACD_LOAD BIT(2) /* supported by a dynamic-load module */
-/* Bit map vector of which conditions and modifiers are not allowed at certain
-times. For each condition and modifier, there's a bitmap of dis-allowed times.
-For some, it is easier to specify the negation of a small number of allowed
-times. */
+ /* Bit map vector of which conditions and modifiers are not allowed at certain
+ times. For each condition and modifier, there's a bitmap of dis-allowed times.
+ For some, it is easier to specify the negation of a small number of allowed
+ times. */
unsigned forbids;
+#define FORBIDDEN(times) (times)
+#define PERMITTED(times) ((unsigned) ~(times))
} condition_def;
static condition_def conditions[] = {
- [ACLC_ACL] = { US"acl", FALSE, FALSE, 0 },
+ [ACLC_ACL] = { US"acl", 0,
+ FORBIDDEN(0) },
- [ACLC_ADD_HEADER] = { US"add_header", TRUE, TRUE,
- (unsigned int)
- ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ [ACLC_ADD_HEADER] = { US"add_header", ACD_EXP | ACD_MOD,
+ PERMITTED(ACL_BIT_MAIL | ACL_BIT_RCPT |
ACL_BIT_PREDATA | ACL_BIT_DATA |
#ifndef DISABLE_PRDR
ACL_BIT_PRDR |
ACL_BIT_NOTSMTP_START),
},
- [ACLC_AUTHENTICATED] = { US"authenticated", FALSE, FALSE,
- ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
- ACL_BIT_CONNECT | ACL_BIT_HELO,
+ [ACLC_AUTHENTICATED] = { US"authenticated", 0,
+ FORBIDDEN(ACL_BIT_NOTSMTP |
+ ACL_BIT_NOTSMTP_START |
+ ACL_BIT_CONNECT | ACL_BIT_HELO),
},
#ifdef EXPERIMENTAL_BRIGHTMAIL
- [ACLC_BMI_OPTIN] = { US"bmi_optin", TRUE, TRUE,
- ACL_BIT_AUTH |
+ [ACLC_BMI_OPTIN] = { US"bmi_optin", ACD_EXP | ACD_MOD,
+ FORBIDDEN(ACL_BIT_AUTH |
ACL_BIT_CONNECT | ACL_BIT_HELO |
ACL_BIT_DATA | ACL_BIT_MIME |
# ifndef DISABLE_PRDR
ACL_BIT_MAILAUTH |
ACL_BIT_MAIL | ACL_BIT_STARTTLS |
ACL_BIT_VRFY | ACL_BIT_PREDATA |
- ACL_BIT_NOTSMTP_START,
+ ACL_BIT_NOTSMTP_START),
},
#endif
- [ACLC_CONDITION] = { US"condition", TRUE, FALSE, 0 },
- [ACLC_CONTINUE] = { US"continue", TRUE, TRUE, 0 },
+ [ACLC_CONDITION] = { US"condition", ACD_EXP,
+ FORBIDDEN(0) },
+ [ACLC_CONTINUE] = { US"continue", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
/* Certain types of control are always allowed, so we let it through
always and check in the control processing itself. */
- [ACLC_CONTROL] = { US"control", TRUE, TRUE, 0 },
+ [ACLC_CONTROL] = { US"control", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
#ifdef EXPERIMENTAL_DCC
- [ACLC_DCC] = { US"dcc", TRUE, FALSE,
- (unsigned int)
- ~(ACL_BIT_DATA |
+ [ACLC_DCC] = { US"dcc", ACD_EXP,
+ PERMITTED(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
# endif
},
#endif
#ifdef WITH_CONTENT_SCAN
- [ACLC_DECODE] = { US"decode", TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
+ [ACLC_DECODE] = { US"decode", ACD_EXP,
+ PERMITTED(ACL_BIT_MIME) },
#endif
- [ACLC_DELAY] = { US"delay", TRUE, TRUE, ACL_BIT_NOTQUIT },
+ [ACLC_DELAY] = { US"delay", ACD_EXP | ACD_MOD,
+ FORBIDDEN(ACL_BIT_NOTQUIT) },
#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) ~ACL_BIT_DKIM },
+ [ACLC_DKIM_SIGNER] = { US"dkim_signers",
+# if SUPPORT_DKIM==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
+ PERMITTED(ACL_BIT_DKIM) },
+ [ACLC_DKIM_STATUS] = { US"dkim_status",
+# if SUPPORT_DKIM==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
+ PERMITTED(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
+# ifndef DISABLE_PRDR
+ | ACL_BIT_PRDR
+# endif
+ ),
+ },
#endif
#ifdef SUPPORT_DMARC
- [ACLC_DMARC_STATUS] = { US"dmarc_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
+ [ACLC_DMARC_STATUS] = { US"dmarc_status",
+# if SUPPORT_DMARC==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
+ PERMITTED(ACL_BIT_DATA) },
#endif
/* Explicit key lookups can be made in non-smtp ACLs so pass
always and check in the verify processing itself. */
- [ACLC_DNSLISTS] = { US"dnslists", TRUE, FALSE, 0 },
+ [ACLC_DNSLISTS] = { US"dnslists", ACD_EXP,
+ FORBIDDEN(0) },
- [ACLC_DOMAINS] = { US"domains", FALSE, FALSE,
- (unsigned int)
- ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+ [ACLC_DOMAINS] = { US"domains", 0,
+ PERMITTED(ACL_BIT_RCPT | ACL_BIT_VRFY
#ifndef DISABLE_PRDR
- |ACL_BIT_PRDR
+ | ACL_BIT_PRDR
#endif
),
},
- [ACLC_ENCRYPTED] = { US"encrypted", FALSE, FALSE,
- ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
- ACL_BIT_CONNECT
+ [ACLC_ENCRYPTED] = { US"encrypted", 0,
+ FORBIDDEN(ACL_BIT_NOTSMTP |
+ ACL_BIT_NOTSMTP_START | ACL_BIT_CONNECT)
},
- [ACLC_ENDPASS] = { US"endpass", TRUE, TRUE, 0 },
+ [ACLC_ENDPASS] = { US"endpass", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
- [ACLC_HOSTS] = { US"hosts", FALSE, FALSE,
- ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+ [ACLC_HOSTS] = { US"hosts", 0,
+ FORBIDDEN(ACL_BIT_NOTSMTP |
+ ACL_BIT_NOTSMTP_START),
},
- [ACLC_LOCAL_PARTS] = { US"local_parts", FALSE, FALSE,
- (unsigned int)
- ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+ [ACLC_LOCAL_PARTS] = { US"local_parts", 0,
+ PERMITTED(ACL_BIT_RCPT | ACL_BIT_VRFY
#ifndef DISABLE_PRDR
| ACL_BIT_PRDR
#endif
),
},
- [ACLC_LOG_MESSAGE] = { US"log_message", TRUE, TRUE, 0 },
- [ACLC_LOG_REJECT_TARGET] = { US"log_reject_target", TRUE, TRUE, 0 },
- [ACLC_LOGWRITE] = { US"logwrite", TRUE, TRUE, 0 },
+ [ACLC_LOG_MESSAGE] = { US"log_message", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
+ [ACLC_LOG_REJECT_TARGET] = { US"log_reject_target", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
+ [ACLC_LOGWRITE] = { US"logwrite", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
#ifdef WITH_CONTENT_SCAN
- [ACLC_MALWARE] = { US"malware", TRUE, FALSE,
- (unsigned int)
- ~(ACL_BIT_DATA |
+ [ACLC_MALWARE] = { US"malware", ACD_EXP,
+ PERMITTED(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
# endif
},
#endif
- [ACLC_MESSAGE] = { US"message", TRUE, TRUE, 0 },
+ [ACLC_MESSAGE] = { US"message", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
#ifdef WITH_CONTENT_SCAN
- [ACLC_MIME_REGEX] = { US"mime_regex", TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
+ [ACLC_MIME_REGEX] = { US"mime_regex", ACD_EXP,
+ PERMITTED(ACL_BIT_MIME) },
#endif
- [ACLC_QUEUE] = { US"queue", TRUE, TRUE,
- ACL_BIT_NOTSMTP |
+ [ACLC_QUEUE] = { US"queue", ACD_EXP | ACD_MOD,
+ FORBIDDEN(ACL_BIT_NOTSMTP |
#ifndef DISABLE_PRDR
ACL_BIT_PRDR |
#endif
- ACL_BIT_DATA,
+ ACL_BIT_DATA),
},
- [ACLC_RATELIMIT] = { US"ratelimit", TRUE, FALSE, 0 },
- [ACLC_RECIPIENTS] = { US"recipients", FALSE, FALSE, (unsigned int) ~ACL_BIT_RCPT },
+ [ACLC_RATELIMIT] = { US"ratelimit", ACD_EXP,
+ FORBIDDEN(0) },
+ [ACLC_RECIPIENTS] = { US"recipients", 0,
+ PERMITTED(ACL_BIT_RCPT) },
#ifdef WITH_CONTENT_SCAN
- [ACLC_REGEX] = { US"regex", TRUE, FALSE,
- (unsigned int)
- ~(ACL_BIT_DATA |
+ [ACLC_REGEX] = { US"regex", ACD_EXP,
+ PERMITTED(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
# endif
},
#endif
- [ACLC_REMOVE_HEADER] = { US"remove_header", TRUE, TRUE,
- (unsigned int)
- ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
+ [ACLC_REMOVE_HEADER] = { US"remove_header", ACD_EXP | ACD_MOD,
+ PERMITTED(ACL_BIT_MAIL|ACL_BIT_RCPT |
ACL_BIT_PREDATA | ACL_BIT_DATA |
#ifndef DISABLE_PRDR
ACL_BIT_PRDR |
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 |
+ [ACLC_SEEN] = { US"seen", ACD_EXP,
+ FORBIDDEN(0) },
+ [ACLC_SENDER_DOMAINS] = { US"sender_domains", 0,
+ FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
ACL_BIT_HELO |
ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
ACL_BIT_ETRN | ACL_BIT_EXPN |
- ACL_BIT_STARTTLS | ACL_BIT_VRFY,
+ ACL_BIT_STARTTLS | ACL_BIT_VRFY),
},
- [ACLC_SENDERS] = { US"senders", FALSE, FALSE,
- ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ [ACLC_SENDERS] = { US"senders", 0,
+ FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
ACL_BIT_HELO |
ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
ACL_BIT_ETRN | ACL_BIT_EXPN |
- ACL_BIT_STARTTLS | ACL_BIT_VRFY,
+ ACL_BIT_STARTTLS | ACL_BIT_VRFY),
},
- [ACLC_SET] = { US"set", TRUE, TRUE, 0 },
+ [ACLC_SET] = { US"set", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
#ifdef WITH_CONTENT_SCAN
- [ACLC_SPAM] = { US"spam", TRUE, FALSE,
- (unsigned int) ~(ACL_BIT_DATA |
+ [ACLC_SPAM] = { US"spam", ACD_EXP,
+ PERMITTED(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
# endif
},
#endif
#ifdef SUPPORT_SPF
- [ACLC_SPF] = { US"spf", TRUE, FALSE,
- ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ [ACLC_SPF] = { US"spf",
+# if SUPPORT_SPF==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
+ FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
ACL_BIT_HELO | ACL_BIT_MAILAUTH |
ACL_BIT_ETRN | ACL_BIT_EXPN |
ACL_BIT_STARTTLS | ACL_BIT_VRFY |
- ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START),
},
- [ACLC_SPF_GUESS] = { US"spf_guess", TRUE, FALSE,
- ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ [ACLC_SPF_GUESS] = { US"spf_guess",
+# if SUPPORT_SPF==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
+ FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
ACL_BIT_HELO | ACL_BIT_MAILAUTH |
ACL_BIT_ETRN | ACL_BIT_EXPN |
ACL_BIT_STARTTLS | ACL_BIT_VRFY |
- ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START),
},
#endif
- [ACLC_UDPSEND] = { US"udpsend", TRUE, TRUE, 0 },
+ [ACLC_UDPSEND] = { US"udpsend", ACD_EXP | ACD_MOD,
+ FORBIDDEN(0) },
/* Certain types of verify are always allowed, so we let it through
always and check in the verify function itself */
- [ACLC_VERIFY] = { US"verify", TRUE, FALSE, 0 },
+ [ACLC_VERIFY] = { US"verify", ACD_EXP,
+ FORBIDDEN(0) },
};
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");
+ int n = sprintf(CS buf, "_ACL_%s_", c->flags & ACD_MOD ? "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! */
+/* These tables support loading of dynamic modules triggered by an ACL
+condition use, spotted during readconf. See acl_read(). */
+
+# ifdef LOOKUP_MODULE_DIR
+typedef struct condition_module {
+ const uschar * mod_name; /* module for the givien conditions */
+ misc_module_info * info; /* NULL when not loaded */
+ const int * conditions; /* array of ACLC_*, -1 terminated */
+} condition_module;
+
+# if SUPPORT_SPF==2
+static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
+# endif
+# if SUPPORT_DKIM==2
+static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 };
+# endif
+# if SUPPORT_DMARC==2
+static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 };
+# endif
+
+/* These are modules which can be loaded on seeing an ACL condition
+during readconf, The "arc" module is handled by custom coding. */
+
+static condition_module condition_modules[] = {
+# if SUPPORT_SPF==2
+ {.mod_name = US"spf", .conditions = spf_condx},
+# endif
+# if SUPPORT_DKIM==2
+ {.mod_name = US"dkim", .conditions = dkim_condx},
+# endif
+# if SUPPORT_DMARC==2
+ {.mod_name = US"dmarc", .conditions = dmarc_condx},
+# endif
+};
+
+# endif /*LOOKUP_MODULE_DIR*/
+
+/****************************/
+
+/* Return values from decode_control() */
enum {
CONTROL_AUTH_UNADVERTISED,
#ifdef SUPPORT_I18N
CONTROL_UTF8_DOWNCONVERT,
#endif
+#ifndef DISABLE_WELLKNOWN
+ CONTROL_WELLKNOWN,
+#endif
};
#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
};
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;;
+ conditions[cond->type].flags & ACD_MOD ? US"modifier" : US"condition");
+ return FALSE;
}
Uskip_whitespace(&s);
-cond->arg = string_copy(s);
+cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
return TRUE;
}
if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0)
{
- if (!this)
+ if (!this) /* not handling a verb right now */
{
*error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name,
saveline);
/* The modifiers may not be negated */
- if (negated && conditions[c].is_modifier)
+ if (negated && conditions[c].flags & ACD_MOD)
{
*error = string_sprintf("ACL error: negation is not allowed with "
"\"%s\"", conditions[c].name);
return NULL;
}
+#ifdef LOOKUP_MODULE_DIR
+ if (conditions[c].flags & ACD_LOAD)
+ { /* a loadable module supports this condition */
+ condition_module * cm;
+ uschar * s = NULL;
+
+ /* Over the list of modules we support, check the list of ACL conditions
+ each supports. This assumes no duplicates. */
+
+ for (cm = condition_modules;
+ cm < condition_modules + nelem(condition_modules); cm++)
+ for (const int * cond = cm->conditions; *cond != -1; cond++)
+ if (*cond == c) goto found;
+ found:
+
+ if (cm >= condition_modules + nelem(condition_modules))
+ { /* shouldn't happen */
+ *error = string_sprintf("ACL error: failed to locate support for '%s'",
+ conditions[c].name);
+ return NULL;
+ }
+ if ( !cm->info /* module not loaded */
+ && !(cm->info = misc_mod_find(cm->mod_name, &s)))
+ {
+ *error = string_sprintf("ACL error: failed to find module for '%s': %s",
+ conditions[c].name, s);
+ return NULL;
+ }
+ }
+# ifdef EXPERIMENTAL_ARC
+ else if (c == ACLC_VERIFY) /* Special handling for verify=arc; */
+ { /* not invented a more general method yet- flag in verify_type_list? */
+ const uschar * t = s;
+ uschar * e;
+ if ( *t++ == '=' && Uskip_whitespace(&t) && Ustrncmp(t, "arc", 3) == 0
+ && !misc_mod_find(US"arc", &e))
+ {
+ *error = string_sprintf("ACL error: failed to find module for '%s': %s",
+ conditions[c].name, e);
+ return NULL;
+ }
+ }
+# endif
+#endif /*LOOKUP_MODULE_DIR*/
+
cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
cond->next = NULL;
cond->type = c;
"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;
g = string_append_listele_n(g, '\n', h->text, i);
}
-return g ? g->s : NULL;
+return string_from_gstring(g);
}
*/
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;
/* 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
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;
/* 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);
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.
#ifdef EXPERIMENTAL_ARC
case VERIFY_ARC:
- { /* Do Authenticated Received Chain checks in a separate function. */
- const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
- int csep = 0;
- uschar * cond;
-
- if (!(arc_state = acl_verify_arc())) return DEFER;
- DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
- arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
-
- if (!condlist) condlist = US"none:pass";
- while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
- if (Ustrcmp(arc_state, cond) == 0) return OK;
- return FAIL;
+ {
+ const misc_module_info * mi = misc_mod_findonly(US"arc");
+ typedef int (*fn_t)(const uschar *);
+ if (mi) return (((fn_t *) mi->functions)[ARC_VERIFY])
+ (CUS string_nextinlist(&list, &sep, NULL, 0));
}
#endif
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);
}
}
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;
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;
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)
anchor = NULL; /* silence an "unused" complaint */
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"internal ACL error: unknown ratelimit mode %d", mode);
+ /*NOTREACHED*/
break;
}
/* We aren't using a pre-computed rate, so get a previously recorded rate
from the database, which will be updated and written back if required. */
-if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE, TRUE)))
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
{
store_pool = old_pool;
sender_rate = NULL;
else
goto badopt;
-if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE)))
+if (!(dbm = dbfn_open(US"seen", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
{
HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n");
*log_msgptr = US"database for 'seen' not available";
+#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 *
*************************************************/
of them, but not for all, because expansion happens down in some lower level
checking functions in some cases. */
- if (!conditions[cb->type].expand_at_top)
+ if (!(conditions[cb->type].flags & ACD_EXP))
arg = cb->arg;
else if (!(arg = expand_string_2(cb->arg, &textonly)))
{
int lhswidth = 0;
debug_printf_indent("check %s%s %n",
- (!conditions[cb->type].is_modifier && cb->u.negated)? "!":"",
+ (!(conditions[cb->type].flags & ACD_MOD) && cb->u.negated) ? "!":"",
conditions[cb->type].name, &lhswidth);
if (cb->type == ACLC_SET)
if ((conditions[cb->type].forbids & (1 << where)) != 0)
{
*log_msgptr = string_sprintf("cannot %s %s condition in %s ACL",
- conditions[cb->type].is_modifier ? "use" : "test",
+ conditions[cb->type].flags & ACD_MOD ? "use" : "test",
conditions[cb->type].name, acl_wherenames[where]);
return ERROR;
}
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 */
return ERROR;
}
+ /*XXX ought to sort these, just for sanity */
switch(control_type)
{
case CONTROL_AUTH_UNADVERTISED:
case CONTROL_FAKEREJECT:
cancel_cutthrough_connection(TRUE, US"fakereject");
case CONTROL_FAKEDEFER:
- fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
+ fake_response = control_type == CONTROL_FAKEDEFER ? DEFER : FAIL;
if (*p == '/')
{
const uschar *pp = p + 1;
break;
}
return ERROR;
-#endif
+#endif /*I18N*/
+#ifndef DISABLE_WELLKNOWN
+ case CONTROL_WELLKNOWN:
+ rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL;
+ break;
+#endif
}
break;
}
#ifndef DISABLE_DKIM
case ACLC_DKIM_SIGNER:
- if (dkim_cur_signer)
- rc = match_isinlist(dkim_cur_signer,
- &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- else
- rc = FAIL;
- break;
-
case ACLC_DKIM_STATUS:
- rc = match_isinlist(dkim_verify_status,
- &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+ /* See comment on ACLC_SPF wrt. coding issues */
+ {
+ misc_module_info * mi = misc_mod_find(US"dkim", &log_message);
+ typedef int (*fn_t)(const uschar *);
+ rc = mi
+ ? (((fn_t *) mi->functions)
+ [cb->type == ACLC_DKIM_SIGNER
+ ? DKIM_SIGNER_ISINLIST
+ : DKIM_STATUS_LISTMATCH]) (arg)
+ : DEFER;
break;
+ }
#endif
#ifdef SUPPORT_DMARC
case ACLC_DMARC_STATUS:
+ /* See comment on ACLC_SPF wrt. coding issues */
+ {
+ misc_module_info * mi = misc_mod_find(US"dmarc", &log_message);
+ typedef uschar * (*efn_t)(int);
+ uschar * expanded_query;
+
+ if (!mi)
+ { rc = DEFER; break; } /* shouldn't happen */
+
if (!f.dmarc_has_been_checked)
- dmarc_process();
- f.dmarc_has_been_checked = TRUE;
+ {
+ typedef int (*pfn_t)(void);
+ (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) ();
+ f.dmarc_has_been_checked = TRUE;
+ }
+
/* used long way of dmarc_exim_expand_query() in case we need more
- * view into the process in the future. */
- rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
+ view into the process in the future. */
+
+ /*XXX is this call used with any other arg? */
+ expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY])
+ (DMARC_VERIFY_STATUS);
+ rc = match_isinlist(expanded_query,
&arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+ }
break;
#endif
}
s++;
}
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
if (logbits == 0) logbits = LOG_MAIN;
log_write(0, logbits, "%s", string_printing(s));
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);
#endif
)
store_pool = POOL_PERM;
+
#ifndef DISABLE_DKIM /* Overwriteable dkim result variables */
- if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
- dkim_verify_status = string_copy(arg);
- else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
- dkim_verify_reason = string_copy(arg);
+ if ( Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+ || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0
+ )
+ {
+ misc_module_info * mi = misc_mod_findonly(US"dkim");
+ typedef void (*fn_t)(const uschar *, void *);
+
+ if (mi)
+ (((fn_t *) mi->functions)[DKIM_SETVAR])
+ (cb->u.varname, string_copy(arg));
+ }
else
#endif
acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
#ifdef SUPPORT_SPF
case ACLC_SPF:
- rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
- break;
-
case ACLC_SPF_GUESS:
- rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
+ /* We have hardwired function-call numbers, and also prototypes for the
+ functions. We could do a function name table search or (simpler)
+ a module include file with defines for the numbers
+ but I can't see how to deal with prototypes. Is a K&R non-prototyped
+ function still usable with today's compilers (but we would lose on
+ type-checking)? We could macroize the typedef, and even the function
+ table access - but it obscures how it works rather. */
+ {
+ misc_module_info * mi = misc_mod_find(US"spf", &log_message);
+ typedef int (*fn_t)(const uschar **, const uschar *, int);
+ fn_t fn;
+
+ if (!mi)
+ { rc = DEFER; break; } /* shouldn't happen */
+
+ fn = ((fn_t *) mi->functions)[SPF_PROCESS];
+
+ rc = fn(&arg, sender_address,
+ cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS);
break;
+ }
#endif
case ACLC_UDPSEND:
/* If a condition was negated, invert OK/FAIL. */
- if (!conditions[cb->type].is_modifier && cb->u.negated)
+ if (!(conditions[cb->type].flags & ACD_MOD) && cb->u.negated)
if (rc == OK) rc = FAIL;
else if (rc == FAIL || rc == FAIL_DROP) rc = OK;
/* 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.) */
*log_msgptr = *user_msgptr = NULL;
f.acl_temp_details = FALSE;
+ config_filename = acl->srcfile;
+ config_lineno = acl->srcline;
+
HDEBUG(D_acl) debug_printf_indent("processing \"%s\" (%s %d)\n",
- verbs[acl->verb], acl->srcfile, acl->srcline);
+ verbs[acl->verb], config_filename, config_lineno);
/* Clear out any search error message from a previous check before testing
this condition. */
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;
}
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;
acl_level++;
ret = acl_check_internal(where, addr, name, user_msgptr, log_msgptr);
acl_level--;
+config_lineno = 0;
acl_narg = sav_narg;
for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
acl_level++;
rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
acl_level--;
+config_lineno = 0;
return rc;
}
DEFER can't tell at the moment
ERROR disaster
*/
-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;
rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
acl_level = 0;
acl_where = ACL_WHERE_UNKNOWN;
+config_lineno = 0;
/* Cutthrough - if requested,
and WHERE_RCPT and not yet opened conn as result of recipient-verify,
putc('-', f);
if (is_tainted(value))
{
- int q = quoter_for_address(value);
+ const uschar * quoter_name;
putc('-', f);
- if (is_real_quoter(q)) fprintf(f, "(%s)", lookup_list[q]->name);
+ (void) quoter_for_address(value, "er_name);
+ if (quoter_name)
+ fprintf(f, "(%s)", quoter_name);
}
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;
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)