/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* 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 */
/* Code for handling Access Control Lists (ACLs) */
}
+static BOOL
+acl_varname_to_cond(const uschar ** sp, acl_condition_block * cond, uschar ** error)
+{
+const uschar * s = *sp, * endptr;
+
+#ifndef DISABLE_DKIM
+if ( Ustrncmp(s, "dkim_verify_status", 18) == 0
+ || Ustrncmp(s, "dkim_verify_reason", 18) == 0)
+ {
+ endptr = s+18;
+ if (isalnum(*endptr))
+ {
+ *error = string_sprintf("invalid variable name after \"set\" in ACL "
+ "modifier \"set %s\" "
+ "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)",
+ s);
+ return FALSE;
+ }
+ cond->u.varname = string_copyn(s, 18);
+ }
+else
+#endif
+ {
+ if (Ustrncmp(s, "acl_c", 5) != 0 && Ustrncmp(s, "acl_m", 5) != 0)
+ {
+ *error = string_sprintf("invalid variable name after \"set\" in ACL "
+ "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s);
+ return FALSE;
+ }
+
+ endptr = s + 5;
+ if (!isdigit(*endptr) && *endptr != '_')
+ {
+ *error = string_sprintf("invalid variable name after \"set\" in ACL "
+ "modifier \"set %s\" (digit or underscore must follow acl_c or acl_m)",
+ s);
+ return FALSE;
+ }
+
+ for ( ; *endptr && *endptr != '=' && !isspace(*endptr); endptr++)
+ if (!isalnum(*endptr) && *endptr != '_')
+ {
+ *error = string_sprintf("invalid character \"%c\" in variable name "
+ "in ACL modifier \"set %s\"", *endptr, s);
+ return FALSE;
+ }
+
+ cond->u.varname = string_copyn(s + 4, endptr - s - 4);
+ }
+s = endptr;
+Uskip_whitespace(&s);
+*sp = s;
+return TRUE;
+}
+
+
+static BOOL
+acl_data_to_cond(const uschar * s, acl_condition_block * cond,
+ const uschar * name, uschar ** error)
+{
+if (*s++ != '=')
+ {
+ *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
+ conditions[cond->type].is_modifier ? US"modifier" : US"condition");
+ return FALSE;;
+ }
+Uskip_whitespace(&s);
+cond->arg = string_copy(s);
+return TRUE;
+}
+
+
/*************************************************
* Read and parse one ACL *
*************************************************/
acl_block *this = NULL;
acl_condition_block *cond;
acl_condition_block **condp = NULL;
-uschar * s;
+const uschar * s;
*error = NULL;
{
int v, c;
BOOL negated = FALSE;
- uschar *saveline = s;
+ const uschar * saveline = s;
uschar name[EXIM_DRIVERNAME_MAX];
/* Conditions (but not verbs) are allowed to be negated by an initial
*error = string_sprintf("malformed ACL line \"%s\"", saveline);
return NULL;
}
- this = store_get(sizeof(acl_block), GET_UNTAINTED);
- *lastp = this;
- lastp = &(this->next);
+ *lastp = this = store_get(sizeof(acl_block), GET_UNTAINTED);
+ lastp = &this->next;
this->next = NULL;
this->condition = NULL;
this->verb = v;
this->srcline = config_lineno; /* for debug output */
this->srcfile = config_filename; /**/
- condp = &(this->condition);
- if (*s == 0) continue; /* No condition on this line */
+ condp = &this->condition;
+ if (!*s) continue; /* No condition on this line */
if (*s == '!')
{
negated = TRUE;
cond->u.negated = negated;
*condp = cond;
- condp = &(cond->next);
+ condp = &cond->next;
/* The "set" modifier is different in that its argument is "name=value"
rather than just a value, and we can check the validity of the name, which
compatibility. */
if (c == ACLC_SET)
-#ifndef DISABLE_DKIM
- if ( Ustrncmp(s, "dkim_verify_status", 18) == 0
- || Ustrncmp(s, "dkim_verify_reason", 18) == 0)
- {
- uschar * endptr = s+18;
-
- if (isalnum(*endptr))
- {
- *error = string_sprintf("invalid variable name after \"set\" in ACL "
- "modifier \"set %s\" "
- "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)",
- s);
- return NULL;
- }
- cond->u.varname = string_copyn(s, 18);
- s = endptr;
- Uskip_whitespace(&s);
- }
- else
-#endif
- {
- uschar *endptr;
-
- if (Ustrncmp(s, "acl_c", 5) != 0 && Ustrncmp(s, "acl_m", 5) != 0)
- {
- *error = string_sprintf("invalid variable name after \"set\" in ACL "
- "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s);
- return NULL;
- }
-
- endptr = s + 5;
- if (!isdigit(*endptr) && *endptr != '_')
- {
- *error = string_sprintf("invalid variable name after \"set\" in ACL "
- "modifier \"set %s\" (digit or underscore must follow acl_c or acl_m)",
- s);
- return NULL;
- }
-
- while (*endptr && *endptr != '=' && !isspace(*endptr))
- {
- if (!isalnum(*endptr) && *endptr != '_')
- {
- *error = string_sprintf("invalid character \"%c\" in variable name "
- "in ACL modifier \"set %s\"", *endptr, s);
- return NULL;
- }
- endptr++;
- }
-
- cond->u.varname = string_copyn(s + 4, endptr - s - 4);
- s = endptr;
- Uskip_whitespace(&s);
- }
+ if (!acl_varname_to_cond(&s, cond, error)) return NULL;
/* For "set", we are now positioned for the data. For the others, only
"endpass" has no data */
if (c != ACLC_ENDPASS)
- {
- if (*s++ != '=')
- {
- *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
- conditions[c].is_modifier ? US"modifier" : US"condition");
- return NULL;
- }
- Uskip_whitespace(&s);
- cond->arg = string_copy(s);
- }
+ if (!acl_data_to_cond(s, cond, name, error)) return NULL;
}
return yield;
address_item *addr, int level, BOOL *epp, uschar **user_msgptr,
uschar **log_msgptr, int *basic_errno)
{
-uschar *user_message = NULL;
-uschar *log_message = NULL;
+uschar * user_message = NULL;
+uschar * log_message = NULL;
int rc = OK;
-#ifdef WITH_CONTENT_SCAN
-int sep = -'/';
-#endif
for (; cb; cb = cb->next)
{
- const uschar *arg;
+ const uschar * arg;
int control_type;
+ BOOL textonly = FALSE;
/* The message and log_message items set up messages to be used in
case of rejection. They are expanded later. */
if (!conditions[cb->type].expand_at_top)
arg = cb->arg;
- else if (!(arg = expand_string(cb->arg)))
+
+ else if (!(arg = expand_string_2(cb->arg, &textonly)))
{
if (f.expand_string_forcedfail) continue;
*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
switch(cb->type)
{
case ACLC_ADD_HEADER:
- setup_header(arg);
- break;
+ setup_header(arg);
+ break;
/* A nested ACL that returns "discard" makes sense only for an "accept" or
"discard" verb. */
verbs[verb]);
return ERROR;
}
- break;
+ break;
case ACLC_AUTHENTICATED:
rc = sender_host_authenticated ? match_isinlist(sender_host_authenticated,
&arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL;
- break;
+ break;
#ifdef EXPERIMENTAL_BRIGHTMAIL
case ACLC_BMI_OPTIN:
/* The true/false parsing here should be kept in sync with that used in
expand.c when dealing with ECOND_BOOL so that we don't have too many
different definitions of what can be a boolean. */
- if (*arg == '-'
- ? Ustrspn(arg+1, "0123456789") == Ustrlen(arg+1) /* Negative number */
- : Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */
- rc = (Uatoi(arg) == 0)? FAIL : OK;
- else
- rc = (strcmpic(arg, US"no") == 0 ||
- strcmpic(arg, US"false") == 0)? FAIL :
- (strcmpic(arg, US"yes") == 0 ||
- strcmpic(arg, US"true") == 0)? OK : DEFER;
- if (rc == DEFER)
- *log_msgptr = string_sprintf("invalid \"condition\" value \"%s\"", arg);
- break;
+ if (*arg == '-'
+ ? Ustrspn(arg+1, "0123456789") == Ustrlen(arg+1) /* Negative number */
+ : Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */
+ rc = (Uatoi(arg) == 0)? FAIL : OK;
+ else
+ rc = (strcmpic(arg, US"no") == 0 ||
+ strcmpic(arg, US"false") == 0)? FAIL :
+ (strcmpic(arg, US"yes") == 0 ||
+ strcmpic(arg, US"true") == 0)? OK : DEFER;
+ if (rc == DEFER)
+ *log_msgptr = string_sprintf("invalid \"condition\" value \"%s\"", arg);
+ break;
case ACLC_CONTINUE: /* Always succeeds */
- break;
+ break;
case ACLC_CONTROL:
{
break;
}
- #ifdef EXPERIMENTAL_DCC
+#ifdef EXPERIMENTAL_DCC
case ACLC_DCC:
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
+ int sep = -'/';
+ uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
/* Run the dcc backend. */
rc = dcc_process(&ss);
/* Modify return code based upon the existence of options. */
while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
+ break;
}
- break;
- #endif
+#endif
- #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
case ACLC_DECODE:
- rc = mime_decode(&arg);
- break;
- #endif
+ rc = mime_decode(&arg);
+ break;
+#endif
case ACLC_DELAY:
{
#endif
}
}
+ break;
}
- break;
#ifndef DISABLE_DKIM
case ACLC_DKIM_SIGNER:
- if (dkim_cur_signer)
- rc = match_isinlist(dkim_cur_signer,
+ if (dkim_cur_signer)
+ rc = match_isinlist(dkim_cur_signer,
&arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- else
- rc = FAIL;
- break;
+ else
+ rc = FAIL;
+ break;
case ACLC_DKIM_STATUS:
- rc = match_isinlist(dkim_verify_status,
- &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- break;
+ rc = match_isinlist(dkim_verify_status,
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+ break;
#endif
#ifdef SUPPORT_DMARC
case ACLC_DMARC_STATUS:
- if (!f.dmarc_has_been_checked)
- 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),
- &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- break;
+ if (!f.dmarc_has_been_checked)
+ 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),
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+ break;
#endif
case ACLC_DNSLISTS:
- rc = verify_check_dnsbl(where, &arg, log_msgptr);
- break;
+ rc = verify_check_dnsbl(where, &arg, log_msgptr);
+ break;
case ACLC_DOMAINS:
- rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor,
- addr->domain_cache, MCL_DOMAIN, TRUE, CUSS &deliver_domain_data);
- break;
+ rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor,
+ addr->domain_cache, MCL_DOMAIN, TRUE, CUSS &deliver_domain_data);
+ break;
/* The value in tls_cipher is the full cipher name, for example,
TLSv1:DES-CBC3-SHA:168, whereas the values to test for are just the
writing is poorly documented. */
case ACLC_ENCRYPTED:
- if (tls_in.cipher == NULL) rc = FAIL; else
- {
- uschar *endcipher = NULL;
- uschar *cipher = Ustrchr(tls_in.cipher, ':');
- if (!cipher) cipher = tls_in.cipher; else
- {
- endcipher = Ustrchr(++cipher, ':');
- if (endcipher) *endcipher = 0;
- }
- rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- if (endcipher) *endcipher = ':';
- }
- break;
+ if (!tls_in.cipher) rc = FAIL;
+ else
+ {
+ uschar *endcipher = NULL;
+ uschar *cipher = Ustrchr(tls_in.cipher, ':');
+ if (!cipher) cipher = tls_in.cipher; else
+ {
+ endcipher = Ustrchr(++cipher, ':');
+ if (endcipher) *endcipher = 0;
+ }
+ rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+ if (endcipher) *endcipher = ':';
+ }
+ break;
/* Use verify_check_this_host() instead of verify_check_host() so that
we can pass over &host_data to catch any looked up data. Once it has been
message in the same SMTP connection. */
case ACLC_HOSTS:
- rc = verify_check_this_host(&arg, sender_host_cache, NULL,
- sender_host_address ? sender_host_address : US"", CUSS &host_data);
- if (rc == DEFER) *log_msgptr = search_error_message;
- if (host_data) host_data = string_copy_perm(host_data, TRUE);
- break;
+ rc = verify_check_this_host(&arg, sender_host_cache, NULL,
+ sender_host_address ? sender_host_address : US"", CUSS &host_data);
+ if (rc == DEFER) *log_msgptr = search_error_message;
+ if (host_data) host_data = string_copy_perm(host_data, TRUE);
+ break;
case ACLC_LOCAL_PARTS:
- rc = match_isinlist(addr->cc_local_part, &arg, 0,
- &localpartlist_anchor, addr->localpart_cache, MCL_LOCALPART, TRUE,
- CUSS &deliver_localpart_data);
- break;
+ rc = match_isinlist(addr->cc_local_part, &arg, 0,
+ &localpartlist_anchor, addr->localpart_cache, MCL_LOCALPART, TRUE,
+ CUSS &deliver_localpart_data);
+ break;
case ACLC_LOG_REJECT_TARGET:
{
- int logbits = 0;
- int sep = 0;
- const uschar *s = arg;
- uschar * ss;
- while ((ss = string_nextinlist(&s, &sep, NULL, 0)))
+ int logbits = 0, sep = 0;
+ const uschar * s = arg;
+
+ for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
{
if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
}
}
log_reject_target = logbits;
+ break;
}
- break;
case ACLC_LOGWRITE:
{
if (logbits == 0) logbits = LOG_MAIN;
log_write(0, logbits, "%s", string_printing(s));
+ break;
}
- break;
- #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
case ACLC_MALWARE: /* Run the malware backend. */
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
- uschar * opt;
BOOL defer_ok = FALSE;
- int timeout = 0;
+ int timeout = 0, sep = -'/';
+ uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
- while ((opt = string_nextinlist(&list, &sep, NULL, 0)))
+ for (uschar * opt; opt = string_nextinlist(&list, &sep, NULL, 0); )
if (strcmpic(opt, US"defer_ok") == 0)
defer_ok = TRUE;
else if ( strncmpic(opt, US"tmo=", 4) == 0
return ERROR;
}
- rc = malware(ss, timeout);
+ rc = malware(ss, textonly, timeout);
if (rc == DEFER && defer_ok)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
+ break;
}
- break;
case ACLC_MIME_REGEX:
- rc = mime_regex(&arg);
- break;
- #endif
+ rc = mime_regex(&arg, textonly);
+ break;
+#endif
case ACLC_QUEUE:
- if (is_tainted(arg))
- {
- *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted",
- arg);
- return ERROR;
- }
- if (Ustrchr(arg, '/'))
- {
- *log_msgptr = string_sprintf(
- "Directory separator not permitted in queue name: '%s'", arg);
- return ERROR;
- }
- queue_name = string_copy_perm(arg, FALSE);
- break;
+ if (is_tainted(arg))
+ {
+ *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted",
+ arg);
+ return ERROR;
+ }
+ if (Ustrchr(arg, '/'))
+ {
+ *log_msgptr = string_sprintf(
+ "Directory separator not permitted in queue name: '%s'", arg);
+ return ERROR;
+ }
+ queue_name = string_copy_perm(arg, FALSE);
+ break;
case ACLC_RATELIMIT:
- rc = acl_ratelimit(arg, where, log_msgptr);
- break;
+ rc = acl_ratelimit(arg, where, log_msgptr);
+ break;
case ACLC_RECIPIENTS:
- rc = match_address_list(CUS addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
- CUSS &recipient_data);
- break;
+ rc = match_address_list(CUS addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
+ CUSS &recipient_data);
+ break;
#ifdef WITH_CONTENT_SCAN
case ACLC_REGEX:
- rc = regex(&arg);
- break;
+ rc = regex(&arg, textonly);
+ break;
#endif
case ACLC_REMOVE_HEADER:
- setup_remove_header(arg);
- break;
+ setup_remove_header(arg);
+ break;
case ACLC_SEEN:
- rc = acl_seen(arg, where, log_msgptr);
- break;
+ rc = acl_seen(arg, where, log_msgptr);
+ break;
case ACLC_SENDER_DOMAINS:
{
sdomain = sdomain ? sdomain + 1 : US"";
rc = match_isinlist(sdomain, &arg, 0, &domainlist_anchor,
sender_domain_cache, MCL_DOMAIN, TRUE, NULL);
+ break;
}
- break;
case ACLC_SENDERS:
- rc = match_address_list(CUS sender_address, TRUE, TRUE, &arg,
- sender_address_cache, -1, 0, CUSS &sender_data);
- break;
+ rc = match_address_list(CUS sender_address, TRUE, TRUE, &arg,
+ sender_address_cache, -1, 0, CUSS &sender_data);
+ break;
/* Connection variables must persist forever; message variables not */
#endif
acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
store_pool = old_pool;
+ break;
}
- break;
#ifdef WITH_CONTENT_SCAN
case ACLC_SPAM:
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
+ int sep = -'/';
+ uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
rc = spam(CUSS &ss);
/* Modify return code based upon the existence of options. */
while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
+ break;
}
- break;
#endif
#ifdef SUPPORT_SPF
case ACLC_SPF:
rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
- break;
+ break;
+
case ACLC_SPF_GUESS:
rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
- break;
+ break;
#endif
case ACLC_UDPSEND:
- rc = acl_udpsend(arg, log_msgptr);
- break;
+ rc = acl_udpsend(arg, log_msgptr);
+ break;
/* If the verb is WARN, discard any user message from verification, because
such messages are SMTP responses, not header additions. The latter come
(until something changes it). */
case ACLC_VERIFY:
- rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
- if (*user_msgptr)
- acl_verify_message = *user_msgptr;
- if (verb == ACL_WARN) *user_msgptr = NULL;
- break;
+ rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
+ if (*user_msgptr)
+ acl_verify_message = *user_msgptr;
+ if (verb == ACL_WARN) *user_msgptr = NULL;
+ break;
default:
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown "
- "condition %d", cb->type);
- break;
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown "
+ "condition %d", cb->type);
+ break;
}
/* If a condition was negated, invert OK/FAIL. */
fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
}
+
+
+
+uschar *
+acl_standalone_setvar(const uschar * s)
+{
+acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
+uschar * errstr = NULL, * log_msg = NULL;
+BOOL endpass_seen;
+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_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN,
+ NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK)
+ return string_sprintf("oops: %s", errstr);
+return string_sprintf("variable %s set", cond->u.varname);
+}
+
+
#endif /* !MACRO_PREDEF */
/* vi: aw ai sw=2
*/