* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2021 - 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 */
/* Functions concerned with rewriting headers */
Returns: fully-qualified address
*/
-uschar *
-rewrite_address_qualify(uschar *s, BOOL is_recipient)
+const uschar *
+rewrite_address_qualify(const uschar *s, BOOL is_recipient)
{
-return (parse_find_at(s) != NULL)? s :
- string_sprintf("%s@%s", s,
- is_recipient? qualify_domain_recipient : qualify_domain_sender);
+return parse_find_at(s)
+ ? s : string_sprintf("%s@%s", s,
+ is_recipient ? qualify_domain_recipient : qualify_domain_sender);
}
rewritten address is returned, not just the active bit.
*/
-uschar *
-rewrite_one(uschar *s, int flag, BOOL *whole, BOOL add_header, uschar *name,
+const uschar *
+rewrite_one(const uschar *s, int flag, BOOL *whole, BOOL add_header, uschar *name,
rewrite_rule *rewrite_rules)
{
-uschar *yield = s;
-uschar *subject = s;
-uschar *domain = NULL;
+const uschar *yield = s;
+const uschar *subject = s;
+const uschar *domain = NULL;
BOOL done = FALSE;
int rule_number = 1;
int yield_start = 0, yield_end = 0;
if (whole) *whole = FALSE;
-/* Scan the rewriting rules */
+/* Scan the rewriting rules, ignoring any without matching flag */
for (rewrite_rule * rule = rewrite_rules;
rule && !done;
- rule_number++, rule = rule->next)
+ rule_number++, rule = rule->next) if (rule->flags & flag)
{
int start, end, pdomain;
int count = 0;
- uschar *save_localpart;
- const uschar *save_domain;
- uschar *error, *new, *newparsed;
-
- /* Ensure that the flag matches the flags in the rule. */
-
- if (!(rule->flags & flag)) continue;
+ const uschar * save_localpart;
+ const uschar * save_domain;
+ uschar * error, * new;
+ const uschar * newparsed;
/* Come back here for a repeat after a successful rewrite. We do this
only so many times. */
if (flag & rewrite_smtp)
{
- uschar *key = expand_string(rule->key);
+ BOOL textonly_re;
+ const uschar * key = expand_string_2(rule->key, &textonly_re);
if (!key)
{
if (!f.expand_string_forcedfail)
"checking for SMTP rewriting: %s", rule->key, expand_string_message);
continue;
}
- if (match_check_string(subject, key, 0, TRUE, FALSE, FALSE, NULL) != OK)
+ if (match_check_string(subject, key, 0,
+ textonly_re ? MCS_CACHEABLE | MCS_PARTIAL : MCS_PARTIAL, NULL) != OK)
continue;
new = expand_string(rule->replacement);
}
else
{
- if (!domain) domain = Ustrrchr(subject, '@') + 1;
+ if (!domain) domain = CUstrrchr(subject, '@') + 1;
/* Use the general function for matching an address against a list (here
just one item, so use the "impossible value" separator UCHAR_MAX+1). */
save_domain = deliver_domain;
/* We have subject pointing to "localpart@domain" and domain pointing to
- the domain. Temporarily terminate the local part so that it can be
- set up as an expansion variable */
+ the domain. Split into local part and domain so that it can be set up as
+ an expansion variable */
- domain[-1] = 0;
- deliver_localpart = subject;
+ deliver_localpart = US string_copyn(subject, domain-subject-1);
deliver_domain = domain;
new = expand_string(rule->replacement);
- domain[-1] = '@';
deliver_localpart = save_localpart;
deliver_domain = save_domain;
}
Returns: possibly rewritten address
*/
-uschar *
-rewrite_address(uschar *s, BOOL is_recipient, BOOL add_header,
+const uschar *
+rewrite_address(const uschar *s, BOOL is_recipient, BOOL add_header,
rewrite_rule *rewrite_rules, int existflags)
{
-int flag = is_recipient? rewrite_envto : rewrite_envfrom;
+int flag = is_recipient ? rewrite_envto : rewrite_envfrom;
+
s = rewrite_address_qualify(s, is_recipient);
-if ((existflags & flag) != 0)
+if (existflags & flag)
{
- uschar *new = rewrite_one(s, flag, NULL, add_header, is_recipient?
+ const uschar *new = rewrite_one(s, flag, NULL, add_header, is_recipient?
US"original-recipient" : US"sender", rewrite_rules);
if (new != s) s = new;
}
rewrite_rule *rewrite_rules, int existflags, BOOL replace)
{
int lastnewline = 0;
-header_line *newh = NULL;
+header_line * newh = NULL;
rmark function_reset_point = store_mark();
-uschar *s = Ustrchr(h->text, ':') + 1;
-while (isspace(*s)) s++;
+uschar * s = Ustrchr(h->text, ':') + 1;
-DEBUG(D_rewrite)
- debug_printf("rewrite_one_header: type=%c:\n %s", h->type, h->text);
+Uskip_whitespace(&s);
+
+DEBUG(D_rewrite) /* The header text includes the trailing newline */
+ debug_printf_indent("rewrite_one_header: type=%c:\n %s", h->type, h->text);
f.parse_allow_group = TRUE; /* Allow group syntax */
while (*s)
{
- uschar *sprev;
- uschar *ss = parse_find_address_end(s, FALSE);
- uschar *recipient, *new, *errmess;
+ uschar * sprev = s;
+ uschar * ss = parse_find_address_end(s, FALSE), * ss1 = ss;
+ uschar * recipient, * new;
rmark loop_reset_point = store_mark();
+ uschar * errmess = NULL;
BOOL changed = FALSE;
- int terminator = *ss;
+ uschar terminator = *ss;
int start, end, domain;
+ /* If we hit the end of the header, trim trailing newline and whitespace */
+
+ if (!terminator)
+ {
+ while (ss1 > s && isspace(ss1[-1])) ss1--;
+ terminator = *ss1;
+ }
+
/* Temporarily terminate the string at this point, and extract the
operative address within. Then put back the terminator and prepare for
the next address, saving the start of the old one. */
- *ss = 0;
- recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE);
- *ss = terminator;
- sprev = s;
- s = ss + (terminator? 1:0);
- while (isspace(*s)) s++;
+ *ss1 = '\0';
+ recipient = parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
+ *ss1 = terminator;
+ s = ss + (*ss ? 1 : 0);
+ Uskip_whitespace(&s);
/* There isn't much we can do for syntactic disasters at this stage.
- Pro tem (possibly for ever) ignore them. */
+ Pro tem (possibly for ever) ignore them.
+ If we got nothing, then there was any sort of error: non-parsable address,
+ empty address, overlong addres. Sometimes the result matters, sometimes not.
+ It seems this function is called for *any* header we see. */
if (!recipient)
{
+ /* Log unparesable addresses in the header. Slightly ugly because a
+ null output from the extract can also result from a header without an
+ address, "To: undisclosed recpients:;" being the classic case. Ignore
+ this one and carry on. */
+
+ if (Ustrcmp(errmess, "empty address") != 0)
+ log_write(0, LOG_MAIN, "qualify/rewrite: %s", errmess);
+
loop_reset_point = store_reset(loop_reset_point);
continue;
}
as abc@xyz, which the DNS lookup turns into abc@xyz.foo.com). However, if no
change is made here, don't bother carrying on. */
- if (routed_old != NULL)
+ if (routed_old)
{
if (domain <= 0 || strcmpic(recipient+domain, routed_old) != 0) continue;
recipient[domain-1] = 0;
{
BOOL is_recipient =
(flag & (rewrite_sender | rewrite_from | rewrite_replyto)) == 0;
- new = rewrite_address_qualify(recipient, is_recipient);
+ /* deconst ok as recipient was notconst */
+ new = US rewrite_address_qualify(recipient, is_recipient);
changed = (new != recipient);
recipient = new;
"whole" flag set, adjust the pointers so that the whole address gets
replaced, except possibly a final \n. */
- if ((existflags & flag) != 0)
+ if (existflags & flag)
{
BOOL whole;
- new = rewrite_one(recipient, flag, &whole, FALSE, NULL, rewrite_rules);
+ /* deconst ok as recipient was notconst */
+ new = US rewrite_one(recipient, flag, &whole, FALSE, NULL, rewrite_rules);
if (new != recipient)
{
changed = TRUE;
point, because we may have a rewritten line from a previous time round the
loop. */
- if (!changed) loop_reset_point = store_reset(loop_reset_point);
+ if (!changed)
+ loop_reset_point = store_reset(loop_reset_point);
/* If the address has changed, create a new header containing the
rewritten address. We do not need to set the chain pointers at this
int oldlen = end - start;
header_line * prev = newh ? newh : h;
- uschar * newt = store_get_perm(prev->slen - oldlen + newlen + 4, TRUE);
+ uschar * newt = store_get_perm(prev->slen - oldlen + newlen + 4, GET_TAINTED);
uschar * newtstart = newt;
int type = prev->type;
store_reset(function_reset_point);
function_reset_point = store_mark();
- newh = store_get(sizeof(header_line), FALSE);
+ newh = store_get(sizeof(header_line), GET_UNTAINTED);
newh->type = type;
newh->slen = slen;
newh->text = string_copyn(newtstart, slen);
/* Set up for scanning the rest of the header */
s = newh->text + remlen;
- DEBUG(D_rewrite) debug_printf("remainder: %s", (*s == 0)? US"\n" : s);
+ DEBUG(D_rewrite) debug_printf("remainder: %s", *s ? s : US"\n");
}
}
/* If a rewrite happened and "replace" is true, put the new header into the
chain following the old one, and mark the old one as replaced. */
-if (newh != NULL && replace)
+if (newh && replace)
{
newh->next = h->next;
- if (newh->next == NULL) header_last = newh;
+ if (!newh->next) header_last = newh;
h->type = htype_old;
h->next = newh;
}
Returns: nothing
*/
-void rewrite_test(uschar *s)
+void
+rewrite_test(const uschar *s)
{
uschar *recipient, *error;
int start, end, domain;
if ((rewrite_existflags & rewrite_smtp) != 0)
{
- uschar *new = rewrite_one(s, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
- US"", global_rewrite_rules);
+ const uschar * new = rewrite_one(s, rewrite_smtp|rewrite_smtp_sender, NULL,
+ FALSE, US"", global_rewrite_rules);
if (new != s)
{
if (*new == 0)
{
BOOL whole = FALSE;
int flag = 1 << i;
- uschar *new = rewrite_one(recipient, flag, &whole, FALSE, US"",
+ const uschar * new = rewrite_one(recipient, flag, &whole, FALSE, US"",
global_rewrite_rules);
printf("%s: ", rrname[i]);
if (*new == 0)