* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* 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
[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
#ifdef EXPERIMENTAL_DCC
[ACLC_DCC] = { US"dcc", TRUE, FALSE,
- (unsigned int)
+ (unsigned)
~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
#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
[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
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
#ifdef WITH_CONTENT_SCAN
[ACLC_MALWARE] = { US"malware", TRUE, FALSE,
- (unsigned int)
+ (unsigned)
~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
#ifdef WITH_CONTENT_SCAN
[ACLC_REGEX] = { US"regex", TRUE, FALSE,
- (unsigned int)
+ (unsigned)
~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
ACL_BIT_PRDR |
#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
#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
#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,
#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
};
{
*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 = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
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;
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)
/* 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 *
*************************************************/
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:
break;
}
return ERROR;
-#endif
+#endif /*I18N*/
+#ifndef DISABLE_WELLKNOWN
+ case CONTROL_WELLKNOWN:
+ rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL;
+ break;
+#endif
}
break;
}
}
s++;
}
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
if (logbits == 0) logbits = LOG_MAIN;
log_write(0, logbits, "%s", string_printing(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.) */
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;
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);
}