summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2012-07-01 16:01:29 +0100
committerJeremy Harris <jgh146exb@wizmail.org>2012-07-01 16:01:29 +0100
commit3e8abda0fa92b78c4a3dfbad940b12fc90c241e3 (patch)
tree856bfe57a0869370207b86139455ce3a8eb51303
parentad0192a86e59a2c3cef2945d68dc22e52258d085 (diff)
parentbef3ea7f5de507f4eda7f32ac767ec6ac0441d57 (diff)
Merge branch 'acl'
-rw-r--r--doc/doc-docbook/spec.xfpt37
-rw-r--r--doc/doc-txt/ChangeLog3
-rw-r--r--doc/doc-txt/NewStuff9
-rw-r--r--src/src/acl.c64
-rw-r--r--src/src/expand.c144
-rw-r--r--src/src/functions.h1
-rw-r--r--src/src/globals.c10
-rw-r--r--src/src/globals.h2
-rw-r--r--src/src/macros.h4
-rw-r--r--test/confs/000215
-rw-r--r--test/scripts/0000-Basic/000221
-rw-r--r--test/stdout/000221
12 files changed, 313 insertions, 18 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index f23c42afb..eb359d088 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -8758,6 +8758,23 @@ string easier to understand.
This item inserts &"basic"& header lines. It is described with the &%header%&
expansion item below.
+
+.vitem "&*${acl{*&<&'name'&>&*}{*&<&'arg'&>&*}...}*&"
+.cindex "expansion" "calling an acl"
+.cindex "&%acl%&" "call from expansion"
+The name and zero to nine argument strings are first expanded separately. The expanded
+arguments are assigned to the variables &$acl_arg1$& to &$acl_arg9$& in order.
+Any unused are made empty. The variable &$acl_narg$& is set to the number of
+arguments. The named ACL (see chapter &<<CHAPACL>>&) is called
+and may use the variables; if another acl expansion is used the values
+are overwritten. If the ACL sets
+a value using a "message =" modifier and returns accept or deny, the value becomes
+the result of the expansion.
+If no message was set and the ACL returned accept or deny
+the value is an empty string.
+If the ACL returned defer the result is a forced-fail. Otherwise the expansion fails.
+
+
.vitem "&*${dlfunc{*&<&'file'&>&*}{*&<&'function'&>&*}{*&<&'arg'&>&*}&&&
{*&<&'arg'&>&*}...}*&"
.cindex &%dlfunc%&
@@ -10043,6 +10060,21 @@ In all cases, a relative comparator OP is testing if <&'string1'&> OP
10M, not if 10M is larger than &$message_size$&.
+.vitem &*acl&~{{*&<&'name'&>&*}{*&<&'arg1'&>&*}&&&
+ {*&<&'arg2'&>&*}...}*&
+.cindex "expansion" "calling an acl"
+.cindex "&%acl%&" "expansion condition"
+The name and zero to nine argument strings are first expanded separately. The expanded
+arguments are assigned to the variables &$acl_arg1$& to &$acl_arg9$& in order.
+Any unused are made empty. The variable &$acl_narg$& is set to the number of
+arguments. The named ACL (see chapter &<<CHAPACL>>&) is called
+and may use the variables; if another acl expansion is used the values
+are overwritten. If the ACL sets
+a value using a "message =" modifier the variable $value becomes
+the result of the expansion, otherwise it is empty.
+If the ACL returns accept the condition is true; if deny, false.
+If the ACL returns defer the result is a forced-fail.
+
.vitem &*bool&~{*&<&'string'&>&*}*&
.cindex "expansion" "boolean parsing"
.cindex "&%bool%& expansion condition"
@@ -27301,6 +27333,7 @@ The conditions are as follows:
.vitem &*acl&~=&~*&<&'name&~of&~acl&~or&~ACL&~string&~or&~file&~name&~'&>
.cindex "&ACL;" "nested"
.cindex "&ACL;" "indirect"
+.cindex "&ACL;" "arguments"
.cindex "&%acl%& ACL condition"
The possible values of the argument are the same as for the
&%acl_smtp_%&&'xxx'& options. The named or inline ACL is run. If it returns
@@ -27310,6 +27343,10 @@ condition is on a &%warn%& verb. In that case, a &"defer"& return makes the
condition false. This means that further processing of the &%warn%& verb
ceases, but processing of the ACL continues.
+If the argument is a named ACL, up to nine space-separated optional values
+can be appended; they appear in $acl_arg1 to $acl_arg9, and $acl_narg is set
+to the count of values. The name and values are expanded separately.
+
If the nested &%acl%& returns &"drop"& and the outer condition denies access,
the connection is dropped. If it returns &"discard"&, the verb must be
&%accept%& or &%discard%&, and the action is taken immediately &-- no further
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index d1beab6e0..a9c9abed8 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -49,6 +49,9 @@ PP/09 Add gnutls_enable_pkcs11 option.
PP/10 Let Linux makefile inherit CFLAGS/CFLAGS_DYNAMIC.
Pulled from Debian 30_dontoverridecflags.dpatch by Andreas Metzler.
+JH/04 Add expansion item ${acl {name}{arg}...}, expansion condition
+ "acl {{name}{arg}...}", and optional args on acl condition
+ "acl = name arg..."
Exim version 4.80
-----------------
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index c56256bdd..53d533dea 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -96,6 +96,15 @@ Version 4.81
through, thus breakage. So we explicitly inhibit the PKCS11 initialisation
unless this new option is set.
+10. The "acl = name" condition on an ACL now supports optional arguments.
+ New expansion item "${acl {name}{arg}...}" and expansion condition
+ "acl {{name}{arg}...}" are added. In all cases up to nine arguments
+ can be used, appearing in $acl_arg1 to $acl_arg9 for the called ACL.
+ Variable $acl_narg contains the number of arguments. If the ACL sets
+ a "message =" value this becomes the result of the expansion item,
+ or the value of $value for the expansion condition. If the ACL returns
+ accept the expansion condition is true; if reject, false. A defer
+ return results in a forced fail.
Version 4.80
------------
diff --git a/src/src/acl.c b/src/src/acl.c
index a721665d4..5cd0c3507 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -236,7 +236,7 @@ at the outer level. In the other cases, expansion already occurs in the
checking functions. */
static uschar cond_expand_at_top[] = {
- TRUE, /* acl */
+ FALSE, /* acl */
TRUE, /* add_header */
FALSE, /* authenticated */
#ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -696,8 +696,8 @@ static uschar *ratelimit_option_string[] = {
/* Enable recursion between acl_check_internal() and acl_check_condition() */
-static int acl_check_internal(int, address_item *, uschar *, int, uschar **,
- uschar **);
+static int acl_check_wargs(int, address_item *, uschar *, int, uschar **,
+ uschar **);
/*************************************************
@@ -2785,14 +2785,14 @@ for (; cb != NULL; cb = cb->next)
"discard" verb. */
case ACLC_ACL:
- rc = acl_check_internal(where, addr, arg, level+1, user_msgptr, log_msgptr);
- if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
- {
- *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
- "\"%s\" command (only allowed with \"accept\" or \"discard\")",
- verbs[verb]);
- return ERROR;
- }
+ rc = acl_check_wargs(where, addr, arg, level+1, user_msgptr, log_msgptr);
+ if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
+ {
+ *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
+ "\"%s\" command (only allowed with \"accept\" or \"discard\")",
+ verbs[verb]);
+ return ERROR;
+ }
break;
case ACLC_AUTHENTICATED:
@@ -3863,6 +3863,48 @@ return FAIL;
}
+
+
+/* Same args as acl_check_internal() above, but the string s is
+the name of an ACL followed optionally by up to 9 space-separated arguments.
+The name and args are separately expanded. Args go into $acl_arg globals. */
+static int
+acl_check_wargs(int where, address_item *addr, uschar *s, int level,
+ uschar **user_msgptr, uschar **log_msgptr)
+{
+uschar * tmp;
+uschar * tmp_arg[9]; /* must match acl_arg[] */
+uschar * name;
+int i;
+
+if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp)))
+ goto bad;
+
+for (i = 0; i < 9; i++)
+ {
+ while (*s && isspace(*s)) s++;
+ if (!*s) break;
+ if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp)))
+ {
+ tmp = name;
+ goto bad;
+ }
+ }
+acl_narg = i;
+for (i = 0; i < acl_narg; i++) acl_arg[i] = tmp_arg[i];
+while (i < 9) acl_arg[i++] = NULL;
+
+return acl_check_internal(where, addr, name, level, user_msgptr, log_msgptr);
+
+bad:
+if (expand_string_forcedfail) return ERROR;
+*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
+ tmp, expand_string_message);
+return search_find_defer?DEFER:ERROR;
+}
+
+
+
/*************************************************
* Check access using an ACL *
*************************************************/
diff --git a/src/src/expand.c b/src/src/expand.c
index 965842611..767e4771a 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -102,6 +102,7 @@ bcrypt ({CRYPT}$2a$).
alphabetical order. */
static uschar *item_table[] = {
+ US"acl",
US"dlfunc",
US"extract",
US"filter",
@@ -124,6 +125,7 @@ static uschar *item_table[] = {
US"tr" };
enum {
+ EITEM_ACL,
EITEM_DLFUNC,
EITEM_EXTRACT,
EITEM_FILTER,
@@ -247,6 +249,7 @@ static uschar *cond_table[] = {
US"==", /* Backward compatibility */
US">",
US">=",
+ US"acl",
US"and",
US"bool",
US"bool_lax",
@@ -292,6 +295,7 @@ enum {
ECOND_NUM_EE,
ECOND_NUM_G,
ECOND_NUM_GE,
+ ECOND_ACL,
ECOND_AND,
ECOND_BOOL,
ECOND_BOOL_LAX,
@@ -390,6 +394,16 @@ enum {
static var_entry var_table[] = {
/* WARNING: Do not invent variables whose names start acl_c or acl_m because
they will be confused with user-creatable ACL variables. */
+ { "acl_arg1", vtype_stringptr, &acl_arg[0] },
+ { "acl_arg2", vtype_stringptr, &acl_arg[1] },
+ { "acl_arg3", vtype_stringptr, &acl_arg[2] },
+ { "acl_arg4", vtype_stringptr, &acl_arg[3] },
+ { "acl_arg5", vtype_stringptr, &acl_arg[4] },
+ { "acl_arg6", vtype_stringptr, &acl_arg[5] },
+ { "acl_arg7", vtype_stringptr, &acl_arg[6] },
+ { "acl_arg8", vtype_stringptr, &acl_arg[7] },
+ { "acl_arg9", vtype_stringptr, &acl_arg[8] },
+ { "acl_narg", vtype_int, &acl_narg },
{ "acl_verify_message", vtype_stringptr, &acl_verify_message },
{ "address_data", vtype_stringptr, &deliver_address_data },
{ "address_file", vtype_stringptr, &address_file },
@@ -1822,6 +1836,40 @@ if (Ustrncmp(name, "acl_", 4) == 0)
+/*
+Load args from sub array to globals, and call acl_check().
+
+Returns: OK access is granted by an ACCEPT verb
+ DISCARD access is granted by a DISCARD verb
+ FAIL access is denied
+ FAIL_DROP access is denied; drop the connection
+ DEFER can't tell at the moment
+ ERROR disaster
+*/
+static int
+eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
+{
+int i;
+uschar *dummy_log_msg;
+
+for (i = 1; i < nsub && sub[i]; i++)
+ acl_arg[i-1] = sub[i];
+acl_narg = i-1;
+while (i < nsub)
+ acl_arg[i++ - 1] = NULL;
+
+DEBUG(D_expand)
+ debug_printf("expanding: acl: %s arg: %s%s\n",
+ sub[0],
+ acl_narg>0 ? sub[1] : US"<none>",
+ acl_narg>1 ? " +more" : "");
+
+return acl_check(ACL_WHERE_EXPANSION, NULL, sub[0], user_msgp, &dummy_log_msg);
+}
+
+
+
+
/*************************************************
* Read and evaluate a condition *
*************************************************/
@@ -1849,7 +1897,7 @@ int i, rc, cond_type, roffset;
int_eximarith_t num[2];
struct stat statbuf;
uschar name[256];
-uschar *sub[4];
+uschar *sub[10];
const pcre *re;
const uschar *rerror;
@@ -2054,12 +2102,65 @@ switch(cond_type)
return s;
+ /* call ACL (in a conditional context). Accept true, deny false.
+ Defer is a forced-fail. Anything set by message= goes to $value.
+ Up to ten parameters are used; we use the braces round the name+args
+ like the saslauthd condition does, to permit a variable number of args.
+ See also the expansion-item version EITEM_ACL and the traditional
+ acl modifier ACLC_ACL.
+ */
+
+ case ECOND_ACL:
+ /* ${if acl {{name}{arg1}{arg2}...} {yes}{no}} */
+ {
+ uschar *nameargs;
+ uschar *user_msg;
+ BOOL cond = FALSE;
+ int size = 0;
+ int ptr = 0;
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START;
+
+ switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+ &s, yield == NULL, TRUE, US"acl"))
+ {
+ case 1: expand_string_message = US"too few arguments or bracketing "
+ "error for acl";
+ case 2:
+ case 3: return NULL;
+ }
+
+ if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+ {
+ case OK:
+ cond = TRUE;
+ case FAIL:
+ lookup_value = NULL;
+ if (user_msg)
+ {
+ lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+ lookup_value[ptr] = '\0';
+ }
+ *yield = cond;
+ break;
+
+ case DEFER:
+ expand_string_forcedfail = TRUE;
+ default:
+ expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+ return NULL;
+ }
+ return s;
+ }
+
+
/* saslauthd: does Cyrus saslauthd authentication. Four parameters are used:
${if saslauthd {{username}{password}{service}{realm}} {yes}[no}}
However, the last two are optional. That is why the whole set is enclosed
- in their own set or braces. */
+ in their own set of braces. */
case ECOND_SASLAUTHD:
#ifndef CYRUS_SASLAUTHD_SOCKET
@@ -3641,6 +3742,44 @@ while (*s != 0)
switch(item_type)
{
+ /* Call an ACL from an expansion. We feed data in via $acl_arg1 - $acl_arg9.
+ If the ACL returns accept or reject we return content set by "message ="
+ There is currently no limit on recursion; this would have us call
+ acl_check_internal() directly and get a current level from somewhere.
+ See also the acl expansion condition ECOND_ACL and the traditional
+ acl modifier ACLC_ACL.
+ */
+
+ case EITEM_ACL:
+ /* ${acl {name} {arg1}{arg2}...} */
+ {
+ uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
+ uschar *user_msg;
+
+ switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+ if (skipping) continue;
+
+ switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+ {
+ case OK:
+ case FAIL:
+ if (user_msg)
+ yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+ continue;
+
+ case DEFER:
+ expand_string_forcedfail = TRUE;
+ default:
+ expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+ goto EXPAND_FAILED;
+ }
+ }
+
/* Handle conditionals - preserve the values of the numerical expansion
variables in case they get changed by a regular expression match in the
condition. If not, they retain their external settings. At the end
@@ -5533,7 +5672,6 @@ while (*s != 0)
goto EXPAND_FAILED;
}
- if (skipping) continue;
list = ((namedlist_block *)(t->data.ptr))->string;
while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
diff --git a/src/src/functions.h b/src/src/functions.h
index 09f7ab95c..bc791fcdb 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -49,6 +49,7 @@ extern BOOL tls_openssl_options_parse(uschar *, long *);
extern acl_block *acl_read(uschar *(*)(void), uschar **);
extern int acl_check(int, uschar *, uschar *, uschar **, uschar **);
+
extern tree_node *acl_var_create(uschar *);
extern void acl_var_write(uschar *, uschar *, void *);
extern uschar *auth_b64encode(uschar *, int);
diff --git a/src/src/globals.c b/src/src/globals.c
index 1faf75cda..21122f0f9 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -187,13 +187,15 @@ int address_expansions_count = sizeof(address_expansions)/sizeof(uschar **);
header_line *acl_added_headers = NULL;
tree_node *acl_anchor = NULL;
+uschar *acl_arg[9] = {NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL};
+int acl_narg = 0;
uschar *acl_not_smtp = NULL;
#ifdef WITH_CONTENT_SCAN
uschar *acl_not_smtp_mime = NULL;
#endif
uschar *acl_not_smtp_start = NULL;
-
uschar *acl_smtp_auth = NULL;
uschar *acl_smtp_connect = NULL;
uschar *acl_smtp_data = NULL;
@@ -241,7 +243,8 @@ uschar *acl_wherenames[] = { US"RCPT",
US"NOTQUIT",
US"QUIT",
US"STARTTLS",
- US"VRFY"
+ US"VRFY",
+ US"expansion"
};
uschar *acl_wherecodes[] = { US"550", /* RCPT */
@@ -261,7 +264,8 @@ uschar *acl_wherecodes[] = { US"550", /* RCPT */
US"0", /* NOTQUIT; not relevant */
US"0", /* QUIT; not relevant */
US"550", /* STARTTLS */
- US"252" /* VRFY */
+ US"252", /* VRFY */
+ US"0" /* unknown; not relevant */
};
BOOL active_local_from_check = FALSE;
diff --git a/src/src/globals.h b/src/src/globals.h
index 27c87b141..783eb7ba3 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -135,6 +135,8 @@ extern uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT];
extern BOOL accept_8bitmime; /* Allow *BITMIME incoming */
extern header_line *acl_added_headers; /* Headers added by an ACL */
extern tree_node *acl_anchor; /* Tree of named ACLs */
+extern uschar *acl_arg[9]; /* Argument to ACL call */
+extern int acl_narg; /* Number of arguments to ACL call */
extern uschar *acl_not_smtp; /* ACL run for non-SMTP messages */
#ifdef WITH_CONTENT_SCAN
extern uschar *acl_not_smtp_mime; /* For MIME parts of ditto */
diff --git a/src/src/macros.h b/src/src/macros.h
index b17a80e10..d25071aae 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -820,7 +820,9 @@ enum { ACL_WHERE_RCPT, /* Some controls are for RCPT only */
ACL_WHERE_NOTQUIT,
ACL_WHERE_QUIT,
ACL_WHERE_STARTTLS,
- ACL_WHERE_VRFY
+ ACL_WHERE_VRFY,
+
+ ACL_WHERE_EXPANSION /* Currently used by a ${acl:name} expansion */
};
/* Situations for spool_write_header() */
diff --git a/test/confs/0002 b/test/confs/0002
index 6983fd87f..eb473e6b9 100644
--- a/test/confs/0002
+++ b/test/confs/0002
@@ -44,4 +44,19 @@ check_data:
warn logwrite = Subject is: "$h_subject:"
deny message = reply_address=<$reply_address>
+a_ret:
+ accept message = ($acl_narg) [$acl_arg1] [$acl_arg2]
+
+a_none:
+ accept
+
+a_deny:
+ deny message = ($acl_narg) [$acl_arg1] [$acl_arg2]
+
+a_defer:
+ defer
+
+a_sub:
+ require acl = a_deny "new arg1" $acl_arg1
+
# End
diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002
index 652891615..3e2421b1e 100644
--- a/test/scripts/0000-Basic/0002
+++ b/test/scripts/0000-Basic/0002
@@ -86,6 +86,21 @@ reduce: ${reduce {<\x7f 1\x7f2\177 3}{0}{${eval:$value+$item}}}
# Operators
+acl: ${acl
+acl: ${acl}
+acl: ${acl {a_nosuch}}
+acl: ${acl {a_ret}}
+acl: ${acl {a_ret}{person@dom.ain}}
+acl: ${acl {a_ret}{firstarg}{secondarg}}
+acl: ${acl {a_ret}{arg with spaces}}
+acl: ${acl {a_none}}
+acl: ${acl {a_none}{person@dom.ain}}
+acl: ${acl {a_deny}}
+acl: ${acl {a_deny}{person@dom.ain}}
+acl: ${acl {a_defer}}
+acl: ${acl {a_sub}{top_arg_1}{top_arg_2}{top_arg_3}}
+acl: ${reduce {1:2:3:4} {} {$value ${acl {a_ret}{$item}}}}
+
addrss: ${address:local-part@dom.ain}
addrss: ${address:Exim Person <local-part@dom.ain> (that's me)}
domain: ${domain:local-part@dom.ain}
@@ -408,6 +423,12 @@ first_delivery: ${if first_delivery{y}{n}}
queue_running after or: ${if or{{eq {0}{0}}{queue_running}}{y}{n}}
first_delivery after or: ${if or{{eq {0}{0}}{first_delivery}}{y}{n}}
+# acl expansion condition
+acl if: ${if acl {{a_ret}} {Y:$value}{N:$value}}
+acl if: ${if acl {{a_ret}{argY}} {Y:$value}{N:$value}}
+acl if: ${if acl {{a_deny}{argN}{arg2}} {Y:$value}{N:$value}}
+acl if: ${if acl {{a_defer}{argN}{arg2}} {Y:$value}{N:$value}}
+
# Default values for both if strings
\${if eq{1}{1}} >${if eq{1}{1}}<
diff --git a/test/stdout/0002 b/test/stdout/0002
index de67f99fc..b4b96dfb5 100644
--- a/test/stdout/0002
+++ b/test/stdout/0002
@@ -78,6 +78,21 @@
>
> # Operators
>
+> Failed: missing or misplaced { or }
+> Failed: missing or misplaced { or }
+> Failed: error from acl "a_nosuch"
+> acl: (0) [] []
+> acl: (1) [person@dom.ain] []
+> acl: (2) [firstarg] [secondarg]
+> acl: (1) [arg with spaces] []
+> acl:
+> acl:
+> acl: (0) [] []
+> acl: (1) [person@dom.ain] []
+> Failed: error from acl "a_defer"
+> acl: (2) [new arg1] [top_arg_1]
+> acl: (1) [1] [] (1) [2] [] (1) [3] [] (1) [4] []
+>
> addrss: local-part@dom.ain
> addrss: local-part@dom.ain
> domain: dom.ain
@@ -377,6 +392,12 @@
> queue_running after or: y
> first_delivery after or: y
>
+> # acl expansion condition
+> acl if: Y:(0) [] []
+> acl if: Y:(1) [argY] []
+> acl if: N:(2) [argN] [arg2]
+> Failed: error from acl "a_defer"
+>
> # Default values for both if strings
>
> ${if eq{1}{1}} >true<