X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/1ba28e2b955b005ce4825fec792df17f75a8de1e..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/filter.c diff --git a/src/src/filter.c b/src/src/filter.c index fd2f7ced1..82a9122c6 100644 --- a/src/src/filter.c +++ b/src/src/filter.c @@ -1,11 +1,11 @@ -/* $Cambridge: exim/src/src/filter.c,v 1.15 2009/11/16 19:50:37 nm4 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* 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-only */ /* Code for mail filtering functions. */ @@ -28,7 +28,7 @@ union argtypes { struct condition_block *c; struct filter_cmd *f; int i; - uschar *u; + const uschar *u; }; /* Local structures used in this module */ @@ -52,7 +52,7 @@ typedef struct condition_block { /* Miscellaneous other declarations */ static uschar **error_pointer; -static uschar *log_filename; +static const uschar *log_filename; static int filter_options; static int line_number; static int expect_endif; @@ -68,7 +68,7 @@ static BOOL noerror_force; enum { had_neither, had_else, had_elif, had_endif }; -static BOOL read_command_list(uschar **, filter_cmd ***, BOOL); +static BOOL read_command_list(const uschar **, filter_cmd ***, BOOL); /* The string arguments for the mail command. The header line ones (that are @@ -93,7 +93,7 @@ static const char *mailargs[] = { /* "to" must be first, and */ /* The count of string arguments */ -#define MAILARGS_STRING_COUNT (sizeof(mailargs)/sizeof(uschar *)) +#define MAILARGS_STRING_COUNT (nelem(mailargs)) /* The count of string arguments that are actually passed over as strings (once_repeat is converted to an int). */ @@ -187,7 +187,7 @@ static const char *cond_words[] = { "match", "matches"}; -static int cond_word_count = (sizeof(cond_words)/sizeof(uschar *)); +static int cond_word_count = nelem(cond_words); static int cond_types[] = { cond_BEGINS, cond_BEGINS, cond_CONTAINS, cond_CONTAINS, cond_ENDS, cond_ENDS, cond_IS, cond_MATCHES, cond_MATCHES, @@ -209,7 +209,7 @@ static const char *command_list[] = { "noerror", "pipe", "save", "seen", "testprint", "unseen", "vacation" }; -static int command_list_count = sizeof(command_list)/sizeof(uschar *); +static int command_list_count = nelem(command_list); /* This table contains the number of expanded arguments in the bottom 4 bits. If the top bit is set, it means that the default for the command is "seen". */ @@ -253,8 +253,8 @@ Arguments: Returns: pointer to next non-whitespace character */ -static uschar * -nextsigchar(uschar *ptr, BOOL comment_allowed) +static const uschar * +nextsigchar(const uschar *ptr, BOOL comment_allowed) { for (;;) { @@ -291,8 +291,8 @@ Arguments Returns: pointer to the next significant character after the word */ -static uschar * -nextword(uschar *ptr, uschar *buffer, int size, BOOL bracket) +static const uschar * +nextword(const uschar *ptr, uschar *buffer, int size, BOOL bracket) { uschar *bp = buffer; while (*ptr != 0 && !isspace(*ptr) && @@ -327,13 +327,13 @@ Arguments: Returns: the next significant character after the item */ -static uschar * -nextitem(uschar *ptr, uschar *buffer, int size, BOOL bracket) +static const uschar * +nextitem(const uschar *ptr, uschar *buffer, int size, BOOL bracket) { uschar *bp = buffer; if (*ptr != '\"') return nextword(ptr, buffer, size, bracket); -while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n') +while (*++ptr && *ptr != '\"' && *ptr != '\n') { if (bp - buffer >= size - 1) { @@ -346,7 +346,7 @@ while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n') { if (isspace(ptr[1])) /* \NL ignored */ { - uschar *p = ptr + 1; + const uschar *p = ptr + 1; while (*p != '\n' && isspace(*p)) p++; if (*p == '\n') { @@ -357,7 +357,7 @@ while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n') } } - *bp++ = string_interpret_escape(&ptr); + *bp++ = string_interpret_escape(CUSS &ptr); } } @@ -386,7 +386,7 @@ Returns: the number, or 0 on error (with *OK FALSE) */ static int -get_number(uschar *s, BOOL *ok) +get_number(const uschar *s, BOOL *ok) { int value, count; *ok = FALSE; @@ -417,8 +417,8 @@ Arguments: Returns: points to next character after "then" */ -static uschar * -read_condition(uschar *ptr, condition_block **cond, BOOL toplevel) +static const uschar * +read_condition(const uschar *ptr, condition_block **cond, BOOL toplevel) { uschar buffer[1024]; BOOL testfor = TRUE; @@ -435,9 +435,9 @@ for (;;) /* reaching the end of the input is an error. */ - if (*ptr == 0) + if (!*ptr) { - *error_pointer = string_sprintf("\"then\" missing at end of filter file"); + *error_pointer = US"\"then\" missing at end of filter file"; break; } @@ -478,7 +478,7 @@ for (;;) else { ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; /* "Then" at the start of a condition is an error */ @@ -499,7 +499,7 @@ for (;;) /* Build a condition block from the specific word. */ - c = store_get(sizeof(condition_block)); + c = store_get(sizeof(condition_block), GET_UNTAINTED); c->left.u = c->right.u = NULL; c->testfor = testfor; testfor = TRUE; @@ -519,17 +519,17 @@ for (;;) for (;;) { string_item *aa; - uschar *saveptr = ptr; + const uschar * saveptr = ptr; ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; if (Ustrcmp(buffer, "alias") != 0) { ptr = saveptr; break; } ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; - aa = store_get(sizeof(string_item)); + if (*error_pointer) break; + aa = store_get(sizeof(string_item), GET_UNTAINTED); aa->text = string_copy(buffer); aa->next = c->left.a; c->left.a = aa; @@ -542,7 +542,7 @@ for (;;) else if (Ustrcmp(buffer, "foranyaddress") == 0) { ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; if (*ptr != '(') { *error_pointer = string_sprintf("\"(\" expected after \"foranyaddress\" " @@ -554,18 +554,13 @@ for (;;) c->left.u = string_copy(buffer); ptr = read_condition(nextsigchar(ptr+1, TRUE), &(c->right.c), FALSE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; if (*ptr != ')') { *error_pointer = string_sprintf("expected \")\" in line %d of " "filter file", line_number); break; } - if (!testfor) - { - c->testfor = !c->testfor; - testfor = TRUE; - } ptr = nextsigchar(ptr+1, TRUE); } @@ -575,11 +570,11 @@ for (;;) else { int i; - uschar *isptr = NULL; + const uschar *isptr = NULL; c->left.u = string_copy(buffer); ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; /* Handle "does|is [not]", preserving the pointer after "is" in case it isn't that, but the form "is ". */ @@ -590,13 +585,13 @@ for (;;) if (buffer[0] == 'I') { c->type = cond_IS; isptr = ptr; } ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; if (strcmpic(buffer, US"not") == 0) { c->testfor = !c->testfor; - if (isptr != NULL) isptr = ptr; + if (isptr) isptr = ptr; ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; } } @@ -614,22 +609,19 @@ for (;;) if (i >= cond_word_count) { - if (isptr != NULL) - { - ptr = isptr; - } - else + if (!isptr) { *error_pointer = string_sprintf("unrecognized condition word \"%s\" " "near line %d of filter file", buffer, line_number); break; } + ptr = isptr; } /* Get the RH argument. */ ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; c->right.u = string_copy(buffer); } } @@ -664,18 +656,23 @@ for (;;) else { - uschar *saveptr = ptr; +// const uschar *saveptr = ptr; ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (*error_pointer != NULL) break; + if (*error_pointer) break; /* "Then" terminates a toplevel condition; otherwise a closing bracket has been omitted. Put a string terminator at the start of "then" so that reflecting the condition can be done when testing. */ + /*XXX This stops us doing a constification job in this file, unfortunately. + Comment it out and see if anything breaks. + With one addition down at DEFERFREEZEFAIL it passes the testsuite. */ if (Ustrcmp(buffer, "then") == 0) { - if (toplevel) *saveptr = 0; - else *error_pointer = string_sprintf("missing \")\" at end of " +// if (toplevel) *saveptr = 0; +// else + if (!toplevel) + *error_pointer = string_sprintf("missing \")\" at end of " "condition near line %d of filter file", line_number); break; } @@ -688,7 +685,7 @@ for (;;) else if (Ustrcmp(buffer, "and") == 0) { - condition_block *andc = store_get(sizeof(condition_block)); + condition_block * andc = store_get(sizeof(condition_block), GET_UNTAINTED); andc->parent = current_parent; andc->type = cond_and; andc->testfor = TRUE; @@ -706,24 +703,24 @@ for (;;) else if (Ustrcmp(buffer, "or") == 0) { - condition_block *orc = store_get(sizeof(condition_block)); - condition_block *or_parent = NULL; + condition_block * orc = store_get(sizeof(condition_block), GET_UNTAINTED); + condition_block * or_parent = NULL; - if (current_parent != NULL) + if (current_parent) { - while (current_parent->parent != NULL && + while (current_parent->parent && current_parent->parent->type == cond_and) current_parent = current_parent->parent; /* If the parent has a parent, it must be an "or" parent. */ - if (current_parent->parent != NULL) + if (current_parent->parent) or_parent = current_parent->parent; } orc->parent = or_parent; - if (or_parent == NULL) *cond = orc; else - or_parent->right.c = orc; + if (!or_parent) *cond = orc; + else or_parent->right.c = orc; orc->type = cond_or; orc->testfor = TRUE; orc->left.c = (current_parent == NULL)? c : current_parent; @@ -750,7 +747,7 @@ return nextsigchar(ptr, TRUE); /************************************************* -* Ouput the current indent * +* Output the current indent * *************************************************/ static void @@ -846,7 +843,7 @@ Returns: TRUE if command successfully read, else FALSE */ static BOOL -read_command(uschar **pptr, filter_cmd ***lastcmdptr) +read_command(const uschar **pptr, filter_cmd ***lastcmdptr) { int command, i, cmd_bit; filter_cmd *new, **newlastcmdptr; @@ -854,8 +851,8 @@ BOOL yield = TRUE; BOOL was_seen_or_unseen = FALSE; BOOL was_noerror = FALSE; uschar buffer[1024]; -uschar *ptr = *pptr; -uschar *saveptr; +const uschar *ptr = *pptr; +const uschar *saveptr; uschar *fmsg = NULL; /* Read the next word and find which command it is. Command words are normally @@ -864,20 +861,22 @@ terminated by white space, but there are two exceptions, which are the "if" and as brackets are allowed in conditions and users will expect not to require white space here. */ +*buffer = '\0'; /* compiler quietening */ + if (Ustrncmp(ptr, "if(", 3) == 0) { - Ustrcpy(buffer, "if"); + Ustrcpy(buffer, US"if"); ptr += 2; } else if (Ustrncmp(ptr, "elif(", 5) == 0) { - Ustrcpy(buffer, "elif"); + Ustrcpy(buffer, US"elif"); ptr += 4; } else { ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (*error_pointer != NULL) return FALSE; + if (*error_pointer) return FALSE; } for (command = 0; command < command_list_count; command++) @@ -916,11 +915,11 @@ switch (command) case testprint_command: ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*buffer == 0) + if (!*buffer) *error_pointer = string_sprintf("\"%s\" requires an argument " "near line %d of filter file", command_list[command], line_number); - if (*error_pointer != NULL) yield = FALSE; else + if (*error_pointer) yield = FALSE; else { union argtypes argument, second_argument; @@ -930,13 +929,13 @@ switch (command) { argument.u = string_copy(buffer); ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*buffer == 0 || Ustrcmp(buffer, "to") != 0) + if (!*buffer || Ustrcmp(buffer, "to") != 0) *error_pointer = string_sprintf("\"to\" expected in \"add\" command " "near line %d of filter file", line_number); else { ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*buffer == 0) + if (!*buffer) *error_pointer = string_sprintf("value missing after \"to\" " "near line %d of filter file", line_number); else second_argument.u = string_copy(buffer); @@ -960,7 +959,7 @@ switch (command) yield = FALSE; } - if (!system_filtering && second_argument.b != TRUE_UNSET) + if (!f.system_filtering && second_argument.b != TRUE_UNSET) { *error_pointer = string_sprintf("header addition and removal is " "available only in system filters: near line %d of filter file", @@ -972,7 +971,7 @@ switch (command) if (yield) { ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*buffer == 0) + if (!*buffer) *error_pointer = string_sprintf("value missing after \"add\", " "\"remove\", or \"charset\" near line %d of filter file", line_number); @@ -991,7 +990,7 @@ switch (command) if (command == logwrite_command) { int len = Ustrlen(buffer); - if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, "\n"); + if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, US"\n"); } argument.u = string_copy(buffer); @@ -1008,7 +1007,7 @@ switch (command) else if (command == deliver_command) { - uschar *save_ptr = ptr; + const uschar *save_ptr = ptr; ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); if (Ustrcmp(buffer, "errors_to") == 0) { @@ -1023,9 +1022,10 @@ switch (command) FALSE for logging commands, and it doesn't matter for testprint, as that doesn't change the "delivered" status. */ - if (*error_pointer != NULL) yield = FALSE; else + if (*error_pointer) yield = FALSE; + else { - new = store_get(sizeof(filter_cmd) + sizeof(union argtypes)); + new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), GET_UNTAINTED); new->next = NULL; **lastcmdptr = new; *lastcmdptr = &(new->next); @@ -1090,7 +1090,7 @@ switch (command) saveptr = ptr; ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*saveptr != '\"' && (*buffer == 0 || Ustrcmp(buffer, "text") != 0)) + if (*saveptr != '\"' && (!*buffer || Ustrcmp(buffer, "text") != 0)) { ptr = saveptr; fmsg = US""; @@ -1109,12 +1109,12 @@ switch (command) /* Finish has no arguments; fmsg defaults to NULL */ case finish_command: - new = store_get(sizeof(filter_cmd)); + new = store_get(sizeof(filter_cmd), GET_UNTAINTED); new->next = NULL; **lastcmdptr = new; *lastcmdptr = &(new->next); new->command = command; - new->seen = seen_force? seen_value : FALSE; + new->seen = seen_force ? seen_value : FALSE; new->args[0].u = fmsg; break; @@ -1133,10 +1133,10 @@ switch (command) /* Set up the command block for if */ - new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes)); + new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); new->next = NULL; **lastcmdptr = new; - *lastcmdptr = &(new->next); + *lastcmdptr = &new->next; new->command = command; new->seen = FALSE; new->args[0].u = NULL; @@ -1145,8 +1145,8 @@ switch (command) /* Read the condition */ - ptr = read_condition(ptr, &(new->args[0].c), TRUE); - if (*error_pointer != NULL) { yield = FALSE; break; } + ptr = read_condition(ptr, &new->args[0].c, TRUE); + if (*error_pointer) { yield = FALSE; break; } /* Read the commands to be obeyed if the condition is true */ @@ -1161,7 +1161,7 @@ switch (command) while (had_else_endif == had_elif) { filter_cmd *newnew = - store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes)); + store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); new->args[2].f = newnew; new = newnew; new->next = NULL; @@ -1171,8 +1171,8 @@ switch (command) new->args[1].u = new->args[2].u = NULL; new->args[3].u = ptr; - ptr = read_condition(ptr, &(new->args[0].c), TRUE); - if (*error_pointer != NULL) { yield = FALSE; break; } + ptr = read_condition(ptr, &new->args[0].c, TRUE); + if (*error_pointer) { yield = FALSE; break; } newlastcmdptr = &(new->args[1].f); if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) yield = FALSE; @@ -1214,10 +1214,10 @@ switch (command) case mail_command: case vacation_command: - new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes)); + new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes), GET_UNTAINTED); new->next = NULL; new->command = command; - new->seen = seen_force? seen_value : FALSE; + new->seen = seen_force ? seen_value : FALSE; new->noerror = noerror_force; for (i = 0; i < mailargs_total; i++) new->args[i].u = NULL; @@ -1228,13 +1228,10 @@ switch (command) for (;;) { - uschar *saveptr = ptr; + const uschar *saveptr = ptr; ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (*error_pointer != NULL) - { - yield = FALSE; - break; - } + if (*error_pointer) + { yield = FALSE; break; } /* Ensure "return" is followed by "message"; that's a complete option */ @@ -1284,11 +1281,8 @@ switch (command) /* Found keyword, read the data item */ ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*error_pointer != NULL) - { - yield = FALSE; - break; - } + if (*error_pointer) + { yield = FALSE; break; } else new->args[i].u = string_copy(buffer); } @@ -1297,18 +1291,18 @@ switch (command) if (command == vacation_command) { - if (new->args[mailarg_index_file].u == NULL) + if (!new->args[mailarg_index_file].u) { new->args[mailarg_index_file].u = string_copy(US".vacation.msg"); new->args[mailarg_index_expand].u = US""; /* not NULL => TRUE */ } - if (new->args[mailarg_index_log].u == NULL) + if (!new->args[mailarg_index_log].u) new->args[mailarg_index_log].u = string_copy(US".vacation.log"); - if (new->args[mailarg_index_once].u == NULL) + if (!new->args[mailarg_index_once].u) new->args[mailarg_index_once].u = string_copy(US".vacation"); - if (new->args[mailarg_index_once_repeat].u == NULL) + if (!new->args[mailarg_index_once_repeat].u) new->args[mailarg_index_once_repeat].u = string_copy(US"7d"); - if (new->args[mailarg_index_subject].u == NULL) + if (!new->args[mailarg_index_subject].u) new->args[mailarg_index_subject].u = string_copy(US"On vacation"); } @@ -1323,7 +1317,7 @@ switch (command) case seen_command: case unseen_command: - if (*ptr == 0) + if (!*ptr) { *error_pointer = string_sprintf("\"seen\" or \"unseen\" " "near line %d is not followed by a command", line_number); @@ -1344,7 +1338,7 @@ switch (command) /* So does noerror */ case noerror_command: - if (*ptr == 0) + if (!*ptr) { *error_pointer = string_sprintf("\"noerror\" " "near line %d is not followed by a command", line_number); @@ -1380,7 +1374,7 @@ return yield; * Read a list of commands * *************************************************/ -/* If condional is TRUE, the list must be terminated +/* If conditional is TRUE, the list must be terminated by the words "else" or "endif". Arguments: @@ -1393,11 +1387,11 @@ Returns: TRUE on success */ static BOOL -read_command_list(uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional) +read_command_list(const uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional) { if (conditional) expect_endif++; had_else_endif = had_neither; -while (**pptr != 0 && had_else_endif == had_neither) +while (**pptr && had_else_endif == had_neither) { if (!read_command(pptr, lastcmdptr)) return FALSE; *pptr = nextsigchar(*pptr, TRUE); @@ -1431,224 +1425,214 @@ Returns: TRUE if the condition is met */ static BOOL -test_condition(condition_block *c, BOOL toplevel) +test_condition(condition_block * c, BOOL toplevel) { -BOOL yield = FALSE; -const pcre *re; -uschar *exp[2], *p, *pp; -const uschar *regcomp_error = NULL; -int regcomp_error_offset; +BOOL yield = FALSE, textonly_re; +const uschar * exp[2], * p, * pp; int val[2]; -int i; -if (c == NULL) return TRUE; /* does this ever occur? */ +if (!c) return TRUE; /* does this ever occur? */ switch (c->type) { case cond_and: - yield = test_condition(c->left.c, FALSE) && - *error_pointer == NULL && - test_condition(c->right.c, FALSE); - break; + yield = test_condition(c->left.c, FALSE) && + *error_pointer == NULL && + test_condition(c->right.c, FALSE); + break; case cond_or: - yield = test_condition(c->left.c, FALSE) || - (*error_pointer == NULL && - test_condition(c->right.c, FALSE)); - break; + yield = test_condition(c->left.c, FALSE) || + (*error_pointer == NULL && + test_condition(c->right.c, FALSE)); + break; - /* The personal test is meaningless in a system filter. The tests are now in - a separate function (so Sieve can use them). However, an Exim filter does not - scan Cc: (hence the FALSE argument). */ + /* The personal test is meaningless in a system filter. The tests are now in + a separate function (so Sieve can use them). However, an Exim filter does not + scan Cc: (hence the FALSE argument). */ case cond_personal: - yield = system_filtering? FALSE : filter_personal(c->left.a, FALSE); - break; + yield = f.system_filtering? FALSE : filter_personal(c->left.a, FALSE); + break; case cond_delivered: - yield = filter_delivered; - break; + yield = filter_delivered; + break; - /* Only TRUE if a message is actually being processed; FALSE for address - testing and verification. */ + /* Only TRUE if a message is actually being processed; FALSE for address + testing and verification. */ case cond_errormsg: - yield = message_id[0] != 0 && - (sender_address == NULL || sender_address[0] == 0); - break; + yield = message_id[0] != 0 && + (sender_address == NULL || sender_address[0] == 0); + break; - /* Only FALSE if a message is actually being processed; TRUE for address - and filter testing and verification. */ + /* Only FALSE if a message is actually being processed; TRUE for address + and filter testing and verification. */ case cond_firsttime: - yield = filter_test != FTEST_NONE || message_id[0] == 0 || deliver_firsttime; - break; + yield = filter_test != FTEST_NONE || message_id[0] == 0 || f.deliver_firsttime; + break; - /* Only TRUE if a message is actually being processed; FALSE for address - testing and verification. */ + /* Only TRUE if a message is actually being processed; FALSE for address + testing and verification. */ case cond_manualthaw: - yield = message_id[0] != 0 && deliver_manual_thaw; - break; + yield = message_id[0] != 0 && f.deliver_manual_thaw; + break; - /* The foranyaddress condition loops through a list of addresses */ + /* The foranyaddress condition loops through a list of addresses */ case cond_foranyaddress: - p = c->left.u; - pp = expand_string(p); - if (pp == NULL) - { - *error_pointer = string_sprintf("failed to expand \"%s\" in " - "filter file: %s", p, expand_string_message); - return FALSE; - } - - yield = FALSE; - parse_allow_group = TRUE; /* Allow group syntax */ - - while (*pp != 0) - { - uschar *error; - int start, end, domain; - int saveend; - - p = parse_find_address_end(pp, FALSE); - saveend = *p; - - *p = 0; - filter_thisaddress = - parse_extract_address(pp, &error, &start, &end, &domain, FALSE); - *p = saveend; - - if (filter_thisaddress != NULL) - { - if ((filter_test != FTEST_NONE && debug_selector != 0) || - (debug_selector & D_filter) != 0) - { - indent(); - debug_printf("Extracted address %s\n", filter_thisaddress); - } - yield = test_condition(c->right.c, FALSE); - } - - if (yield) break; - if (saveend == 0) break; - pp = p + 1; - } - - parse_allow_group = FALSE; /* Reset group syntax flags */ - parse_found_group = FALSE; - break; - - /* All other conditions have left and right values that need expanding; - on error, it doesn't matter what value is returned. */ - - default: - p = c->left.u; - for (i = 0; i < 2; i++) - { - exp[i] = expand_string(p); - if (exp[i] == NULL) + p = c->left.u; + if (!(pp = expand_cstring(p))) { *error_pointer = string_sprintf("failed to expand \"%s\" in " - "filter file: %s", p, expand_string_message); + "filter file: %s", p, expand_string_message); return FALSE; } - p = c->right.u; - } - - /* Inner switch for the different cases */ - switch(c->type) - { - case cond_is: - yield = strcmpic(exp[0], exp[1]) == 0; - break; - - case cond_IS: - yield = Ustrcmp(exp[0], exp[1]) == 0; - break; - - case cond_contains: - yield = strstric(exp[0], exp[1], FALSE) != NULL; - break; - - case cond_CONTAINS: - yield = Ustrstr(exp[0], exp[1]) != NULL; - break; - - case cond_begins: - yield = strncmpic(exp[0], exp[1], Ustrlen(exp[1])) == 0; - break; - - case cond_BEGINS: - yield = Ustrncmp(exp[0], exp[1], Ustrlen(exp[1])) == 0; - break; + yield = FALSE; + f.parse_allow_group = TRUE; /* Allow group syntax */ - case cond_ends: - case cond_ENDS: + while (*pp) { - int len = Ustrlen(exp[1]); - uschar *s = exp[0] + Ustrlen(exp[0]) - len; - yield = (s < exp[0])? FALSE : - ((c->type == cond_ends)? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0; + uschar *error; + int start, end, domain; + uschar * s; + + p = parse_find_address_end(pp, FALSE); + s = string_copyn(pp, p - pp); + + filter_thisaddress = + parse_extract_address(s, &error, &start, &end, &domain, FALSE); + + if (filter_thisaddress) + { + if ((filter_test != FTEST_NONE && debug_selector != 0) || + (debug_selector & D_filter) != 0) + { + indent(); + debug_printf_indent("Extracted address %s\n", filter_thisaddress); + } + yield = test_condition(c->right.c, FALSE); + } + + if (yield) break; + if (!*p) break; + pp = p + 1; } - break; - case cond_matches: - case cond_MATCHES: - if ((filter_test != FTEST_NONE && debug_selector != 0) || - (debug_selector & D_filter) != 0) - { - debug_printf("Match expanded arguments:\n"); - debug_printf(" Subject = %s\n", exp[0]); - debug_printf(" Pattern = %s\n", exp[1]); - } + f.parse_allow_group = FALSE; /* Reset group syntax flags */ + f.parse_found_group = FALSE; + break; - re = pcre_compile(CS exp[1], - PCRE_COPT | ((c->type == cond_matches)? PCRE_CASELESS : 0), - (const char **)®comp_error, ®comp_error_offset, NULL); + /* All other conditions have left and right values that need expanding; + on error, it doesn't matter what value is returned. */ - if (re == NULL) + default: + p = c->left.u; + for (int i = 0; i < 2; i++) { - *error_pointer = string_sprintf("error while compiling " - "regular expression \"%s\": %s at offset %d", - exp[1], regcomp_error, regcomp_error_offset); - return FALSE; + if (!(exp[i] = expand_string_2(p, &textonly_re))) + { + *error_pointer = string_sprintf("failed to expand \"%s\" in " + "filter file: %s", p, expand_string_message); + return FALSE; + } + p = c->right.u; } - yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1); - break; - - /* For above and below, convert the strings to numbers */ + /* Inner switch for the different cases */ - case cond_above: - case cond_below: - for (i = 0; i < 2; i++) + switch(c->type) { - val[i] = get_number(exp[i], &yield); - if (!yield) - { - *error_pointer = string_sprintf("malformed numerical string \"%s\"", - exp[i]); - return FALSE; - } + case cond_is: + yield = strcmpic(exp[0], exp[1]) == 0; + break; + + case cond_IS: + yield = Ustrcmp(exp[0], exp[1]) == 0; + break; + + case cond_contains: + yield = strstric_c(exp[0], exp[1], FALSE) != NULL; + break; + + case cond_CONTAINS: + yield = Ustrstr(exp[0], exp[1]) != NULL; + break; + + case cond_begins: + yield = strncmpic(exp[0], exp[1], Ustrlen(exp[1])) == 0; + break; + + case cond_BEGINS: + yield = Ustrncmp(exp[0], exp[1], Ustrlen(exp[1])) == 0; + break; + + case cond_ends: + case cond_ENDS: + { + int len = Ustrlen(exp[1]); + const uschar *s = exp[0] + Ustrlen(exp[0]) - len; + yield = s < exp[0] + ? FALSE + : (c->type == cond_ends ? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0; + break; + } + + case cond_matches: + case cond_MATCHES: + { + const pcre2_code * re; + mcs_flags flags = textonly_re ? MCS_CACHEABLE : MCS_NOFLAGS; + + if ((filter_test != FTEST_NONE && debug_selector != 0) || + (debug_selector & D_filter) != 0) + { + debug_printf_indent("Match expanded arguments:\n"); + debug_printf_indent(" Subject = %s\n", exp[0]); + debug_printf_indent(" Pattern = %s\n", exp[1]); + } + + if (c->type == cond_matches) flags |= MCS_CASELESS; + if (!(re = regex_compile(exp[1], flags, error_pointer, pcre_gen_cmp_ctx))) + return FALSE; + + yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1); + break; + } + + /* For above and below, convert the strings to numbers */ + + case cond_above: + case cond_below: + for (int i = 0; i < 2; i++) + { + val[i] = get_number(exp[i], &yield); + if (!yield) + { + *error_pointer = string_sprintf("malformed numerical string \"%s\"", + exp[i]); + return FALSE; + } + } + yield = c->type == cond_above ? (val[0] > val[1]) : (val[0] < val[1]); + break; } - yield = (c->type == cond_above)? (val[0] > val[1]) : (val[0] < val[1]); break; - } - break; } if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0) { indent(); - debug_printf("%sondition is %s: ", + debug_printf_indent("%sondition is %s: ", toplevel? "C" : "Sub-c", (yield == c->testfor)? "true" : "false"); print_condition(c, TRUE); - debug_printf("\n"); + debug_printf_indent("\n"); } return yield == c->testfor; @@ -1679,16 +1663,16 @@ Returns: FF_DELIVERED success, a significant action was taken static int interpret_commands(filter_cmd *commands, address_item **generated) { -uschar *s; +const uschar *s; int mode; address_item *addr; BOOL condition_value; -while (commands != NULL) +while (commands) { int ff_ret; uschar *fmsg, *ff_name; - uschar *expargs[MAILARGS_STRING_COUNT]; + const uschar *expargs[MAILARGS_STRING_COUNT]; int i, n[2]; @@ -1697,21 +1681,15 @@ while (commands != NULL) for (i = 0; i < (command_exparg_count[commands->command] & 15); i++) { - uschar *ss = commands->args[i].u; - if (ss == NULL) - { + const uschar *ss = commands->args[i].u; + if (!ss) expargs[i] = NULL; - } - else + else if (!(expargs[i] = expand_cstring(ss))) { - expargs[i] = expand_string(ss); - if (expargs[i] == NULL) - { - *error_pointer = string_sprintf("failed to expand \"%s\" in " - "%s command: %s", ss, command_list[commands->command], - expand_string_message); - return FF_ERROR; - } + *error_pointer = string_sprintf("failed to expand \"%s\" in " + "%s command: %s", ss, command_list[commands->command], + expand_string_message); + return FF_ERROR; } } @@ -1723,648 +1701,653 @@ while (commands != NULL) switch(commands->command) { case add_command: - for (i = 0; i < 2; i++) - { - uschar *ss = expargs[i]; - uschar *end; - - if (i == 1 && (*ss++ != 'n' || ss[1] != 0)) - { - *error_pointer = string_sprintf("unknown variable \"%s\" in \"add\" " - "command", expargs[i]); - return FF_ERROR; - } - - /* Allow for "--" at the start of the value (from -$n0) for example */ - if (i == 0) while (ss[0] == '-' && ss[1] == '-') ss += 2; - - n[i] = (int)Ustrtol(ss, &end, 0); - if (*end != 0) - { - *error_pointer = string_sprintf("malformed number \"%s\" in \"add\" " - "command", ss); - return FF_ERROR; - } - } - - filter_n[n[1]] += n[0]; - if (filter_test != FTEST_NONE) printf("Add %d to n%d\n", n[0], n[1]); - break; + for (i = 0; i < 2; i++) + { + const uschar *ss = expargs[i]; + uschar *end; + + if (i == 1 && (*ss++ != 'n' || ss[1] != 0)) + { + *error_pointer = string_sprintf("unknown variable \"%s\" in \"add\" " + "command", expargs[i]); + return FF_ERROR; + } + + /* Allow for "--" at the start of the value (from -$n0) for example */ + if (i == 0) while (ss[0] == '-' && ss[1] == '-') ss += 2; + + n[i] = (int)Ustrtol(ss, &end, 0); + if (*end != 0) + { + *error_pointer = string_sprintf("malformed number \"%s\" in \"add\" " + "command", ss); + return FF_ERROR; + } + } + + filter_n[n[1]] += n[0]; + if (filter_test != FTEST_NONE) printf("Add %d to n%d\n", n[0], n[1]); + break; - /* A deliver command's argument must be a valid address. Its optional - second argument (system filter only) must also be a valid address. */ + /* A deliver command's argument must be a valid address. Its optional + second argument (system filter only) must also be a valid address. */ case deliver_command: - for (i = 0; i < 2; i++) - { - s = expargs[i]; - if (s != NULL) - { - int start, end, domain; - uschar *error; - uschar *ss = parse_extract_address(s, &error, &start, &end, &domain, - FALSE); - if (ss != NULL) - expargs[i] = ((filter_options & RDO_REWRITE) != 0)? - rewrite_address(ss, TRUE, FALSE, global_rewrite_rules, - rewrite_existflags) : - rewrite_address_qualify(ss, TRUE); - else - { - *error_pointer = string_sprintf("malformed address \"%s\" in " - "filter file: %s", s, error); - return FF_ERROR; - } - } - } - - /* Stick the errors address into a simple variable, as it will - be referenced a few times. Check that the caller is permitted to - specify it. */ - - s = expargs[1]; + for (i = 0; i < 2; i++) + { + s = expargs[i]; + if (s != NULL) + { + int start, end, domain; + uschar *error; + uschar *ss = parse_extract_address(s, &error, &start, &end, &domain, + FALSE); + if (ss) + expargs[i] = filter_options & RDO_REWRITE + ? rewrite_address(ss, TRUE, FALSE, global_rewrite_rules, + rewrite_existflags) + : rewrite_address_qualify(ss, TRUE); + else + { + *error_pointer = string_sprintf("malformed address \"%s\" in " + "filter file: %s", s, error); + return FF_ERROR; + } + } + } + + /* Stick the errors address into a simple variable, as it will + be referenced a few times. Check that the caller is permitted to + specify it. */ + + s = expargs[1]; + + if (s != NULL && !f.system_filtering) + { + uschar *ownaddress = expand_string(US"$local_part@$domain"); + if (strcmpic(ownaddress, s) != 0) + { + *error_pointer = US"errors_to must point to the caller's address"; + return FF_ERROR; + } + } + + /* Test case: report what would happen */ - if (s != NULL && !system_filtering) - { - uschar *ownaddress = expand_string(US"$local_part@$domain"); - if (strcmpic(ownaddress, s) != 0) - { - *error_pointer = US"errors_to must point to the caller's address"; - return FF_ERROR; - } - } - - /* Test case: report what would happen */ - - if (filter_test != FTEST_NONE) - { - indent(); - printf("%seliver message to: %s%s%s%s\n", - (commands->seen)? "D" : "Unseen d", - expargs[0], - commands->noerror? " (noerror)" : "", - (s != NULL)? " errors_to " : "", - (s != NULL)? s : US""); - } - - /* Real case. */ + if (filter_test != FTEST_NONE) + { + indent(); + printf("%seliver message to: %s%s%s%s\n", + (commands->seen)? "D" : "Unseen d", + expargs[0], + commands->noerror? " (noerror)" : "", + (s != NULL)? " errors_to " : "", + (s != NULL)? s : US""); + } + + /* Real case. */ - else - { - DEBUG(D_filter) debug_printf("Filter: %sdeliver message to: %s%s%s%s\n", - (commands->seen)? "" : "unseen ", - expargs[0], - commands->noerror? " (noerror)" : "", - (s != NULL)? " errors_to " : "", - (s != NULL)? s : US""); - - /* Create the new address and add it to the chain, setting the - af_ignore_error flag if necessary, and the errors address, which can be - set in a system filter and to the local address in user filters. */ - - addr = deliver_make_addr(expargs[0], TRUE); /* TRUE => copy s */ - addr->p.errors_address = (s == NULL)? - s : string_copy(s); /* Default is NULL */ - if (commands->noerror) setflag(addr, af_ignore_error); - addr->next = *generated; - *generated = addr; - } - break; + else + { + DEBUG(D_filter) debug_printf_indent("Filter: %sdeliver message to: %s%s%s%s\n", + (commands->seen)? "" : "unseen ", + expargs[0], + commands->noerror? " (noerror)" : "", + (s != NULL)? " errors_to " : "", + (s != NULL)? s : US""); + + /* Create the new address and add it to the chain, setting the + af_ignore_error flag if necessary, and the errors address, which can be + set in a system filter and to the local address in user filters. */ + + addr = deliver_make_addr(US expargs[0], TRUE); /* TRUE => copy s, so deconst ok */ + addr->prop.errors_address = !s ? NULL : string_copy(s); /* Default is NULL */ + if (commands->noerror) addr->prop.ignore_error = TRUE; + addr->next = *generated; + *generated = addr; + } + break; case save_command: - s = expargs[0]; - mode = commands->args[1].i; + s = expargs[0]; + mode = commands->args[1].i; - /* Test case: report what would happen */ + /* Test case: report what would happen */ - if (filter_test != FTEST_NONE) - { - indent(); - if (mode < 0) - printf("%save message to: %s%s\n", (commands->seen)? - "S" : "Unseen s", s, commands->noerror? " (noerror)" : ""); - else - printf("%save message to: %s %04o%s\n", (commands->seen)? - "S" : "Unseen s", s, mode, commands->noerror? " (noerror)" : ""); - } - - /* Real case: Ensure save argument starts with / if there is a home - directory to prepend. */ + if (filter_test != FTEST_NONE) + { + indent(); + if (mode < 0) + printf("%save message to: %s%s\n", (commands->seen)? + "S" : "Unseen s", s, commands->noerror? " (noerror)" : ""); + else + printf("%save message to: %s %04o%s\n", (commands->seen)? + "S" : "Unseen s", s, mode, commands->noerror? " (noerror)" : ""); + } + + /* Real case: Ensure save argument starts with / if there is a home + directory to prepend. */ - else - { - if (s[0] != '/' && (filter_options & RDO_PREPEND_HOME) != 0 && - deliver_home != NULL && deliver_home[0] != 0) - s = string_sprintf("%s/%s", deliver_home, s); - DEBUG(D_filter) debug_printf("Filter: %ssave message to: %s%s\n", - (commands->seen)? "" : "unseen ", s, - commands->noerror? " (noerror)" : ""); - - /* Create the new address and add it to the chain, setting the - af_pfr and af_file flags, the af_ignore_error flag if necessary, and the - mode value. */ - - addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */ - setflag(addr, af_pfr|af_file); - if (commands->noerror) setflag(addr, af_ignore_error); - addr->mode = mode; - addr->next = *generated; - *generated = addr; - } - break; + else + { + if (s[0] != '/' && (filter_options & RDO_PREPEND_HOME) != 0 && + deliver_home != NULL && deliver_home[0] != 0) + s = string_sprintf("%s/%s", deliver_home, s); + DEBUG(D_filter) debug_printf_indent("Filter: %ssave message to: %s%s\n", + (commands->seen)? "" : "unseen ", s, + commands->noerror? " (noerror)" : ""); + + /* Create the new address and add it to the chain, setting the + af_pfr and af_file flags, the af_ignore_error flag if necessary, and the + mode value. */ + + addr = deliver_make_addr(US s, TRUE); /* TRUE => copy s, so deconst ok */ + setflag(addr, af_pfr); + setflag(addr, af_file); + if (commands->noerror) addr->prop.ignore_error = TRUE; + addr->mode = mode; + addr->next = *generated; + *generated = addr; + } + break; case pipe_command: - s = string_copy(commands->args[0].u); - if (filter_test != FTEST_NONE) - { - indent(); - printf("%sipe message to: %s%s\n", (commands->seen)? - "P" : "Unseen p", s, commands->noerror? " (noerror)" : ""); - } - else /* Ensure pipe command starts with | */ - { - DEBUG(D_filter) debug_printf("Filter: %spipe message to: %s%s\n", - (commands->seen)? "" : "unseen ", s, - commands->noerror? " (noerror)" : ""); - if (s[0] != '|') s = string_sprintf("|%s", s); - - /* Create the new address and add it to the chain, setting the - af_ignore_error flag if necessary. Set the af_expand_pipe flag so that - each command argument is expanded in the transport after the command - has been split up into separate arguments. */ - - addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */ - setflag(addr, af_pfr|af_expand_pipe); - if (commands->noerror) setflag(addr, af_ignore_error); - addr->next = *generated; - *generated = addr; - - /* If there are any numeric variables in existence (e.g. after a regex - condition), or if $thisaddress is set, take a copy for use in the - expansion. Note that we can't pass NULL for filter_thisaddress, because - NULL terminates the list. */ - - if (expand_nmax >= 0 || filter_thisaddress != NULL) - { - int i; - int ecount = (expand_nmax >= 0)? expand_nmax : -1; - uschar **ss = store_get(sizeof(uschar *) * (ecount + 3)); - addr->pipe_expandn = ss; - if (filter_thisaddress == NULL) filter_thisaddress = US""; - *ss++ = string_copy(filter_thisaddress); - for (i = 0; i <= expand_nmax; i++) - *ss++ = string_copyn(expand_nstring[i], expand_nlength[i]); - *ss = NULL; - } - } - break; + s = string_copy(commands->args[0].u); + if (filter_test != FTEST_NONE) + { + indent(); + printf("%sipe message to: %s%s\n", (commands->seen)? + "P" : "Unseen p", s, commands->noerror? " (noerror)" : ""); + } + else /* Ensure pipe command starts with | */ + { + DEBUG(D_filter) debug_printf_indent("Filter: %spipe message to: %s%s\n", + commands->seen ? "" : "unseen ", s, + commands->noerror ? " (noerror)" : ""); + if (s[0] != '|') s = string_sprintf("|%s", s); + + /* Create the new address and add it to the chain, setting the + af_ignore_error flag if necessary. Set the af_expand_pipe flag so that + each command argument is expanded in the transport after the command + has been split up into separate arguments. */ + + addr = deliver_make_addr(US s, TRUE); /* TRUE => copy s, so deconst ok */ + setflag(addr, af_pfr); + setflag(addr, af_expand_pipe); + if (commands->noerror) addr->prop.ignore_error = TRUE; + addr->next = *generated; + *generated = addr; + + /* If there are any numeric variables in existence (e.g. after a regex + condition), or if $thisaddress is set, take a copy for use in the + expansion. Note that we can't pass NULL for filter_thisaddress, because + NULL terminates the list. */ + + if (expand_nmax >= 0 || filter_thisaddress != NULL) + { + int ecount = expand_nmax >= 0 ? expand_nmax : -1; + uschar ** ss = store_get(sizeof(uschar *) * (ecount + 3), GET_UNTAINTED); + + addr->pipe_expandn = ss; + if (!filter_thisaddress) filter_thisaddress = US""; + *ss++ = string_copy(filter_thisaddress); + for (int i = 0; i <= expand_nmax; i++) + *ss++ = string_copyn(expand_nstring[i], expand_nlength[i]); + *ss = NULL; + } + } + break; - /* Set up the file name and mode, and close any previously open - file. */ + /* Set up the file name and mode, and close any previously open + file. */ case logfile_command: - log_mode = commands->args[1].i; - if (log_mode == -1) log_mode = 0600; - if (log_fd >= 0) - { - (void)close(log_fd); - log_fd = -1; - } - log_filename = expargs[0]; - if (filter_test != FTEST_NONE) - { - indent(); - printf("%sogfile %s\n", (commands->seen)? "Seen l" : "L", log_filename); - } - break; + log_mode = commands->args[1].i; + if (log_mode == -1) log_mode = 0600; + if (log_fd >= 0) + { + (void)close(log_fd); + log_fd = -1; + } + log_filename = expargs[0]; + if (filter_test != FTEST_NONE) + { + indent(); + printf("%sogfile %s\n", (commands->seen)? "Seen l" : "L", log_filename); + } + break; case logwrite_command: - s = expargs[0]; - - if (filter_test != FTEST_NONE) - { - indent(); - printf("%sogwrite \"%s\"\n", (commands->seen)? "Seen l" : "L", - string_printing(s)); - } - - /* Attempt to write to a log file only if configured as permissible. - Logging may be forcibly skipped for verifying or testing. */ - - else if ((filter_options & RDO_LOG) != 0) /* Locked out */ - { - DEBUG(D_filter) - debug_printf("filter log command aborted: euid=%ld\n", - (long int)geteuid()); - *error_pointer = US"logwrite command forbidden"; - return FF_ERROR; - } - else if ((filter_options & RDO_REALLOG) != 0) - { - int len; - DEBUG(D_filter) debug_printf("writing filter log as euid %ld\n", - (long int)geteuid()); - if (log_fd < 0) - { - if (log_filename == NULL) - { - *error_pointer = US"attempt to obey \"logwrite\" command " - "without a previous \"logfile\""; - return FF_ERROR; - } - log_fd = Uopen(log_filename, O_CREAT|O_APPEND|O_WRONLY, log_mode); - if (log_fd < 0) - { - *error_pointer = string_open_failed(errno, "filter log file \"%s\"", - log_filename); - return FF_ERROR; - } - } - len = Ustrlen(s); - if (write(log_fd, s, len) != len) - { - *error_pointer = string_sprintf("write error on file \"%s\": %s", - log_filename, strerror(errno)); - return FF_ERROR; - } - } - else - { - DEBUG(D_filter) debug_printf("skipping logwrite (verifying or testing)\n"); - } - break; - - /* Header addition and removal is available only in the system filter. The - command is rejected at parse time otherwise. However "headers charset" is - always permitted. */ - - case headers_command: - { - int subtype = commands->args[1].i; s = expargs[0]; if (filter_test != FTEST_NONE) - printf("Headers %s \"%s\"\n", (subtype == TRUE)? "add" : - (subtype == FALSE)? "remove" : "charset", string_printing(s)); - - if (subtype == TRUE) - { - while (isspace(*s)) s++; - if (s[0] != 0) - { - header_add(htype_other, "%s%s", s, (s[Ustrlen(s)-1] == '\n')? - "" : "\n"); - header_last->type = header_checkname(header_last, FALSE); - if (header_last->type >= 'a') header_last->type = htype_other; - } - } - - else if (subtype == FALSE) - { - int sep = 0; - uschar *ss; - uschar *list = s; - uschar buffer[128]; - while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) - != NULL) - header_remove(0, ss); - } + { + indent(); + printf("%sogwrite \"%s\"\n", (commands->seen)? "Seen l" : "L", + string_printing(s)); + } + + /* Attempt to write to a log file only if configured as permissible. + Logging may be forcibly skipped for verifying or testing. */ + + else if ((filter_options & RDO_LOG) != 0) /* Locked out */ + { + DEBUG(D_filter) + debug_printf_indent("filter log command aborted: euid=%ld\n", + (long int)geteuid()); + *error_pointer = US"logwrite command forbidden"; + return FF_ERROR; + } + else if ((filter_options & RDO_REALLOG) != 0) + { + int len; + DEBUG(D_filter) debug_printf_indent("writing filter log as euid %ld\n", + (long int)geteuid()); + if (log_fd < 0) + { + if (!log_filename) + { + *error_pointer = US"attempt to obey \"logwrite\" command " + "without a previous \"logfile\""; + return FF_ERROR; + } + log_fd = Uopen(log_filename, O_CREAT|O_APPEND|O_WRONLY, log_mode); + if (log_fd < 0) + { + *error_pointer = string_open_failed("filter log file \"%s\"", + log_filename); + return FF_ERROR; + } + } + len = Ustrlen(s); + if (write(log_fd, s, len) != len) + { + *error_pointer = string_sprintf("write error on file \"%s\": %s", + log_filename, strerror(errno)); + return FF_ERROR; + } + } + else + DEBUG(D_filter) + debug_printf_indent("skipping logwrite (verifying or testing)\n"); + break; - /* This setting lasts only while the filter is running; on exit, the - variable is reset to the previous value. */ + /* Header addition and removal is available only in the system filter. The + command is rejected at parse time otherwise. However "headers charset" is + always permitted. */ - else headers_charset = s; - } - break; + case headers_command: + { + int subtype = commands->args[1].i; + s = expargs[0]; + + if (filter_test != FTEST_NONE) + printf("Headers %s \"%s\"\n", + subtype == TRUE ? "add" + : subtype == FALSE ? "remove" + : "charset", + string_printing(s)); + + if (subtype == TRUE) + { + while (isspace(*s)) s++; + if (*s) + { + header_add(htype_other, "%s%s", s, + s[Ustrlen(s)-1] == '\n' ? "" : "\n"); + header_last->type = header_checkname(header_last, FALSE); + if (header_last->type >= 'a') header_last->type = htype_other; + } + } + + else if (subtype == FALSE) + { + int sep = 0; + const uschar * list = s; + + for (uschar * ss; ss = string_nextinlist(&list, &sep, NULL, 0); ) + header_remove(0, ss); + } + + /* This setting lasts only while the filter is running; on exit, the + variable is reset to the previous value. */ + + else headers_charset = s; + } + break; - /* Defer, freeze, and fail are available only when explicitly permitted. - These commands are rejected at parse time otherwise. The message can get - very long by the inclusion of message headers; truncate if it is, and also - ensure printing characters so as not to mess up log files. */ + /* Defer, freeze, and fail are available only when explicitly permitted. + These commands are rejected at parse time otherwise. The message can get + very long by the inclusion of message headers; truncate if it is, and also + ensure printing characters so as not to mess up log files. */ case defer_command: - ff_name = US"defer"; - ff_ret = FF_DEFER; - goto DEFERFREEZEFAIL; + ff_name = US"defer"; + ff_ret = FF_DEFER; + goto DEFERFREEZEFAIL; case fail_command: - ff_name = US"fail"; - ff_ret = FF_FAIL; - goto DEFERFREEZEFAIL; + ff_name = US"fail"; + ff_ret = FF_FAIL; + goto DEFERFREEZEFAIL; case freeze_command: - ff_name = US"freeze"; - ff_ret = FF_FREEZE; + ff_name = US"freeze"; + ff_ret = FF_FREEZE; DEFERFREEZEFAIL: - fmsg = expargs[0]; - if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, " ... (truncated)"); - fmsg = string_printing(fmsg); - *error_pointer = fmsg; + *error_pointer = fmsg = US string_printing(Ustrlen(expargs[0]) > 1024 + ? string_sprintf("%.1000s ... (truncated)", expargs[0]) + : string_copy(expargs[0])); + for(uschar * s = fmsg; *s; s++) + if (!s[1] && *s == '\n') { *s = '\0'; break; } /* drop trailing newline */ - if (filter_test != FTEST_NONE) - { - indent(); - printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg); - } - else DEBUG(D_filter) debug_printf("Filter: %s \"%s\"\n", ff_name, fmsg); - return ff_ret; + if (filter_test != FTEST_NONE) + { + indent(); + printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg); + } + else + DEBUG(D_filter) debug_printf_indent("Filter: %s \"%s\"\n", ff_name, fmsg); + return ff_ret; case finish_command: - if (filter_test != FTEST_NONE) - { - indent(); - printf("%sinish\n", (commands->seen)? "Seen f" : "F"); - } - else - { - DEBUG(D_filter) debug_printf("Filter: %sfinish\n", - (commands->seen)? " Seen " : ""); - } - finish_obeyed = TRUE; - return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED; + if (filter_test != FTEST_NONE) + { + indent(); + printf("%sinish\n", (commands->seen)? "Seen f" : "F"); + } + else + DEBUG(D_filter) debug_printf_indent("Filter: %sfinish\n", + commands->seen ? " Seen " : ""); + finish_obeyed = TRUE; + return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED; case if_command: - { - uschar *save_address = filter_thisaddress; - int ok = FF_DELIVERED; - condition_value = test_condition(commands->args[0].c, TRUE); - if (*error_pointer != NULL) ok = FF_ERROR; else - { - output_indent += 2; - ok = interpret_commands(commands->args[condition_value? 1:2].f, - generated); - output_indent -= 2; - } - filter_thisaddress = save_address; - if (finish_obeyed || (ok != FF_DELIVERED && ok != FF_NOTDELIVERED)) - return ok; - } - break; + { + uschar *save_address = filter_thisaddress; + int ok = FF_DELIVERED; + condition_value = test_condition(commands->args[0].c, TRUE); + if (*error_pointer) + ok = FF_ERROR; + else + { + output_indent += 2; + ok = interpret_commands(commands->args[condition_value? 1:2].f, + generated); + output_indent -= 2; + } + filter_thisaddress = save_address; + if (finish_obeyed || ok != FF_DELIVERED && ok != FF_NOTDELIVERED) + return ok; + } + break; - /* To try to catch runaway loops, do not generate mail if the - return path is unset or if a non-trusted user supplied -f <> - as the return path. */ + /* To try to catch runaway loops, do not generate mail if the + return path is unset or if a non-trusted user supplied -f <> + as the return path. */ case mail_command: case vacation_command: - if (return_path == NULL || return_path[0] == 0) - { - if (filter_test != FTEST_NONE) - printf("%s command ignored because return_path is empty\n", - command_list[commands->command]); - else DEBUG(D_filter) debug_printf("%s command ignored because return_path " - "is empty\n", command_list[commands->command]); - break; - } - - /* Check the contents of the strings. The type of string can be deduced - from the value of i. - - . If i is equal to mailarg_index_text it's a text string for the body, - where anything goes. - - . If i is > mailarg_index_text, we are dealing with a file name, which - cannot contain non-printing characters. - - . If i is less than mailarg_index_headers we are dealing with something - that will go in a single message header line, where newlines must be - followed by white space. - - . If i is equal to mailarg_index_headers, we have a string that contains - one or more headers. Newlines that are not followed by white space must - be followed by a header name. - */ - - for (i = 0; i < MAILARGS_STRING_COUNT; i++) - { - uschar *p; - uschar *s = expargs[i]; - - if (s == NULL) continue; - - if (i != mailarg_index_text) for (p = s; *p != 0; p++) - { - int c = *p; - if (i > mailarg_index_text) - { - if (!mac_isprint(c)) - { - *error_pointer = string_sprintf("non-printing character in \"%s\" " - "in %s command", string_printing(s), - command_list[commands->command]); - return FF_ERROR; - } - } - - /* i < mailarg_index_text */ - - else if (c == '\n' && !isspace(p[1])) - { - if (i < mailarg_index_headers) - { - *error_pointer = string_sprintf("\\n not followed by space in " - "\"%.1024s\" in %s command", string_printing(s), - command_list[commands->command]); - return FF_ERROR; - } - - /* Check for the start of a new header line within the string */ - - else - { - uschar *pp; - for (pp = p + 1;; pp++) - { - c = *pp; - if (c == ':' && pp != p + 1) break; - if (c == 0 || c == ':' || isspace(*pp)) - { - *error_pointer = string_sprintf("\\n not followed by space or " - "valid header name in \"%.1024s\" in %s command", - string_printing(s), command_list[commands->command]); - return FF_ERROR; - } - } - p = pp; - } - } - } /* Loop to scan the string */ - - /* The string is OK */ - - commands->args[i].u = s; - } - - /* Proceed with mail or vacation command */ - - if (filter_test != FTEST_NONE) - { - uschar *to = commands->args[mailarg_index_to].u; - indent(); - printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M", - (to == NULL)? US"" : to, - (commands->command == vacation_command)? " (vacation)" : "", - (commands->noerror)? " (noerror)" : ""); - for (i = 1; i < MAILARGS_STRING_COUNT; i++) - { - uschar *arg = commands->args[i].u; - if (arg != NULL) - { - int len = Ustrlen(mailargs[i]); - int indent = (debug_selector != 0)? output_indent : 0; - while (len++ < 7 + indent) printf(" "); - printf("%s: %s%s\n", mailargs[i], string_printing(arg), - (commands->args[mailarg_index_expand].u != NULL && - Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : ""); - } - } - if (commands->args[mailarg_index_return].u != NULL) - printf("Return original message\n"); - } - else - { - uschar *tt; - uschar *log_addr = NULL; - uschar *to = commands->args[mailarg_index_to].u; - int size = 0; - int ptr = 0; - int badflag = 0; - - if (to == NULL) to = expand_string(US"$reply_address"); - while (isspace(*to)) to++; - - for (tt = to; *tt != 0; tt++) /* Get rid of newlines */ - if (*tt == '\n') *tt = ' '; - - DEBUG(D_filter) - { - debug_printf("Filter: %smail to: %s%s%s\n", - (commands->seen)? "seen " : "", - to, - (commands->command == vacation_command)? " (vacation)" : "", - (commands->noerror)? " (noerror)" : ""); - for (i = 1; i < MAILARGS_STRING_COUNT; i++) - { - uschar *arg = commands->args[i].u; - if (arg != NULL) - { - int len = Ustrlen(mailargs[i]); - while (len++ < 15) debug_printf(" "); - debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg), - (commands->args[mailarg_index_expand].u != NULL && - Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : ""); - } - } - } - - /* Create the "address" for the autoreply. This is used only for logging, - as the actual recipients are extracted from the To: line by -t. We use the - same logic here to extract the working addresses (there may be more than - one). Just in case there are a vast number of addresses, stop when the - string gets too long. */ - - tt = to; - while (*tt != 0) - { - uschar *ss = parse_find_address_end(tt, FALSE); - uschar *recipient, *errmess; - int start, end, domain; - int temp = *ss; - - *ss = 0; - recipient = parse_extract_address(tt, &errmess, &start, &end, &domain, - FALSE); - *ss = temp; - - /* Ignore empty addresses and errors; an error will occur later if - there's something really bad. */ - - if (recipient != NULL) - { - log_addr = string_cat(log_addr, &size, &ptr, - (log_addr == NULL)? US">" : US",", 1); - log_addr = string_cat(log_addr, &size, &ptr, recipient, - Ustrlen(recipient)); - } - - /* Check size */ - - if (ptr > 256) - { - log_addr = string_cat(log_addr, &size, &ptr, US", ...", 5); - break; - } - - /* Move on past this address */ - - tt = ss + (*ss? 1:0); - while (isspace(*tt)) tt++; - } - - if (log_addr == NULL) - { - log_addr = string_sprintf(">**bad-reply**"); - badflag = af_bad_reply; - } - else log_addr[ptr] = 0; - - addr = deliver_make_addr(log_addr, FALSE); - setflag(addr, (af_pfr|badflag)); - if (commands->noerror) setflag(addr, af_ignore_error); - addr->next = *generated; - *generated = addr; - addr->reply = store_get(sizeof(reply_item)); - addr->reply->from = NULL; - addr->reply->to = string_copy(to); - addr->reply->file_expand = - commands->args[mailarg_index_expand].u != NULL; - addr->reply->expand_forbid = expand_forbid; - addr->reply->return_message = - commands->args[mailarg_index_return].u != NULL; - addr->reply->once_repeat = 0; - - if (commands->args[mailarg_index_once_repeat].u != NULL) - { - addr->reply->once_repeat = - readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0, - FALSE); - if (addr->reply->once_repeat < 0) - { - *error_pointer = string_sprintf("Bad time value for \"once_repeat\" " - "in mail or vacation command: %s", - commands->args[mailarg_index_once_repeat]); - return FF_ERROR; - } - } - - /* Set up all the remaining string arguments (those other than "to") */ - - for (i = 1; i < mailargs_string_passed; i++) - { - uschar *ss = commands->args[i].u; - *((uschar **)(((uschar *)(addr->reply)) + reply_offsets[i])) = - (ss == NULL)? NULL : string_copy(ss); - } - } - break; + if (!return_path || !*return_path) + { + if (filter_test != FTEST_NONE) + printf("%s command ignored because return_path is empty\n", + command_list[commands->command]); + else DEBUG(D_filter) debug_printf_indent("%s command ignored because return_path " + "is empty\n", command_list[commands->command]); + break; + } + + /* Check the contents of the strings. The type of string can be deduced + from the value of i. + + . If i is equal to mailarg_index_text it's a text string for the body, + where anything goes. + + . If i is > mailarg_index_text, we are dealing with a file name, which + cannot contain non-printing characters. + + . If i is less than mailarg_index_headers we are dealing with something + that will go in a single message header line, where newlines must be + followed by white space. + + . If i is equal to mailarg_index_headers, we have a string that contains + one or more headers. Newlines that are not followed by white space must + be followed by a header name. + */ + + for (i = 0; i < MAILARGS_STRING_COUNT; i++) + { + const uschar *s = expargs[i]; + + if (!s) continue; + + if (i != mailarg_index_text) for (const uschar * p = s; *p; p++) + { + int c = *p; + if (i > mailarg_index_text) + { + if (!mac_isprint(c)) + { + *error_pointer = string_sprintf("non-printing character in \"%s\" " + "in %s command", string_printing(s), + command_list[commands->command]); + return FF_ERROR; + } + } + + /* i < mailarg_index_text */ + + else if (c == '\n' && !isspace(p[1])) + { + if (i < mailarg_index_headers) + { + *error_pointer = string_sprintf("\\n not followed by space in " + "\"%.1024s\" in %s command", string_printing(s), + command_list[commands->command]); + return FF_ERROR; + } + + /* Check for the start of a new header line within the string */ + + else + { + const uschar *pp; + for (pp = p + 1;; pp++) + { + c = *pp; + if (c == ':' && pp != p + 1) break; + if (!c || c == ':' || isspace(c)) + { + *error_pointer = string_sprintf("\\n not followed by space or " + "valid header name in \"%.1024s\" in %s command", + string_printing(s), command_list[commands->command]); + return FF_ERROR; + } + } + p = pp; + } + } + } /* Loop to scan the string */ + + /* The string is OK */ + + commands->args[i].u = s; + } + + /* Proceed with mail or vacation command */ + + if (filter_test != FTEST_NONE) + { + const uschar *to = commands->args[mailarg_index_to].u; + indent(); + printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M", + to ? to : US"", + commands->command == vacation_command ? " (vacation)" : "", + commands->noerror ? " (noerror)" : ""); + for (i = 1; i < MAILARGS_STRING_COUNT; i++) + { + const uschar *arg = commands->args[i].u; + if (arg) + { + int len = Ustrlen(mailargs[i]); + int indent = (debug_selector != 0)? output_indent : 0; + while (len++ < 7 + indent) printf(" "); + printf("%s: %s%s\n", mailargs[i], string_printing(arg), + (commands->args[mailarg_index_expand].u != NULL && + Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : ""); + } + } + if (commands->args[mailarg_index_return].u) + printf("Return original message\n"); + } + else + { + const uschar *tt; + const uschar *to = commands->args[mailarg_index_to].u; + gstring * log_addr = NULL; + + if (!to) to = expand_string(US"$reply_address"); + while (isspace(*to)) to++; + + for (tt = to; *tt; tt++) /* Get rid of newlines */ + if (*tt == '\n') + { + uschar * s = string_copy(to); + for (uschar * ss = s; *ss; ss++) + if (*ss == '\n') *ss = ' '; + to = s; + break; + } + + DEBUG(D_filter) + { + debug_printf_indent("Filter: %smail to: %s%s%s\n", + commands->seen ? "seen " : "", + to, + commands->command == vacation_command ? " (vacation)" : "", + commands->noerror ? " (noerror)" : ""); + for (i = 1; i < MAILARGS_STRING_COUNT; i++) + { + const uschar *arg = commands->args[i].u; + if (arg) + { + int len = Ustrlen(mailargs[i]); + while (len++ < 15) debug_printf_indent(" "); + debug_printf_indent("%s: %s%s\n", mailargs[i], string_printing(arg), + (commands->args[mailarg_index_expand].u != NULL && + Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : ""); + } + } + } + + /* Create the "address" for the autoreply. This is used only for logging, + as the actual recipients are extracted from the To: line by -t. We use the + same logic here to extract the working addresses (there may be more than + one). Just in case there are a vast number of addresses, stop when the + string gets too long. */ + + tt = to; + while (*tt) + { + uschar *ss = parse_find_address_end(tt, FALSE); + uschar *recipient, *errmess; + int start, end, domain; + int temp = *ss; + + *ss = 0; + recipient = parse_extract_address(tt, &errmess, &start, &end, &domain, + FALSE); + *ss = temp; + + /* Ignore empty addresses and errors; an error will occur later if + there's something really bad. */ + + if (recipient) + { + log_addr = string_catn(log_addr, log_addr ? US"," : US">", 1); + log_addr = string_cat (log_addr, recipient); + } + + /* Check size */ + + if (log_addr && log_addr->ptr > 256) + { + log_addr = string_catn(log_addr, US", ...", 5); + break; + } + + /* Move on past this address */ + + tt = ss + (*ss ? 1 : 0); + while (isspace(*tt)) tt++; + } + + if (log_addr) + addr = deliver_make_addr(string_from_gstring(log_addr), FALSE); + else + { + addr = deliver_make_addr(US ">**bad-reply**", FALSE); + setflag(addr, af_bad_reply); + } + + setflag(addr, af_pfr); + if (commands->noerror) addr->prop.ignore_error = TRUE; + addr->next = *generated; + *generated = addr; + + addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED); + addr->reply->from = NULL; + addr->reply->to = string_copy(to); + addr->reply->file_expand = + commands->args[mailarg_index_expand].u != NULL; + addr->reply->expand_forbid = expand_forbid; + addr->reply->return_message = + commands->args[mailarg_index_return].u != NULL; + addr->reply->once_repeat = 0; + + if (commands->args[mailarg_index_once_repeat].u != NULL) + { + addr->reply->once_repeat = + readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0, + FALSE); + if (addr->reply->once_repeat < 0) + { + *error_pointer = string_sprintf("Bad time value for \"once_repeat\" " + "in mail or vacation command: %s", + commands->args[mailarg_index_once_repeat].u); + return FF_ERROR; + } + } + + /* Set up all the remaining string arguments (those other than "to") */ + + for (i = 1; i < mailargs_string_passed; i++) + { + const uschar *ss = commands->args[i].u; + *(USS((US addr->reply) + reply_offsets[i])) = + ss ? string_copy(ss) : NULL; + } + } + break; case testprint_command: - if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0) - { - uschar *s = string_printing(expargs[0]); - if (filter_test == FTEST_NONE) - debug_printf("Filter: testprint: %s\n", s); - else - printf("Testprint: %s\n", s); - } + if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0) + { + const uschar *s = string_printing(expargs[0]); + if (filter_test == FTEST_NONE) + debug_printf_indent("Filter: testprint: %s\n", s); + else + printf("Testprint: %s\n", s); + } } commands = commands->next; } -return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED; +return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED; } @@ -2386,9 +2369,10 @@ Returns: TRUE if the message is deemed to be personal BOOL filter_personal(string_item *aliases, BOOL scan_cc) { -uschar *self, *self_from, *self_to; -uschar *psself = NULL, *psself_from = NULL, *psself_to = NULL; -void *reset_point = store_get(0); +const uschar *self, *self_from, *self_to; +uschar *psself = NULL; +const uschar *psself_from = NULL, *psself_to = NULL; +rmark reset_point = store_mark(); BOOL yield; header_line *h; int to_count = 2; @@ -2403,14 +2387,13 @@ defined in RFC 2369. We also scan for "Auto-Submitted"; if it is found to contain any value other than "no", the message is not personal (RFC 3834). Previously the test was for "auto-". */ -for (h = header_list; h != NULL; h = h->next) +for (h = header_list; h; h = h->next) { - uschar *s; if (h->type == htype_old) continue; if (strncmpic(h->text, US"List-", 5) == 0) { - s = h->text + 5; + uschar * s = h->text + 5; if (strncmpic(s, US"Id:", 3) == 0 || strncmpic(s, US"Help:", 5) == 0 || strncmpic(s, US"Subscribe:", 10) == 0 || @@ -2423,12 +2406,12 @@ for (h = header_list; h != NULL; h = h->next) else if (strncmpic(h->text, US"Auto-submitted:", 15) == 0) { - s = h->text + 15; - while (isspace(*s)) s++; + uschar * s = h->text + 15; + Uskip_whitespace(&s); if (strncmpic(s, US"no", 2) != 0) return FALSE; s += 2; - while (isspace(*s)) s++; - if (*s != 0) return FALSE; + Uskip_whitespace(&s); + if (*s) return FALSE; } } @@ -2441,18 +2424,18 @@ self_to = rewrite_one(self, rewrite_to, NULL, FALSE, US"", global_rewrite_rules); -if (self_from == NULL) self_from = self; -if (self_to == NULL) self_to = self; +if (!self_from) self_from = self; +if (self_to) self_to = self; /* If there's a prefix or suffix set, we must include the prefixed/ suffixed version of the local part in the tests. */ -if (deliver_localpart_prefix != NULL || deliver_localpart_suffix != NULL) +if (deliver_localpart_prefix || deliver_localpart_suffix) { psself = string_sprintf("%s%s%s@%s", - (deliver_localpart_prefix == NULL)? US"" : deliver_localpart_prefix, + deliver_localpart_prefix ? deliver_localpart_prefix : US"", deliver_localpart, - (deliver_localpart_suffix == NULL)? US"" : deliver_localpart_suffix, + deliver_localpart_suffix ? deliver_localpart_suffix : US"", deliver_domain); psself_from = rewrite_one(psself, rewrite_from, NULL, FALSE, US"", global_rewrite_rules); @@ -2516,24 +2499,25 @@ Returns: FF_DELIVERED success, a significant action was taken */ int -filter_interpret(uschar *filter, int options, address_item **generated, +filter_interpret(const uschar *filter, int options, address_item **generated, uschar **error) { int i; int yield = FF_ERROR; -uschar *ptr = filter; -uschar *save_headers_charset = headers_charset; +const uschar *ptr = filter; +const uschar *save_headers_charset = headers_charset; filter_cmd *commands = NULL; filter_cmd **lastcmdptr = &commands; DEBUG(D_route) debug_printf("Filter: start of processing\n"); +acl_level++; /* Initialize "not in an if command", set the global flag that is always TRUE while filtering, and zero the variables. */ expect_endif = 0; output_indent = 0; -filter_running = TRUE; +f.filter_running = TRUE; for (i = 0; i < FILTER_VARIABLE_COUNT; i++) filter_n[i] = 0; /* To save having to pass certain values about all the time, make them static. @@ -2566,35 +2550,35 @@ if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0) switch(yield) { case FF_DEFER: - s = US"Filtering ended by \"defer\"."; - break; + s = US"Filtering ended by \"defer\"."; + break; case FF_FREEZE: - s = US"Filtering ended by \"freeze\"."; - break; + s = US"Filtering ended by \"freeze\"."; + break; case FF_FAIL: - s = US"Filtering ended by \"fail\"."; - break; + s = US"Filtering ended by \"fail\"."; + break; case FF_DELIVERED: - s = US"Filtering set up at least one significant delivery " - "or other action.\n" - "No other deliveries will occur."; - break; + s = US"Filtering set up at least one significant delivery " + "or other action.\n" + "No other deliveries will occur."; + break; case FF_NOTDELIVERED: - s = US"Filtering did not set up a significant delivery.\n" - "Normal delivery will occur."; - break; + s = US"Filtering did not set up a significant delivery.\n" + "Normal delivery will occur."; + break; case FF_ERROR: - s = string_sprintf("Filter error: %s", *error); - break; + s = string_sprintf("Filter error: %s", *error); + break; } if (filter_test != FTEST_NONE) printf("%s\n", CS s); - else debug_printf("%s\n", s); + else debug_printf_indent("%s\n", s); } /* Close the log file if it was opened, and kill off any numerical variables @@ -2602,9 +2586,10 @@ before returning. Reset the header decoding charset. */ if (log_fd >= 0) (void)close(log_fd); expand_nmax = -1; -filter_running = FALSE; +f.filter_running = FALSE; headers_charset = save_headers_charset; +acl_level--; DEBUG(D_route) debug_printf("Filter: end of processing\n"); return yield; }