X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4bb241788d6183df9df6b11a6e6071734b65fce1..HEAD:/src/src/filter.c diff --git a/src/src/filter.c b/src/src/filter.c index a037e2edf..813ffdd7c 100644 --- a/src/src/filter.c +++ b/src/src/filter.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Code for mail filtering functions. */ @@ -70,35 +71,6 @@ enum { had_neither, had_else, had_elif, had_endif }; static BOOL read_command_list(const uschar **, filter_cmd ***, BOOL); -/* The string arguments for the mail command. The header line ones (that are -permitted to include \n followed by white space) first, and then the body text -one (it can have \n anywhere). Then the file names and once_repeat, which may -not contain \n. */ - -static const char *mailargs[] = { /* "to" must be first, and */ - "to", /* "cc" and "bcc" must follow */ - "cc", - "bcc", - "from", - "reply_to", - "subject", - "extra_headers", /* miscellaneous added header lines */ - "text", - "file", - "log", - "once", - "once_repeat" -}; - -/* The count of string arguments */ - -#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). */ - -#define mailargs_string_passed (MAILARGS_STRING_COUNT - 1) - /* This defines the offsets for the arguments; first the string ones, and then the non-string ones. The order must be as above. */ @@ -119,21 +91,50 @@ enum { mailarg_index_to, mailargs_total /* total number of arguments */ }; +/* The string arguments for the mail command. The header line ones (that are +permitted to include \n followed by white space) first, and then the body text +one (it can have \n anywhere). Then the file names and once_repeat, which may +not contain \n. */ + +static const char *mailargs[] = { /* "to" must be first, and */ + [mailarg_index_to] = "to", /* "cc" and "bcc" must follow */ + [mailarg_index_cc] = "cc", + [mailarg_index_bcc] = "bcc", + [mailarg_index_from] = "from", + [mailarg_index_reply_to] = "reply_to", + [mailarg_index_subject] = "subject", + [mailarg_index_headers] = "extra_headers", /* misc added header lines */ + [mailarg_index_text] = "text", + [mailarg_index_file] = "file", + [mailarg_index_log] = "log", + [mailarg_index_once] = "once", + [mailarg_index_once_repeat] = "once_repeat" +}; + +/* The count of string arguments */ + +#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). */ + +#define mailargs_string_passed (MAILARGS_STRING_COUNT - 1) + /* Offsets in the data structure for the string arguments (note that once_repeat isn't a string argument at this point.) */ -static int reply_offsets[] = { /* must be in same order as above */ - offsetof(reply_item, to), - offsetof(reply_item, cc), - offsetof(reply_item, bcc), - offsetof(reply_item, from), - offsetof(reply_item, reply_to), - offsetof(reply_item, subject), - offsetof(reply_item, headers), - offsetof(reply_item, text), - offsetof(reply_item, file), - offsetof(reply_item, logfile), - offsetof(reply_item, oncelog), +static int reply_offsets[] = { + [mailarg_index_to] = offsetof(reply_item, to), + [mailarg_index_cc] = offsetof(reply_item, cc), + [mailarg_index_bcc] = offsetof(reply_item, bcc), + [mailarg_index_from] = offsetof(reply_item, from), + [mailarg_index_reply_to] = offsetof(reply_item, reply_to), + [mailarg_index_subject] = offsetof(reply_item, subject), + [mailarg_index_headers] = offsetof(reply_item, headers), + [mailarg_index_text] = offsetof(reply_item, text), + [mailarg_index_file] = offsetof(reply_item, file), + [mailarg_index_log] = offsetof(reply_item, logfile), + [mailarg_index_once] = offsetof(reply_item, oncelog), }; /* Condition identities and names, with negated versions for some @@ -146,20 +147,48 @@ enum { cond_and, cond_or, cond_personal, cond_begins, cond_BEGINS, cond_manualthaw, cond_foranyaddress }; static const char *cond_names[] = { - "and", "or", "personal", - "begins", "BEGINS", "ends", "ENDS", - "is", "IS", "matches", "MATCHES", "contains", - "CONTAINS", "delivered", "above", "below", "error_message", - "first_delivery", "manually_thawed", "foranyaddress" }; + [cond_and] = "and", + [cond_or] = "or", + [cond_personal] = "personal", + [cond_begins] = "begins", + [cond_BEGINS] = "BEGINS", + [cond_ends] = "ends", + [cond_ENDS] = "ENDS", + [cond_is] = "is", + [cond_IS] = "IS", + [cond_matches] = "matches", + [cond_MATCHES] = "MATCHES", + [cond_contains] = "contains", + [cond_CONTAINS] = "CONTAINS", + [cond_delivered] = "delivered", + [cond_above] = "above", + [cond_below] = "below", + [cond_errormsg] = "error_message", + [cond_firsttime] = "first_delivery", + [cond_manualthaw] = "manually_thawed", + [cond_foranyaddress] = "foranyaddress" }; static const char *cond_not_names[] = { - "", "", "not personal", - "does not begin", "does not BEGIN", - "does not end", "does not END", - "is not", "IS not", "does not match", - "does not MATCH", "does not contain", "does not CONTAIN", - "not delivered", "not above", "not below", "not error_message", - "not first_delivery", "not manually_thawed", "not foranyaddress" }; + [cond_and] = "", + [cond_or] = "", + [cond_personal] = "not personal", + [cond_begins] = "does not begin", + [cond_BEGINS] = "does not BEGIN", + [cond_ends] = "does not end", + [cond_ENDS] = "does not END", + [cond_is] = "is not", + [cond_IS] = "IS not", + [cond_matches] = "does not match", + [cond_MATCHES] = "does not MATCH", + [cond_contains] = "does not contain", + [cond_CONTAINS] = "does not CONTAIN", + [cond_delivered] = "not delivered", + [cond_above] = "not above", + [cond_below] = "not below", + [cond_errormsg] = "not error_message", + [cond_firsttime] = "not first_delivery", + [cond_manualthaw] = "not manually_thawed", + [cond_foranyaddress] = "not foranyaddress" }; /* Tables of binary condition words and their corresponding types. Not easy to amalgamate with the above because of the different variants. */ @@ -193,19 +222,36 @@ static int cond_types[] = { cond_BEGINS, cond_BEGINS, cond_CONTAINS, cond_above, cond_begins, cond_begins, cond_below, cond_contains, cond_contains, cond_ends, cond_ends, cond_is, cond_matches, cond_matches }; -/* Command identities: must be kept in step with the list of command words -and the list of expanded argument counts which follow. */ - -enum { add_command, defer_command, deliver_command, elif_command, else_command, - endif_command, finish_command, fail_command, freeze_command, - headers_command, if_command, logfile_command, logwrite_command, - mail_command, noerror_command, pipe_command, save_command, seen_command, - testprint_command, unseen_command, vacation_command }; - -static const char *command_list[] = { - "add", "defer", "deliver", "elif", "else", "endif", "finish", - "fail", "freeze", "headers", "if", "logfile", "logwrite", "mail", - "noerror", "pipe", "save", "seen", "testprint", "unseen", "vacation" +/* Command identities */ + +enum { ADD_COMMAND, DEFER_COMMAND, DELIVER_COMMAND, ELIF_COMMAND, ELSE_COMMAND, + ENDIF_COMMAND, FINISH_COMMAND, FAIL_COMMAND, FREEZE_COMMAND, + HEADERS_COMMAND, IF_COMMAND, LOGFILE_COMMAND, LOGWRITE_COMMAND, + MAIL_COMMAND, NOERROR_COMMAND, PIPE_COMMAND, SAVE_COMMAND, SEEN_COMMAND, + TESTPRINT_COMMAND, UNSEEN_COMMAND, VACATION_COMMAND }; + +static const char * command_list[] = { + [ADD_COMMAND] = "add", + [DEFER_COMMAND] = "defer", + [DELIVER_COMMAND] = "deliver", + [ELIF_COMMAND] = "elif", + [ELSE_COMMAND] = "else", + [ENDIF_COMMAND] = "endif", + [FINISH_COMMAND] = "finish", + [FAIL_COMMAND] = "fail", + [FREEZE_COMMAND] = "freeze", + [HEADERS_COMMAND] = "headers", + [IF_COMMAND] = "if", + [LOGFILE_COMMAND] = "logfile", + [LOGWRITE_COMMAND] = "logwrite", + [MAIL_COMMAND] = "mail", + [NOERROR_COMMAND] = "noerror", + [PIPE_COMMAND] = "pipe", + [SAVE_COMMAND] = "save", + [SEEN_COMMAND] = "seen", + [TESTPRINT_COMMAND] = "testprint", + [UNSEEN_COMMAND] = "unseen", + [VACATION_COMMAND] = "vacation" }; static int command_list_count = nelem(command_list); @@ -214,27 +260,27 @@ static int command_list_count = nelem(command_list); If the top bit is set, it means that the default for the command is "seen". */ static uschar command_exparg_count[] = { - 2, /* add */ - 1, /* defer */ - 128+2, /* deliver */ - 0, /* elif */ - 0, /* else */ - 0, /* endif */ - 0, /* finish */ - 1, /* fail */ - 1, /* freeze */ - 1, /* headers */ - 0, /* if */ - 1, /* logfile */ - 1, /* logwrite */ - MAILARGS_STRING_COUNT, /* mail */ - 0, /* noerror */ - 128+0, /* pipe */ - 128+1, /* save */ - 0, /* seen */ - 1, /* testprint */ - 0, /* unseen */ - MAILARGS_STRING_COUNT /* vacation */ + [ADD_COMMAND] = 2, + [DEFER_COMMAND] = 1, + [DELIVER_COMMAND] = 128+2, + [ELIF_COMMAND] = 0, + [ELSE_COMMAND] = 0, + [ENDIF_COMMAND] = 0, + [FINISH_COMMAND] = 0, + [FAIL_COMMAND] = 1, + [FREEZE_COMMAND] = 1, + [HEADERS_COMMAND] = 1, + [IF_COMMAND] = 0, + [LOGFILE_COMMAND] = 1, + [LOGWRITE_COMMAND] = 1, + [MAIL_COMMAND] = MAILARGS_STRING_COUNT, + [NOERROR_COMMAND] = 0, + [PIPE_COMMAND] = 128+0, + [SAVE_COMMAND] = 128+1, + [SEEN_COMMAND] = 0, + [TESTPRINT_COMMAND] = 1, + [UNSEEN_COMMAND] = 0, + [VACATION_COMMAND] = MAILARGS_STRING_COUNT }; @@ -258,16 +304,11 @@ nextsigchar(const uschar *ptr, BOOL comment_allowed) for (;;) { while (isspace(*ptr)) - { - if (*ptr == '\n') line_number++; - ptr++; - } + if (*ptr++ == '\n') line_number++; if (comment_allowed && *ptr == '#') - { - while (*(++ptr) != '\n' && *ptr != 0); - continue; - } - else break; + while (*++ptr != '\n' && *ptr) ; + else + break; } return ptr; } @@ -293,18 +334,19 @@ Returns: pointer to the next significant character after the word static const uschar * nextword(const uschar *ptr, uschar *buffer, int size, BOOL bracket) { -uschar *bp = buffer; -while (*ptr != 0 && !isspace(*ptr) && +uschar * bp = buffer; +while (*ptr && !isspace(*ptr) && (!bracket || (*ptr != '(' && *ptr != ')'))) - { - if (bp - buffer < size - 1) *bp++ = *ptr++; else + if (bp - buffer < size - 1) + *bp++ = *ptr++; + else { *error_pointer = string_sprintf("word is too long in line %d of " "filter file (max = %d chars)", line_number, size); break; } - } -*bp = 0; + +*bp = '\0'; return nextsigchar(ptr, TRUE); } @@ -392,8 +434,8 @@ int value, count; if (sscanf(CS s, "%i%n", &value, &count) != 1) return 0; if (tolower(s[count]) == 'k') { value *= 1024; count++; } if (tolower(s[count]) == 'm') { value *= 1024*1024; count++; } -while (isspace((s[count]))) count++; -if (s[count] != 0) return 0; +while (isspace(s[count])) count++; +if (s[count]) return 0; *ok = TRUE; return value; } @@ -670,8 +712,8 @@ for (;;) { // if (toplevel) *saveptr = 0; // else - if (!toplevel) - *error_pointer = string_sprintf("missing \")\" at end of " + if (!toplevel) + *error_pointer = string_sprintf("missing \")\" at end of " "condition near line %d of filter file", line_number); break; } @@ -781,8 +823,8 @@ switch(c->type) case cond_errormsg: case cond_firsttime: case cond_manualthaw: - debug_printf("%s", name); - break; + debug_printf("%s", name); + break; case cond_is: case cond_IS: @@ -796,31 +838,31 @@ switch(c->type) case cond_ENDS: case cond_above: case cond_below: - debug_printf("%s %s %s", c->left.u, name, c->right.u); - break; + debug_printf("%s %s %s", c->left.u, name, c->right.u); + break; case cond_and: - if (!c->testfor) debug_printf("not ("); - print_condition(c->left.c, FALSE); - debug_printf(" %s ", cond_names[c->type]); - print_condition(c->right.c, FALSE); - if (!c->testfor) debug_printf(")"); - break; + if (!c->testfor) debug_printf("not ("); + print_condition(c->left.c, FALSE); + debug_printf(" %s ", cond_names[c->type]); + print_condition(c->right.c, FALSE); + if (!c->testfor) debug_printf(")"); + break; case cond_or: - if (!c->testfor) debug_printf("not ("); - else if (!toplevel) debug_printf("("); - print_condition(c->left.c, FALSE); - debug_printf(" %s ", cond_names[c->type]); - print_condition(c->right.c, FALSE); - if (!toplevel || !c->testfor) debug_printf(")"); - break; + if (!c->testfor) debug_printf("not ("); + else if (!toplevel) debug_printf("("); + print_condition(c->left.c, FALSE); + debug_printf(" %s ", cond_names[c->type]); + print_condition(c->right.c, FALSE); + if (!toplevel || !c->testfor) debug_printf(")"); + break; case cond_foranyaddress: - debug_printf("%s %s (", name, c->left.u); - print_condition(c->right.c, FALSE); - debug_printf(")"); - break; + debug_printf("%s %s (", name, c->left.u); + print_condition(c->right.c, FALSE); + debug_printf(")"); + break; } } @@ -890,15 +932,15 @@ switch (command) stored in the second argument slot. Neither may be preceded by seen, unseen or noerror. */ - case add_command: - case headers_command: - if (seen_force || noerror_force) - { - *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " - "found before an \"%s\" command near line %d", - command_list[command], line_number); - yield = FALSE; - } + case ADD_COMMAND: + case HEADERS_COMMAND: + if (seen_force || noerror_force) + { + *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " + "found before an \"%s\" command near line %d", + command_list[command], line_number); + yield = FALSE; + } /* Fall through */ /* Logwrite, logfile, pipe, and testprint all take a single argument, save @@ -906,303 +948,303 @@ switch (command) have "errors_to
" in a system filter, or in a user filter if the address is the current one. */ - case deliver_command: - case logfile_command: - case logwrite_command: - case pipe_command: - case save_command: - case testprint_command: + case DELIVER_COMMAND: + case LOGFILE_COMMAND: + case LOGWRITE_COMMAND: + case PIPE_COMMAND: + case SAVE_COMMAND: + case TESTPRINT_COMMAND: - ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (!*buffer) - *error_pointer = string_sprintf("\"%s\" requires an argument " - "near line %d of filter file", command_list[command], line_number); + ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); + if (!*buffer) + *error_pointer = string_sprintf("\"%s\" requires an argument " + "near line %d of filter file", command_list[command], line_number); - if (*error_pointer) yield = FALSE; else - { - union argtypes argument, second_argument; + if (*error_pointer) yield = FALSE; else + { + union argtypes argument, second_argument; - argument.u = second_argument.u = NULL; + argument.u = second_argument.u = NULL; - if (command == add_command) - { - argument.u = string_copy(buffer); - ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - 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) - *error_pointer = string_sprintf("value missing after \"to\" " - "near line %d of filter file", line_number); - else second_argument.u = string_copy(buffer); - } - } + if (command == ADD_COMMAND) + { + argument.u = string_copy(buffer); + ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); + 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) + *error_pointer = string_sprintf("value missing after \"to\" " + "near line %d of filter file", line_number); + else second_argument.u = string_copy(buffer); + } + } - else if (command == headers_command) - { - if (Ustrcmp(buffer, "add") == 0) - second_argument.b = TRUE; - else - if (Ustrcmp(buffer, "remove") == 0) second_argument.b = FALSE; - else - if (Ustrcmp(buffer, "charset") == 0) - second_argument.b = TRUE_UNSET; - else - { - *error_pointer = string_sprintf("\"add\", \"remove\", or \"charset\" " - "expected after \"headers\" near line %d of filter file", - line_number); - yield = FALSE; - } + else if (command == HEADERS_COMMAND) + { + if (Ustrcmp(buffer, "add") == 0) + second_argument.b = TRUE; + else + if (Ustrcmp(buffer, "remove") == 0) second_argument.b = FALSE; + else + if (Ustrcmp(buffer, "charset") == 0) + second_argument.b = TRUE_UNSET; + else + { + *error_pointer = string_sprintf("\"add\", \"remove\", or \"charset\" " + "expected after \"headers\" near line %d of filter file", + line_number); + yield = FALSE; + } - 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", - line_number); - yield = FALSE; - break; - } + 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", + line_number); + yield = FALSE; + break; + } - if (yield) - { - ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (!*buffer) - *error_pointer = string_sprintf("value missing after \"add\", " - "\"remove\", or \"charset\" near line %d of filter file", - line_number); - else argument.u = string_copy(buffer); - } - } + if (yield) + { + ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); + if (!*buffer) + *error_pointer = string_sprintf("value missing after \"add\", " + "\"remove\", or \"charset\" near line %d of filter file", + line_number); + else argument.u = string_copy(buffer); + } + } - /* The argument for the logwrite command must end in a newline, and the save - and logfile commands can have an optional mode argument. The deliver - command can have an optional "errors_to
" for a system filter, - or for a user filter if the address is the user's address. Accept the - syntax here - the check is later. */ + /* The argument for the logwrite command must end in a newline, and the save + and logfile commands can have an optional mode argument. The deliver + command can have an optional "errors_to
" for a system filter, + or for a user filter if the address is the user's address. Accept the + syntax here - the check is later. */ - else - { - if (command == logwrite_command) - { - int len = Ustrlen(buffer); - if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, US"\n"); - } + else + { + if (command == LOGWRITE_COMMAND) + { + int len = Ustrlen(buffer); + if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, US"\n"); + } - argument.u = string_copy(buffer); + argument.u = string_copy(buffer); - if (command == save_command || command == logfile_command) - { - if (isdigit(*ptr)) - { - ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - second_argument.i = (int)Ustrtol(buffer, NULL, 8); - } - else second_argument.i = -1; - } + if (command == SAVE_COMMAND || command == LOGFILE_COMMAND) + { + if (isdigit(*ptr)) + { + ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); + second_argument.i = (int)Ustrtol(buffer, NULL, 8); + } + else second_argument.i = -1; + } - else if (command == deliver_command) - { - const uschar *save_ptr = ptr; - ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (Ustrcmp(buffer, "errors_to") == 0) - { - ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - second_argument.u = string_copy(buffer); - } - else ptr = save_ptr; - } - } + else if (command == DELIVER_COMMAND) + { + const uschar *save_ptr = ptr; + ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); + if (Ustrcmp(buffer, "errors_to") == 0) + { + ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); + second_argument.u = string_copy(buffer); + } + else ptr = save_ptr; + } + } - /* Set up the command block. Seen defaults TRUE for delivery commands, - FALSE for logging commands, and it doesn't matter for testprint, as - that doesn't change the "delivered" status. */ + /* Set up the command block. Seen defaults TRUE for delivery commands, + FALSE for logging commands, and it doesn't matter for testprint, as + that doesn't change the "delivered" status. */ - if (*error_pointer) yield = FALSE; - else - { - new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), GET_UNTAINTED); - new->next = NULL; - **lastcmdptr = new; - *lastcmdptr = &(new->next); - new->command = command; - new->seen = seen_force? seen_value : command_exparg_count[command] >= 128; - new->noerror = noerror_force; - new->args[0] = argument; - new->args[1] = second_argument; + if (*error_pointer) yield = FALSE; + else + { + new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), GET_UNTAINTED); + new->next = NULL; + **lastcmdptr = new; + *lastcmdptr = &(new->next); + new->command = command; + new->seen = seen_force? seen_value : command_exparg_count[command] >= 128; + new->noerror = noerror_force; + new->args[0] = argument; + new->args[1] = second_argument; + } } - } - break; + break; /* Elif, else and endif just set a flag if expected. */ - case elif_command: - case else_command: - case endif_command: - if (seen_force || noerror_force) - { - *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " - "near line %d is not followed by a command", line_number); - yield = FALSE; - } + case ELIF_COMMAND: + case ELSE_COMMAND: + case ENDIF_COMMAND: + if (seen_force || noerror_force) + { + *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " + "near line %d is not followed by a command", line_number); + yield = FALSE; + } - if (expect_endif > 0) - had_else_endif = (command == elif_command)? had_elif : - (command == else_command)? had_else : had_endif; - else - { - *error_pointer = string_sprintf("unexpected \"%s\" command near " - "line %d of filter file", buffer, line_number); - yield = FALSE; - } - break; + if (expect_endif > 0) + had_else_endif = (command == ELIF_COMMAND)? had_elif : + (command == ELSE_COMMAND)? had_else : had_endif; + else + { + *error_pointer = string_sprintf("unexpected \"%s\" command near " + "line %d of filter file", buffer, line_number); + yield = FALSE; + } + break; /* Defer, freeze, and fail are available only if permitted. */ - case defer_command: - cmd_bit = RDO_DEFER; - goto DEFER_FREEZE_FAIL; + case DEFER_COMMAND: + cmd_bit = RDO_DEFER; + goto DEFER_FREEZE_FAIL; - case fail_command: - cmd_bit = RDO_FAIL; - goto DEFER_FREEZE_FAIL; + case FAIL_COMMAND: + cmd_bit = RDO_FAIL; + goto DEFER_FREEZE_FAIL; - case freeze_command: - cmd_bit = RDO_FREEZE; + case FREEZE_COMMAND: + cmd_bit = RDO_FREEZE; DEFER_FREEZE_FAIL: - if ((filter_options & cmd_bit) == 0) - { - *error_pointer = string_sprintf("filtering command \"%s\" is disabled: " - "near line %d of filter file", buffer, line_number); - yield = FALSE; - break; - } + if ((filter_options & cmd_bit) == 0) + { + *error_pointer = string_sprintf("filtering command \"%s\" is disabled: " + "near line %d of filter file", buffer, line_number); + yield = FALSE; + break; + } - /* A text message can be provided after the "text" keyword, or - as a string in quotes. */ + /* A text message can be provided after the "text" keyword, or + as a string in quotes. */ - saveptr = ptr; - ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*saveptr != '\"' && (!*buffer || Ustrcmp(buffer, "text") != 0)) - { - ptr = saveptr; - fmsg = US""; - } - else - { - if (*saveptr != '\"') - ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - fmsg = string_copy(buffer); - } + saveptr = ptr; + ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); + if (*saveptr != '\"' && (!*buffer || Ustrcmp(buffer, "text") != 0)) + { + ptr = saveptr; + fmsg = US""; + } + else + { + if (*saveptr != '\"') + ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); + fmsg = string_copy(buffer); + } - /* Drop through and treat as "finish", but never set "seen". */ + /* Drop through and treat as "finish", but never set "seen". */ - seen_value = FALSE; + seen_value = FALSE; - /* Finish has no arguments; fmsg defaults to NULL */ + /* Finish has no arguments; fmsg defaults to NULL */ - case finish_command: - 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->args[0].u = fmsg; - break; + case FINISH_COMMAND: + 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->args[0].u = fmsg; + break; /* Seen, unseen, and noerror are not allowed before if, which takes a condition argument and then and else sub-commands. */ - case if_command: - if (seen_force || noerror_force) - { - *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " - "found before an \"if\" command near line %d", - line_number); - yield = FALSE; - } + case IF_COMMAND: + if (seen_force || noerror_force) + { + *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " + "found before an \"if\" command near line %d", + line_number); + yield = FALSE; + } - /* Set up the command block for if */ + /* Set up the command block for if */ - new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); - new->next = NULL; - **lastcmdptr = new; - *lastcmdptr = &new->next; - new->command = command; - new->seen = FALSE; - new->args[0].u = NULL; - new->args[1].u = new->args[2].u = NULL; - new->args[3].u = ptr; + new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); + new->next = NULL; + **lastcmdptr = new; + *lastcmdptr = &new->next; + new->command = command; + new->seen = FALSE; + new->args[0].u = NULL; + new->args[1].u = new->args[2].u = NULL; + new->args[3].u = ptr; - /* Read the condition */ + /* Read the condition */ - ptr = read_condition(ptr, &new->args[0].c, TRUE); - if (*error_pointer) { 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 */ + /* Read the commands to be obeyed if the condition is true */ - newlastcmdptr = &(new->args[1].f); - if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) yield = FALSE; + newlastcmdptr = &(new->args[1].f); + if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) yield = FALSE; - /* If commands were successfully read, handle the various possible - terminators. There may be a number of successive "elif" sections. */ + /* If commands were successfully read, handle the various possible + terminators. There may be a number of successive "elif" sections. */ - else - { - while (had_else_endif == had_elif) + else { - filter_cmd *newnew = - store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); - new->args[2].f = newnew; - new = newnew; - new->next = NULL; - new->command = command; - new->seen = FALSE; - new->args[0].u = NULL; - 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) { yield = FALSE; break; } - newlastcmdptr = &(new->args[1].f); - if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) - yield = FALSE; - } + while (had_else_endif == had_elif) + { + filter_cmd *newnew = + store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED); + new->args[2].f = newnew; + new = newnew; + new->next = NULL; + new->command = command; + new->seen = FALSE; + new->args[0].u = NULL; + 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) { yield = FALSE; break; } + newlastcmdptr = &(new->args[1].f); + if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) + yield = FALSE; + } - if (yield == FALSE) break; + if (yield == FALSE) break; - /* Handle termination by "else", possibly following one or more - "elsif" sections. */ + /* Handle termination by "else", possibly following one or more + "elsif" sections. */ - if (had_else_endif == had_else) - { - newlastcmdptr = &(new->args[2].f); - if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) - yield = FALSE; - else if (had_else_endif != had_endif) - { - *error_pointer = string_sprintf("\"endif\" missing near line %d of " - "filter file", line_number); - yield = FALSE; - } - } + if (had_else_endif == had_else) + { + newlastcmdptr = &(new->args[2].f); + if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) + yield = FALSE; + else if (had_else_endif != had_endif) + { + *error_pointer = string_sprintf("\"endif\" missing near line %d of " + "filter file", line_number); + yield = FALSE; + } + } - /* Otherwise the terminator was "endif" - this is checked by - read_command_list(). The pointer is already set to NULL. */ - } + /* Otherwise the terminator was "endif" - this is checked by + read_command_list(). The pointer is already set to NULL. */ + } - /* Reset the terminator flag. */ + /* Reset the terminator flag. */ - had_else_endif = had_neither; - break; + had_else_endif = had_neither; + break; /* The mail & vacation commands have a whole slew of keyworded arguments. @@ -1211,150 +1253,150 @@ switch (command) are logically booleans, because they are stored in a uschar * value, we use NULL and not FALSE, to keep 64-bit compilers happy. */ - case mail_command: - case vacation_command: - 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->noerror = noerror_force; - for (i = 0; i < mailargs_total; i++) new->args[i].u = NULL; - - /* Read keyword/value pairs until we hit one that isn't. The data - must contain only printing chars plus tab, though the "text" value - can also contain newlines. The "file" keyword can be preceded by the - word "expand", and "return message" has no data. */ - - for (;;) - { - const uschar *saveptr = ptr; - ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (*error_pointer) - { yield = FALSE; break; } - - /* Ensure "return" is followed by "message"; that's a complete option */ - - if (Ustrcmp(buffer, "return") == 0) + case MAIL_COMMAND: + case VACATION_COMMAND: + 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->noerror = noerror_force; + for (i = 0; i < mailargs_total; i++) new->args[i].u = NULL; + + /* Read keyword/value pairs until we hit one that isn't. The data + must contain only printing chars plus tab, though the "text" value + can also contain newlines. The "file" keyword can be preceded by the + word "expand", and "return message" has no data. */ + + for (;;) { - new->args[mailarg_index_return].u = US""; /* not NULL => TRUE */ + const uschar *saveptr = ptr; ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (Ustrcmp(buffer, "message") != 0) - { - *error_pointer = string_sprintf("\"return\" not followed by \"message\" " - " near line %d of filter file", line_number); - yield = FALSE; - break; - } - continue; - } + if (*error_pointer) + { yield = FALSE; break; } - /* Ensure "expand" is followed by "file", then fall through to process the - file keyword. */ + /* Ensure "return" is followed by "message"; that's a complete option */ - if (Ustrcmp(buffer, "expand") == 0) - { - new->args[mailarg_index_expand].u = US""; /* not NULL => TRUE */ - ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); - if (Ustrcmp(buffer, "file") != 0) - { - *error_pointer = string_sprintf("\"expand\" not followed by \"file\" " - " near line %d of filter file", line_number); - yield = FALSE; - break; - } - } + if (Ustrcmp(buffer, "return") == 0) + { + new->args[mailarg_index_return].u = US""; /* not NULL => TRUE */ + ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); + if (Ustrcmp(buffer, "message") != 0) + { + *error_pointer = string_sprintf("\"return\" not followed by \"message\" " + " near line %d of filter file", line_number); + yield = FALSE; + break; + } + continue; + } - /* Scan for the keyword */ + /* Ensure "expand" is followed by "file", then fall through to process the + file keyword. */ - for (i = 0; i < MAILARGS_STRING_COUNT; i++) - if (Ustrcmp(buffer, mailargs[i]) == 0) break; + if (Ustrcmp(buffer, "expand") == 0) + { + new->args[mailarg_index_expand].u = US""; /* not NULL => TRUE */ + ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); + if (Ustrcmp(buffer, "file") != 0) + { + *error_pointer = string_sprintf("\"expand\" not followed by \"file\" " + " near line %d of filter file", line_number); + yield = FALSE; + break; + } + } - /* Not found keyword; assume end of this command */ + /* Scan for the keyword */ - if (i >= MAILARGS_STRING_COUNT) - { - ptr = saveptr; - break; - } + for (i = 0; i < MAILARGS_STRING_COUNT; i++) + if (Ustrcmp(buffer, mailargs[i]) == 0) break; - /* Found keyword, read the data item */ + /* Not found keyword; assume end of this command */ - ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); - if (*error_pointer) - { yield = FALSE; break; } - else new->args[i].u = string_copy(buffer); - } + if (i >= MAILARGS_STRING_COUNT) + { + ptr = saveptr; + break; + } - /* If this is the vacation command, apply some default settings to - some of the arguments. */ + /* Found keyword, read the data item */ - if (command == vacation_command) - { - if (!new->args[mailarg_index_file].u) + ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); + if (*error_pointer) + { yield = FALSE; break; } + else new->args[i].u = string_copy(buffer); + } + + /* If this is the vacation command, apply some default settings to + some of the arguments. */ + + if (command == VACATION_COMMAND) { - 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_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) + new->args[mailarg_index_log].u = string_copy(US".vacation.log"); + 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) + new->args[mailarg_index_once_repeat].u = string_copy(US"7d"); + if (!new->args[mailarg_index_subject].u) + new->args[mailarg_index_subject].u = string_copy(US"On vacation"); } - 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) - new->args[mailarg_index_once].u = string_copy(US".vacation"); - 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) - new->args[mailarg_index_subject].u = string_copy(US"On vacation"); - } - /* Join the address on to the chain of generated addresses */ + /* Join the address on to the chain of generated addresses */ - **lastcmdptr = new; - *lastcmdptr = &(new->next); - break; + **lastcmdptr = new; + *lastcmdptr = &(new->next); + break; /* Seen and unseen just set flags */ - case seen_command: - case unseen_command: - if (!*ptr) - { - *error_pointer = string_sprintf("\"seen\" or \"unseen\" " - "near line %d is not followed by a command", line_number); - yield = FALSE; - } - if (seen_force) - { - *error_pointer = string_sprintf("\"seen\" or \"unseen\" repeated " - "near line %d", line_number); - yield = FALSE; - } - seen_value = (command == seen_command); - seen_force = TRUE; - was_seen_or_unseen = TRUE; - break; + case SEEN_COMMAND: + case UNSEEN_COMMAND: + if (!*ptr) + { + *error_pointer = string_sprintf("\"seen\" or \"unseen\" " + "near line %d is not followed by a command", line_number); + yield = FALSE; + } + if (seen_force) + { + *error_pointer = string_sprintf("\"seen\" or \"unseen\" repeated " + "near line %d", line_number); + yield = FALSE; + } + seen_value = (command == SEEN_COMMAND); + seen_force = TRUE; + was_seen_or_unseen = TRUE; + break; /* So does noerror */ - case noerror_command: - if (!*ptr) - { - *error_pointer = string_sprintf("\"noerror\" " - "near line %d is not followed by a command", line_number); - yield = FALSE; - } - noerror_force = TRUE; - was_noerror = TRUE; - break; + case NOERROR_COMMAND: + if (!*ptr) + { + *error_pointer = string_sprintf("\"noerror\" " + "near line %d is not followed by a command", line_number); + yield = FALSE; + } + noerror_force = TRUE; + was_noerror = TRUE; + break; /* Oops */ default: - *error_pointer = string_sprintf("unknown filtering command \"%s\" " - "near line %d of filter file", buffer, line_number); - yield = FALSE; - break; + *error_pointer = string_sprintf("unknown filtering command \"%s\" " + "near line %d of filter file", buffer, line_number); + yield = FALSE; + break; } if (!was_seen_or_unseen && !was_noerror) @@ -1424,213 +1466,202 @@ 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 uschar *exp[2], * p, * pp; +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 = f.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] && (!sender_address || !*sender_address); + 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 || f.deliver_firsttime; - break; + yield = filter_test != FTEST_NONE || !message_id[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 && f.deliver_manual_thaw; - break; + yield = message_id[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; - if (!(pp = expand_cstring(p))) - { - *error_pointer = string_sprintf("failed to expand \"%s\" in " - "filter file: %s", p, expand_string_message); - return FALSE; - } + 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); + return FALSE; + } - yield = FALSE; - f.parse_allow_group = TRUE; /* Allow group syntax */ + yield = FALSE; + f.parse_allow_group = TRUE; /* Allow group syntax */ - while (*pp) - { - uschar *error; - int start, end, domain; - uschar * s; + while (*pp) + { + uschar *error; + int start, end, domain; + uschar * s; - p = parse_find_address_end(pp, FALSE); - s = string_copyn(pp, p - pp); + p = parse_find_address_end(pp, FALSE); + s = string_copyn(pp, p - pp); - filter_thisaddress = - parse_extract_address(s, &error, &start, &end, &domain, FALSE); + 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 (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; - } + if (yield) break; + if (!*p) break; + pp = p + 1; + } - f.parse_allow_group = FALSE; /* Reset group syntax flags */ - f.parse_found_group = FALSE; - break; + f.parse_allow_group = FALSE; /* Reset group syntax flags */ + f.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. */ + /* 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++) - { - if (!(exp[i] = expand_cstring(p))) + default: + p = c->left.u; + for (int i = 0; i < 2; i++) { - *error_pointer = string_sprintf("failed to expand \"%s\" in " - "filter file: %s", p, expand_string_message); - 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; } - p = c->right.u; - } - /* Inner switch for the different cases */ + /* 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_c(exp[0], exp[1], FALSE) != NULL; - break; + switch(c->type) + { + case cond_is: + yield = strcmpic(exp[0], exp[1]) == 0; + break; - case cond_CONTAINS: - yield = Ustrstr(exp[0], exp[1]) != NULL; - break; + case cond_IS: + yield = Ustrcmp(exp[0], exp[1]) == 0; + break; - case cond_begins: - yield = strncmpic(exp[0], exp[1], Ustrlen(exp[1])) == 0; - break; + case cond_contains: + yield = strstric_c(exp[0], exp[1], FALSE) != NULL; + break; - case cond_BEGINS: - yield = Ustrncmp(exp[0], exp[1], Ustrlen(exp[1])) == 0; - break; + case cond_CONTAINS: + yield = Ustrstr(exp[0], exp[1]) != NULL; + 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_begins: + yield = strncmpic(exp[0], exp[1], Ustrlen(exp[1])) == 0; + break; - case cond_matches: - case cond_MATCHES: - { - const pcre2_code *re; - int err; - PCRE2_SIZE offset; + case cond_BEGINS: + yield = Ustrncmp(exp[0], exp[1], Ustrlen(exp[1])) == 0; + break; - if ((filter_test != FTEST_NONE && debug_selector != 0) || - (debug_selector & D_filter) != 0) + case cond_ends: + case cond_ENDS: { - debug_printf_indent("Match expanded arguments:\n"); - debug_printf_indent(" Subject = %s\n", exp[0]); - debug_printf_indent(" Pattern = %s\n", exp[1]); + 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; } - if (!(re = pcre2_compile((PCRE2_SPTR)exp[1], PCRE2_ZERO_TERMINATED, - PCRE_COPT | (c->type == cond_matches ? PCRE2_CASELESS : 0), - &err, &offset, pcre_cmp_ctx))) + case cond_matches: + case cond_MATCHES: { - uschar errbuf[128]; - pcre2_get_error_message(err, errbuf, sizeof(errbuf)); - *error_pointer = string_sprintf("error while compiling " - "regular expression \"%s\": %s at offset %ld", - exp[1], errbuf, (long)offset); - return FALSE; - } + const pcre2_code * re; + mcs_flags flags = textonly_re ? MCS_CACHEABLE : MCS_NOFLAGS; - yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1); - break; - } + 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]); + } - /* For above and below, convert the strings to numbers */ + if (c->type == cond_matches) flags |= MCS_CASELESS; + if (!(re = regex_compile(exp[1], flags, error_pointer, pcre_gen_cmp_ctx))) + return FALSE; - case cond_above: - case cond_below: - for (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 = 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) || @@ -1709,7 +1740,7 @@ while (commands) switch(commands->command) { - case add_command: + case ADD_COMMAND: for (i = 0; i < 2; i++) { const uschar *ss = expargs[i]; @@ -1741,7 +1772,7 @@ while (commands) /* 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: + case DELIVER_COMMAND: for (i = 0; i < 2; i++) { s = expargs[i]; @@ -1817,7 +1848,7 @@ while (commands) } break; - case save_command: + case SAVE_COMMAND: s = expargs[0]; mode = commands->args[1].i; @@ -1860,7 +1891,7 @@ while (commands) } break; - case pipe_command: + case PIPE_COMMAND: s = string_copy(commands->args[0].u); if (filter_test != FTEST_NONE) { @@ -1910,7 +1941,7 @@ while (commands) /* Set up the file name and mode, and close any previously open file. */ - case logfile_command: + case LOGFILE_COMMAND: log_mode = commands->args[1].i; if (log_mode == -1) log_mode = 0600; if (log_fd >= 0) @@ -1926,7 +1957,7 @@ while (commands) } break; - case logwrite_command: + case LOGWRITE_COMMAND: s = expargs[0]; if (filter_test != FTEST_NONE) @@ -1985,7 +2016,7 @@ while (commands) command is rejected at parse time otherwise. However "headers charset" is always permitted. */ - case headers_command: + case HEADERS_COMMAND: { int subtype = commands->args[1].i; s = expargs[0]; @@ -1999,8 +2030,7 @@ while (commands) if (subtype == TRUE) { - while (isspace(*s)) s++; - if (*s) + if (Uskip_whitespace(&s)) { header_add(htype_other, "%s%s", s, s[Ustrlen(s)-1] == '\n' ? "" : "\n"); @@ -2030,17 +2060,17 @@ while (commands) 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: + case DEFER_COMMAND: ff_name = US"defer"; ff_ret = FF_DEFER; goto DEFERFREEZEFAIL; - case fail_command: + case FAIL_COMMAND: ff_name = US"fail"; ff_ret = FF_FAIL; goto DEFERFREEZEFAIL; - case freeze_command: + case FREEZE_COMMAND: ff_name = US"freeze"; ff_ret = FF_FREEZE; @@ -2060,7 +2090,7 @@ while (commands) DEBUG(D_filter) debug_printf_indent("Filter: %s \"%s\"\n", ff_name, fmsg); return ff_ret; - case finish_command: + case FINISH_COMMAND: if (filter_test != FTEST_NONE) { indent(); @@ -2072,7 +2102,7 @@ while (commands) finish_obeyed = TRUE; return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED; - case if_command: + case IF_COMMAND: { uschar *save_address = filter_thisaddress; int ok = FF_DELIVERED; @@ -2097,8 +2127,8 @@ while (commands) return path is unset or if a non-trusted user supplied -f <> as the return path. */ - case mail_command: - case vacation_command: + case MAIL_COMMAND: + case VACATION_COMMAND: if (!return_path || !*return_path) { if (filter_test != FTEST_NONE) @@ -2194,7 +2224,7 @@ while (commands) indent(); printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M", to ? to : US"", - commands->command == vacation_command ? " (vacation)" : "", + commands->command == VACATION_COMMAND ? " (vacation)" : "", commands->noerror ? " (noerror)" : ""); for (i = 1; i < MAILARGS_STRING_COUNT; i++) { @@ -2219,7 +2249,7 @@ while (commands) gstring * log_addr = NULL; if (!to) to = expand_string(US"$reply_address"); - while (isspace(*to)) to++; + Uskip_whitespace(&to); for (tt = to; *tt; tt++) /* Get rid of newlines */ if (*tt == '\n') @@ -2236,7 +2266,7 @@ while (commands) debug_printf_indent("Filter: %smail to: %s%s%s\n", commands->seen ? "seen " : "", to, - commands->command == vacation_command ? " (vacation)" : "", + commands->command == VACATION_COMMAND ? " (vacation)" : "", commands->noerror ? " (noerror)" : ""); for (i = 1; i < MAILARGS_STRING_COUNT; i++) { @@ -2291,7 +2321,7 @@ while (commands) /* Move on past this address */ tt = ss + (*ss ? 1 : 0); - while (isspace(*tt)) tt++; + Uskip_whitespace(&tt); } if (log_addr) @@ -2342,7 +2372,7 @@ while (commands) } break; - case testprint_command: + case TESTPRINT_COMMAND: if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0) { const uschar *s = string_printing(expargs[0]); @@ -2356,7 +2386,7 @@ while (commands) commands = commands->next; } -return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED; +return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED; } @@ -2605,3 +2635,5 @@ return yield; /* End of filter.c */ +/* vi: aw ai sw=2 +*/