Commit | Line | Data |
---|---|---|
059ec3d9 PH |
1 | /************************************************* |
2 | * Exim - an Internet mail transport agent * | |
3 | *************************************************/ | |
4 | ||
9242a7e8 | 5 | /* Copyright (c) University of Cambridge 1995 - 2017 */ |
059ec3d9 PH |
6 | /* See the file NOTICE for conditions of use and distribution. */ |
7 | ||
8 | ||
9 | /* Code for mail filtering functions. */ | |
10 | ||
11 | #include "exim.h" | |
12 | ||
13 | ||
14 | /* Command arguments and left/right points in conditions can contain different | |
15 | types of data, depending on the particular command or condition. Originally, | |
16 | (void *) was used as "any old type", with casts, but this gives trouble and | |
17 | warnings in some environments. So now it is done "properly", with a union. We | |
18 | need to declare the structures first because some of them are recursive. */ | |
19 | ||
20 | struct filter_cmd; | |
21 | struct condition_block; | |
22 | ||
23 | union argtypes { | |
24 | struct string_item *a; | |
25 | BOOL b; | |
26 | struct condition_block *c; | |
27 | struct filter_cmd *f; | |
28 | int i; | |
29 | uschar *u; | |
30 | }; | |
31 | ||
32 | /* Local structures used in this module */ | |
33 | ||
34 | typedef struct filter_cmd { | |
35 | struct filter_cmd *next; | |
36 | int command; | |
37 | BOOL seen; | |
38 | BOOL noerror; | |
39 | union argtypes args[1]; | |
40 | } filter_cmd; | |
41 | ||
42 | typedef struct condition_block { | |
43 | struct condition_block *parent; | |
44 | int type; | |
45 | BOOL testfor; | |
46 | union argtypes left; | |
47 | union argtypes right; | |
48 | } condition_block; | |
49 | ||
50 | /* Miscellaneous other declarations */ | |
51 | ||
52 | static uschar **error_pointer; | |
53 | static uschar *log_filename; | |
54 | static int filter_options; | |
55 | static int line_number; | |
56 | static int expect_endif; | |
57 | static int had_else_endif; | |
58 | static int log_fd; | |
59 | static int log_mode; | |
60 | static int output_indent; | |
61 | static BOOL filter_delivered; | |
62 | static BOOL finish_obeyed; | |
63 | static BOOL seen_force; | |
64 | static BOOL seen_value; | |
65 | static BOOL noerror_force; | |
66 | ||
67 | enum { had_neither, had_else, had_elif, had_endif }; | |
68 | ||
69 | static BOOL read_command_list(uschar **, filter_cmd ***, BOOL); | |
70 | ||
71 | ||
72 | /* The string arguments for the mail command. The header line ones (that are | |
73 | permitted to include \n followed by white space) first, and then the body text | |
74 | one (it can have \n anywhere). Then the file names and once_repeat, which may | |
75 | not contain \n. */ | |
76 | ||
1ba28e2b PP |
77 | static const char *mailargs[] = { /* "to" must be first, and */ |
78 | "to", /* "cc" and "bcc" must follow */ | |
059ec3d9 PH |
79 | "cc", |
80 | "bcc", | |
81 | "from", | |
82 | "reply_to", | |
83 | "subject", | |
84 | "extra_headers", /* miscellaneous added header lines */ | |
85 | "text", | |
86 | "file", | |
87 | "log", | |
88 | "once", | |
89 | "once_repeat" | |
90 | }; | |
91 | ||
92 | /* The count of string arguments */ | |
93 | ||
5903c6ff | 94 | #define MAILARGS_STRING_COUNT (nelem(mailargs)) |
059ec3d9 PH |
95 | |
96 | /* The count of string arguments that are actually passed over as strings | |
97 | (once_repeat is converted to an int). */ | |
98 | ||
99 | #define mailargs_string_passed (MAILARGS_STRING_COUNT - 1) | |
100 | ||
101 | /* This defines the offsets for the arguments; first the string ones, and | |
102 | then the non-string ones. The order must be as above. */ | |
103 | ||
104 | enum { mailarg_index_to, | |
105 | mailarg_index_cc, | |
106 | mailarg_index_bcc, | |
107 | mailarg_index_from, | |
108 | mailarg_index_reply_to, | |
109 | mailarg_index_subject, | |
110 | mailarg_index_headers, /* misc headers must be last */ | |
111 | mailarg_index_text, /* text is first after headers */ | |
112 | mailarg_index_file, /* between text and expand are filenames */ | |
113 | mailarg_index_log, | |
114 | mailarg_index_once, | |
115 | mailarg_index_once_repeat, /* a time string */ | |
116 | mailarg_index_expand, /* first non-string argument */ | |
117 | mailarg_index_return, | |
118 | mailargs_total /* total number of arguments */ | |
119 | }; | |
120 | ||
121 | /* Offsets in the data structure for the string arguments (note that | |
122 | once_repeat isn't a string argument at this point.) */ | |
123 | ||
124 | static int reply_offsets[] = { /* must be in same order as above */ | |
125 | offsetof(reply_item, to), | |
126 | offsetof(reply_item, cc), | |
127 | offsetof(reply_item, bcc), | |
128 | offsetof(reply_item, from), | |
129 | offsetof(reply_item, reply_to), | |
130 | offsetof(reply_item, subject), | |
131 | offsetof(reply_item, headers), | |
132 | offsetof(reply_item, text), | |
133 | offsetof(reply_item, file), | |
134 | offsetof(reply_item, logfile), | |
135 | offsetof(reply_item, oncelog), | |
136 | }; | |
137 | ||
138 | /* Condition identities and names, with negated versions for some | |
139 | of them. */ | |
140 | ||
141 | enum { cond_and, cond_or, cond_personal, cond_begins, cond_BEGINS, | |
142 | cond_ends, cond_ENDS, cond_is, cond_IS, cond_matches, | |
143 | cond_MATCHES, cond_contains, cond_CONTAINS, cond_delivered, | |
144 | cond_above, cond_below, cond_errormsg, cond_firsttime, | |
145 | cond_manualthaw, cond_foranyaddress }; | |
146 | ||
1ba28e2b | 147 | static const char *cond_names[] = { |
059ec3d9 PH |
148 | "and", "or", "personal", |
149 | "begins", "BEGINS", "ends", "ENDS", | |
150 | "is", "IS", "matches", "MATCHES", "contains", | |
151 | "CONTAINS", "delivered", "above", "below", "error_message", | |
152 | "first_delivery", "manually_thawed", "foranyaddress" }; | |
153 | ||
1ba28e2b | 154 | static const char *cond_not_names[] = { |
059ec3d9 PH |
155 | "", "", "not personal", |
156 | "does not begin", "does not BEGIN", | |
157 | "does not end", "does not END", | |
158 | "is not", "IS not", "does not match", | |
159 | "does not MATCH", "does not contain", "does not CONTAIN", | |
160 | "not delivered", "not above", "not below", "not error_message", | |
161 | "not first_delivery", "not manually_thawed", "not foranyaddress" }; | |
162 | ||
163 | /* Tables of binary condition words and their corresponding types. Not easy | |
164 | to amalgamate with the above because of the different variants. */ | |
165 | ||
1ba28e2b | 166 | static const char *cond_words[] = { |
059ec3d9 PH |
167 | "BEGIN", |
168 | "BEGINS", | |
169 | "CONTAIN", | |
170 | "CONTAINS", | |
171 | "END", | |
172 | "ENDS", | |
173 | "IS", | |
174 | "MATCH", | |
175 | "MATCHES", | |
176 | "above", | |
177 | "begin", | |
178 | "begins", | |
179 | "below", | |
180 | "contain", | |
181 | "contains", | |
182 | "end", | |
183 | "ends", | |
184 | "is", | |
185 | "match", | |
186 | "matches"}; | |
187 | ||
5903c6ff | 188 | static int cond_word_count = nelem(cond_words); |
059ec3d9 PH |
189 | |
190 | static int cond_types[] = { cond_BEGINS, cond_BEGINS, cond_CONTAINS, | |
191 | cond_CONTAINS, cond_ENDS, cond_ENDS, cond_IS, cond_MATCHES, cond_MATCHES, | |
192 | cond_above, cond_begins, cond_begins, cond_below, cond_contains, | |
193 | cond_contains, cond_ends, cond_ends, cond_is, cond_matches, cond_matches }; | |
194 | ||
195 | /* Command identities: must be kept in step with the list of command words | |
196 | and the list of expanded argument counts which follow. */ | |
197 | ||
198 | enum { add_command, defer_command, deliver_command, elif_command, else_command, | |
199 | endif_command, finish_command, fail_command, freeze_command, | |
200 | headers_command, if_command, logfile_command, logwrite_command, | |
201 | mail_command, noerror_command, pipe_command, save_command, seen_command, | |
202 | testprint_command, unseen_command, vacation_command }; | |
203 | ||
1ba28e2b | 204 | static const char *command_list[] = { |
059ec3d9 PH |
205 | "add", "defer", "deliver", "elif", "else", "endif", "finish", |
206 | "fail", "freeze", "headers", "if", "logfile", "logwrite", "mail", | |
207 | "noerror", "pipe", "save", "seen", "testprint", "unseen", "vacation" | |
208 | }; | |
209 | ||
5903c6ff | 210 | static int command_list_count = nelem(command_list); |
059ec3d9 PH |
211 | |
212 | /* This table contains the number of expanded arguments in the bottom 4 bits. | |
213 | If the top bit is set, it means that the default for the command is "seen". */ | |
214 | ||
215 | static uschar command_exparg_count[] = { | |
216 | 2, /* add */ | |
217 | 1, /* defer */ | |
218 | 128+2, /* deliver */ | |
219 | 0, /* elif */ | |
220 | 0, /* else */ | |
221 | 0, /* endif */ | |
222 | 0, /* finish */ | |
223 | 1, /* fail */ | |
224 | 1, /* freeze */ | |
225 | 1, /* headers */ | |
226 | 0, /* if */ | |
227 | 1, /* logfile */ | |
228 | 1, /* logwrite */ | |
229 | MAILARGS_STRING_COUNT, /* mail */ | |
230 | 0, /* noerror */ | |
231 | 128+0, /* pipe */ | |
232 | 128+1, /* save */ | |
233 | 0, /* seen */ | |
234 | 1, /* testprint */ | |
235 | 0, /* unseen */ | |
236 | MAILARGS_STRING_COUNT /* vacation */ | |
237 | }; | |
238 | ||
239 | ||
240 | ||
241 | /************************************************* | |
242 | * Find next significant uschar * | |
243 | *************************************************/ | |
244 | ||
245 | /* Function to skip over white space and, optionally, comments. | |
246 | ||
247 | Arguments: | |
248 | ptr pointer to next character | |
249 | comment_allowed if TRUE, comments (# to \n) are skipped | |
250 | ||
251 | Returns: pointer to next non-whitespace character | |
252 | */ | |
253 | ||
254 | static uschar * | |
255 | nextsigchar(uschar *ptr, BOOL comment_allowed) | |
256 | { | |
257 | for (;;) | |
258 | { | |
259 | while (isspace(*ptr)) | |
260 | { | |
261 | if (*ptr == '\n') line_number++; | |
262 | ptr++; | |
263 | } | |
264 | if (comment_allowed && *ptr == '#') | |
265 | { | |
266 | while (*(++ptr) != '\n' && *ptr != 0); | |
267 | continue; | |
268 | } | |
269 | else break; | |
270 | } | |
271 | return ptr; | |
272 | } | |
273 | ||
274 | ||
275 | ||
276 | /************************************************* | |
277 | * Read one word * | |
278 | *************************************************/ | |
279 | ||
280 | /* The terminator is white space unless bracket is TRUE, in which | |
281 | case ( and ) terminate. | |
282 | ||
283 | Arguments | |
284 | ptr pointer to next character | |
285 | buffer where to put the word | |
286 | size size of buffer | |
287 | bracket if TRUE, terminate on ( and ) as well as space | |
288 | ||
289 | Returns: pointer to the next significant character after the word | |
290 | */ | |
291 | ||
292 | static uschar * | |
293 | nextword(uschar *ptr, uschar *buffer, int size, BOOL bracket) | |
294 | { | |
295 | uschar *bp = buffer; | |
296 | while (*ptr != 0 && !isspace(*ptr) && | |
297 | (!bracket || (*ptr != '(' && *ptr != ')'))) | |
298 | { | |
299 | if (bp - buffer < size - 1) *bp++ = *ptr++; else | |
300 | { | |
301 | *error_pointer = string_sprintf("word is too long in line %d of " | |
302 | "filter file (max = %d chars)", line_number, size); | |
303 | break; | |
304 | } | |
305 | } | |
306 | *bp = 0; | |
307 | return nextsigchar(ptr, TRUE); | |
308 | } | |
309 | ||
310 | ||
311 | ||
312 | /************************************************* | |
313 | * Read one item * | |
314 | *************************************************/ | |
315 | ||
316 | /* Might be a word, or might be a quoted string; in the latter case | |
317 | do the escape stuff. | |
318 | ||
319 | Arguments: | |
320 | ptr pointer to next character | |
321 | buffer where to put the item | |
322 | size size of buffer | |
323 | bracket if TRUE, terminate non-quoted on ( and ) as well as space | |
324 | ||
325 | Returns: the next significant character after the item | |
326 | */ | |
327 | ||
328 | static uschar * | |
329 | nextitem(uschar *ptr, uschar *buffer, int size, BOOL bracket) | |
330 | { | |
331 | uschar *bp = buffer; | |
332 | if (*ptr != '\"') return nextword(ptr, buffer, size, bracket); | |
333 | ||
334 | while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n') | |
335 | { | |
336 | if (bp - buffer >= size - 1) | |
337 | { | |
338 | *error_pointer = string_sprintf("string is too long in line %d of " | |
339 | "filter file (max = %d chars)", line_number, size); | |
340 | break; | |
341 | } | |
342 | ||
343 | if (*ptr != '\\') *bp++ = *ptr; else | |
344 | { | |
345 | if (isspace(ptr[1])) /* \<whitespace>NL<whitespace> ignored */ | |
346 | { | |
347 | uschar *p = ptr + 1; | |
348 | while (*p != '\n' && isspace(*p)) p++; | |
349 | if (*p == '\n') | |
350 | { | |
351 | line_number++; | |
352 | ptr = p; | |
353 | while (ptr[1] != '\n' && isspace(ptr[1])) ptr++; | |
354 | continue; | |
355 | } | |
356 | } | |
357 | ||
55414b25 | 358 | *bp++ = string_interpret_escape(CUSS &ptr); |
059ec3d9 PH |
359 | } |
360 | } | |
361 | ||
362 | if (*ptr == '\"') ptr++; | |
363 | else if (*error_pointer == NULL) | |
364 | *error_pointer = string_sprintf("quote missing at end of string " | |
365 | "in line %d", line_number); | |
366 | ||
367 | *bp = 0; | |
368 | return nextsigchar(ptr, TRUE); | |
369 | } | |
370 | ||
371 | ||
372 | ||
373 | ||
374 | /************************************************* | |
375 | * Convert a string + K|M to a number * | |
376 | *************************************************/ | |
377 | ||
378 | /* | |
379 | Arguments: | |
380 | s points to text string | |
381 | OK set TRUE if a valid number was read | |
382 | ||
383 | Returns: the number, or 0 on error (with *OK FALSE) | |
384 | */ | |
385 | ||
386 | static int | |
387 | get_number(uschar *s, BOOL *ok) | |
388 | { | |
389 | int value, count; | |
390 | *ok = FALSE; | |
391 | if (sscanf(CS s, "%i%n", &value, &count) != 1) return 0; | |
392 | if (tolower(s[count]) == 'k') { value *= 1024; count++; } | |
393 | if (tolower(s[count]) == 'm') { value *= 1024*1024; count++; } | |
394 | while (isspace((s[count]))) count++; | |
395 | if (s[count] != 0) return 0; | |
396 | *ok = TRUE; | |
397 | return value; | |
398 | } | |
399 | ||
400 | ||
401 | ||
402 | /************************************************* | |
403 | * Read one condition * | |
404 | *************************************************/ | |
405 | ||
406 | /* A complete condition must be terminated by "then"; bracketed internal | |
407 | conditions must be terminated by a closing bracket. They are read by calling | |
408 | this function recursively. | |
409 | ||
410 | Arguments: | |
411 | ptr points to start of condition | |
412 | condition_block where to hang the created condition block | |
413 | toplevel TRUE when called at the top level | |
414 | ||
415 | Returns: points to next character after "then" | |
416 | */ | |
417 | ||
418 | static uschar * | |
419 | read_condition(uschar *ptr, condition_block **cond, BOOL toplevel) | |
420 | { | |
421 | uschar buffer[1024]; | |
422 | BOOL testfor = TRUE; | |
423 | condition_block *current_parent = NULL; | |
424 | condition_block **current = cond; | |
425 | ||
426 | *current = NULL; | |
427 | ||
428 | /* Loop to read next condition */ | |
429 | ||
430 | for (;;) | |
431 | { | |
432 | condition_block *c; | |
433 | ||
434 | /* reaching the end of the input is an error. */ | |
435 | ||
436 | if (*ptr == 0) | |
437 | { | |
438 | *error_pointer = string_sprintf("\"then\" missing at end of filter file"); | |
439 | break; | |
440 | } | |
441 | ||
442 | /* Opening bracket at the start of a condition introduces a nested | |
443 | condition, which must be terminated by a closing bracket. */ | |
444 | ||
445 | if (*ptr == '(') | |
446 | { | |
447 | ptr = read_condition(nextsigchar(ptr+1, TRUE), &c, FALSE); | |
448 | if (*error_pointer != NULL) break; | |
449 | if (*ptr != ')') | |
450 | { | |
451 | *error_pointer = string_sprintf("expected \")\" in line %d of " | |
452 | "filter file", line_number); | |
453 | break; | |
454 | } | |
455 | if (!testfor) | |
456 | { | |
457 | c->testfor = !c->testfor; | |
458 | testfor = TRUE; | |
459 | } | |
460 | ptr = nextsigchar(ptr+1, TRUE); | |
461 | } | |
462 | ||
463 | ||
464 | /* Closing bracket at the start of a condition is an error. Give an | |
465 | explicit message, as otherwise "unknown condition" would be confusing. */ | |
466 | ||
467 | else if (*ptr == ')') | |
468 | { | |
469 | *error_pointer = string_sprintf("unexpected \")\" in line %d of " | |
470 | "filter file", line_number); | |
471 | break; | |
472 | } | |
473 | ||
474 | /* Otherwise we expect a word or a string. */ | |
475 | ||
476 | else | |
477 | { | |
478 | ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); | |
479 | if (*error_pointer != NULL) break; | |
480 | ||
481 | /* "Then" at the start of a condition is an error */ | |
482 | ||
483 | if (Ustrcmp(buffer, "then") == 0) | |
484 | { | |
485 | *error_pointer = string_sprintf("unexpected \"then\" near line %d of " | |
486 | "filter file", line_number); | |
487 | break; | |
488 | } | |
489 | ||
490 | /* "Not" at the start of a condition negates the testing condition. */ | |
491 | ||
492 | if (Ustrcmp(buffer, "not") == 0) | |
493 | { | |
494 | testfor = !testfor; | |
495 | continue; | |
496 | } | |
497 | ||
498 | /* Build a condition block from the specific word. */ | |
499 | ||
500 | c = store_get(sizeof(condition_block)); | |
501 | c->left.u = c->right.u = NULL; | |
502 | c->testfor = testfor; | |
503 | testfor = TRUE; | |
504 | ||
505 | /* Check for conditions that start with a keyword */ | |
506 | ||
507 | if (Ustrcmp(buffer, "delivered") == 0) c->type = cond_delivered; | |
508 | else if (Ustrcmp(buffer, "error_message") == 0) c->type = cond_errormsg; | |
509 | else if (Ustrcmp(buffer, "first_delivery") == 0) c->type = cond_firsttime; | |
510 | else if (Ustrcmp(buffer, "manually_thawed") == 0) c->type = cond_manualthaw; | |
511 | ||
512 | /* Personal can be followed by any number of aliases */ | |
513 | ||
514 | else if (Ustrcmp(buffer, "personal") == 0) | |
515 | { | |
516 | c->type = cond_personal; | |
517 | for (;;) | |
518 | { | |
519 | string_item *aa; | |
520 | uschar *saveptr = ptr; | |
521 | ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); | |
4dc2379a | 522 | if (*error_pointer) break; |
059ec3d9 PH |
523 | if (Ustrcmp(buffer, "alias") != 0) |
524 | { | |
525 | ptr = saveptr; | |
526 | break; | |
527 | } | |
528 | ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); | |
4dc2379a | 529 | if (*error_pointer) break; |
059ec3d9 PH |
530 | aa = store_get(sizeof(string_item)); |
531 | aa->text = string_copy(buffer); | |
532 | aa->next = c->left.a; | |
533 | c->left.a = aa; | |
534 | } | |
535 | } | |
536 | ||
537 | /* Foranyaddress must be followed by a string and a condition enclosed | |
538 | in parentheses, which is handled as a subcondition. */ | |
539 | ||
540 | else if (Ustrcmp(buffer, "foranyaddress") == 0) | |
541 | { | |
542 | ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); | |
4dc2379a | 543 | if (*error_pointer) break; |
059ec3d9 PH |
544 | if (*ptr != '(') |
545 | { | |
546 | *error_pointer = string_sprintf("\"(\" expected after \"foranyaddress\" " | |
547 | "near line %d of filter file", line_number); | |
548 | break; | |
549 | } | |
550 | ||
551 | c->type = cond_foranyaddress; | |
552 | c->left.u = string_copy(buffer); | |
553 | ||
554 | ptr = read_condition(nextsigchar(ptr+1, TRUE), &(c->right.c), FALSE); | |
4dc2379a | 555 | if (*error_pointer) break; |
059ec3d9 PH |
556 | if (*ptr != ')') |
557 | { | |
558 | *error_pointer = string_sprintf("expected \")\" in line %d of " | |
559 | "filter file", line_number); | |
560 | break; | |
561 | } | |
059ec3d9 PH |
562 | ptr = nextsigchar(ptr+1, TRUE); |
563 | } | |
564 | ||
565 | /* If it's not a word we recognize, then it must be the lefthand | |
566 | operand of one of the comparison words. */ | |
567 | ||
568 | else | |
569 | { | |
570 | int i; | |
571 | uschar *isptr = NULL; | |
572 | ||
573 | c->left.u = string_copy(buffer); | |
574 | ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); | |
4dc2379a | 575 | if (*error_pointer) break; |
059ec3d9 PH |
576 | |
577 | /* Handle "does|is [not]", preserving the pointer after "is" in | |
578 | case it isn't that, but the form "is <string>". */ | |
579 | ||
580 | if (strcmpic(buffer, US"does") == 0 || strcmpic(buffer, US"is") == 0) | |
581 | { | |
582 | if (buffer[0] == 'i') { c->type = cond_is; isptr = ptr; } | |
583 | if (buffer[0] == 'I') { c->type = cond_IS; isptr = ptr; } | |
584 | ||
585 | ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); | |
4dc2379a | 586 | if (*error_pointer) break; |
059ec3d9 PH |
587 | if (strcmpic(buffer, US"not") == 0) |
588 | { | |
589 | c->testfor = !c->testfor; | |
4dc2379a | 590 | if (isptr) isptr = ptr; |
059ec3d9 | 591 | ptr = nextword(ptr, buffer, sizeof(buffer), TRUE); |
4dc2379a | 592 | if (*error_pointer) break; |
059ec3d9 PH |
593 | } |
594 | } | |
595 | ||
596 | for (i = 0; i < cond_word_count; i++) | |
597 | { | |
598 | if (Ustrcmp(buffer, cond_words[i]) == 0) | |
599 | { | |
600 | c->type = cond_types[i]; | |
601 | break; | |
602 | } | |
603 | } | |
604 | ||
605 | /* If an unknown word follows "is" or "is not" | |
606 | it's actually the argument. Reset to read it. */ | |
607 | ||
608 | if (i >= cond_word_count) | |
609 | { | |
4dc2379a | 610 | if (!isptr) |
059ec3d9 PH |
611 | { |
612 | *error_pointer = string_sprintf("unrecognized condition word \"%s\" " | |
613 | "near line %d of filter file", buffer, line_number); | |
614 | break; | |
615 | } | |
4dc2379a | 616 | ptr = isptr; |
059ec3d9 PH |
617 | } |
618 | ||
619 | /* Get the RH argument. */ | |
620 | ||
621 | ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE); | |
4dc2379a | 622 | if (*error_pointer) break; |
059ec3d9 PH |
623 | c->right.u = string_copy(buffer); |
624 | } | |
625 | } | |
626 | ||
627 | /* We have read some new condition and set it up in the condition block | |
628 | c; point the current pointer at it, and then deal with what follows. */ | |
629 | ||
630 | *current = c; | |
631 | ||
632 | /* Closing bracket terminates if this is a lower-level condition. Otherwise | |
633 | it is unexpected. */ | |
634 | ||
635 | if (*ptr == ')') | |
636 | { | |
637 | if (toplevel) | |
638 | *error_pointer = string_sprintf("unexpected \")\" in line %d of " | |
639 | "filter file", line_number); | |
640 | break; | |
641 | } | |
642 | ||
643 | /* Opening bracket following a condition is an error; give an explicit | |
644 | message to make it clearer what is wrong. */ | |
645 | ||
646 | else if (*ptr == '(') | |
647 | { | |
648 | *error_pointer = string_sprintf("unexpected \"(\" in line %d of " | |
649 | "filter file", line_number); | |
650 | break; | |
651 | } | |
652 | ||
653 | /* Otherwise the next thing must be one of the words "and", "or" or "then" */ | |
654 | ||
655 | else | |
656 | { | |
657 | uschar *saveptr = ptr; | |
658 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
4dc2379a | 659 | if (*error_pointer) break; |
059ec3d9 PH |
660 | |
661 | /* "Then" terminates a toplevel condition; otherwise a closing bracket | |
662 | has been omitted. Put a string terminator at the start of "then" so | |
663 | that reflecting the condition can be done when testing. */ | |
664 | ||
665 | if (Ustrcmp(buffer, "then") == 0) | |
666 | { | |
667 | if (toplevel) *saveptr = 0; | |
4dc2379a | 668 | else *error_pointer = string_sprintf("missing \")\" at end of " |
059ec3d9 PH |
669 | "condition near line %d of filter file", line_number); |
670 | break; | |
671 | } | |
672 | ||
673 | /* "And" causes a new condition block to replace the one we have | |
674 | just read, which becomes the left sub-condition. The current pointer | |
675 | is reset to the pointer for the right sub-condition. We have to keep | |
676 | track of the tree of sequential "ands", so as to traverse back up it | |
677 | if an "or" is met. */ | |
678 | ||
679 | else if (Ustrcmp(buffer, "and") == 0) | |
680 | { | |
681 | condition_block *andc = store_get(sizeof(condition_block)); | |
682 | andc->parent = current_parent; | |
683 | andc->type = cond_and; | |
684 | andc->testfor = TRUE; | |
685 | andc->left.c = c; | |
686 | andc->right.u = NULL; /* insurance */ | |
687 | *current = andc; | |
688 | current = &(andc->right.c); | |
689 | current_parent = andc; | |
690 | } | |
691 | ||
692 | /* "Or" is similar, but has to be done a bit more carefully to | |
693 | ensure that "and" is more binding. If there's a parent set, we | |
694 | are following a sequence of "and"s and must track back to their | |
695 | start. */ | |
696 | ||
697 | else if (Ustrcmp(buffer, "or") == 0) | |
698 | { | |
699 | condition_block *orc = store_get(sizeof(condition_block)); | |
700 | condition_block *or_parent = NULL; | |
701 | ||
4dc2379a | 702 | if (current_parent) |
059ec3d9 | 703 | { |
4dc2379a | 704 | while (current_parent->parent && |
059ec3d9 PH |
705 | current_parent->parent->type == cond_and) |
706 | current_parent = current_parent->parent; | |
707 | ||
708 | /* If the parent has a parent, it must be an "or" parent. */ | |
709 | ||
4dc2379a | 710 | if (current_parent->parent) |
059ec3d9 PH |
711 | or_parent = current_parent->parent; |
712 | } | |
713 | ||
714 | orc->parent = or_parent; | |
4dc2379a JH |
715 | if (!or_parent) *cond = orc; |
716 | else or_parent->right.c = orc; | |
059ec3d9 PH |
717 | orc->type = cond_or; |
718 | orc->testfor = TRUE; | |
719 | orc->left.c = (current_parent == NULL)? c : current_parent; | |
720 | orc->right.c = NULL; /* insurance */ | |
721 | current = &(orc->right.c); | |
722 | current_parent = orc; | |
723 | } | |
724 | ||
725 | /* Otherwise there is a disaster */ | |
726 | ||
727 | else | |
728 | { | |
729 | *error_pointer = string_sprintf("\"and\" or \"or\" or \"%s\" " | |
730 | "expected near line %d of filter file, but found \"%s\"", | |
731 | toplevel? "then" : ")", line_number, buffer); | |
732 | break; | |
733 | } | |
734 | } | |
735 | } | |
736 | ||
737 | return nextsigchar(ptr, TRUE); | |
738 | } | |
739 | ||
740 | ||
741 | ||
742 | /************************************************* | |
4c04137d | 743 | * Output the current indent * |
059ec3d9 PH |
744 | *************************************************/ |
745 | ||
746 | static void | |
747 | indent(void) | |
748 | { | |
749 | int i; | |
750 | for (i = 0; i < output_indent; i++) debug_printf(" "); | |
751 | } | |
752 | ||
753 | ||
754 | ||
755 | /************************************************* | |
756 | * Condition printer: for debugging * | |
757 | *************************************************/ | |
758 | ||
759 | /* | |
760 | Arguments: | |
761 | c the block at the top of the tree | |
762 | toplevel TRUE at toplevel - stops overall brackets | |
763 | ||
764 | Returns: nothing | |
765 | */ | |
766 | ||
767 | static void | |
768 | print_condition(condition_block *c, BOOL toplevel) | |
769 | { | |
1ba28e2b | 770 | const char *name = (c->testfor)? cond_names[c->type] : cond_not_names[c->type]; |
059ec3d9 PH |
771 | switch(c->type) |
772 | { | |
773 | case cond_personal: | |
774 | case cond_delivered: | |
775 | case cond_errormsg: | |
776 | case cond_firsttime: | |
777 | case cond_manualthaw: | |
778 | debug_printf("%s", name); | |
779 | break; | |
780 | ||
781 | case cond_is: | |
782 | case cond_IS: | |
783 | case cond_matches: | |
784 | case cond_MATCHES: | |
785 | case cond_contains: | |
786 | case cond_CONTAINS: | |
787 | case cond_begins: | |
788 | case cond_BEGINS: | |
789 | case cond_ends: | |
790 | case cond_ENDS: | |
791 | case cond_above: | |
792 | case cond_below: | |
1ba28e2b | 793 | debug_printf("%s %s %s", c->left.u, name, c->right.u); |
059ec3d9 PH |
794 | break; |
795 | ||
796 | case cond_and: | |
797 | if (!c->testfor) debug_printf("not ("); | |
798 | print_condition(c->left.c, FALSE); | |
799 | debug_printf(" %s ", cond_names[c->type]); | |
800 | print_condition(c->right.c, FALSE); | |
801 | if (!c->testfor) debug_printf(")"); | |
802 | break; | |
803 | ||
804 | case cond_or: | |
805 | if (!c->testfor) debug_printf("not ("); | |
806 | else if (!toplevel) debug_printf("("); | |
807 | print_condition(c->left.c, FALSE); | |
808 | debug_printf(" %s ", cond_names[c->type]); | |
809 | print_condition(c->right.c, FALSE); | |
810 | if (!toplevel || !c->testfor) debug_printf(")"); | |
811 | break; | |
812 | ||
813 | case cond_foranyaddress: | |
814 | debug_printf("%s %s (", name, c->left.u); | |
815 | print_condition(c->right.c, FALSE); | |
816 | debug_printf(")"); | |
817 | break; | |
818 | } | |
819 | } | |
820 | ||
821 | ||
822 | ||
823 | ||
824 | /************************************************* | |
825 | * Read one filtering command * | |
826 | *************************************************/ | |
827 | ||
828 | /* | |
829 | Arguments: | |
830 | pptr points to pointer to first character of command; the pointer | |
831 | is updated to point after the last character read | |
832 | lastcmdptr points to pointer to pointer to last command; used for hanging | |
833 | on the newly read command | |
834 | ||
835 | Returns: TRUE if command successfully read, else FALSE | |
836 | */ | |
837 | ||
838 | static BOOL | |
839 | read_command(uschar **pptr, filter_cmd ***lastcmdptr) | |
840 | { | |
841 | int command, i, cmd_bit; | |
842 | filter_cmd *new, **newlastcmdptr; | |
843 | BOOL yield = TRUE; | |
844 | BOOL was_seen_or_unseen = FALSE; | |
845 | BOOL was_noerror = FALSE; | |
846 | uschar buffer[1024]; | |
847 | uschar *ptr = *pptr; | |
848 | uschar *saveptr; | |
849 | uschar *fmsg = NULL; | |
850 | ||
851 | /* Read the next word and find which command it is. Command words are normally | |
852 | terminated by white space, but there are two exceptions, which are the "if" and | |
853 | "elif" commands. We must allow for them to be terminated by an opening bracket, | |
854 | as brackets are allowed in conditions and users will expect not to require | |
855 | white space here. */ | |
856 | ||
857 | if (Ustrncmp(ptr, "if(", 3) == 0) | |
858 | { | |
859 | Ustrcpy(buffer, "if"); | |
860 | ptr += 2; | |
861 | } | |
862 | else if (Ustrncmp(ptr, "elif(", 5) == 0) | |
863 | { | |
864 | Ustrcpy(buffer, "elif"); | |
865 | ptr += 4; | |
866 | } | |
867 | else | |
868 | { | |
869 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
870 | if (*error_pointer != NULL) return FALSE; | |
871 | } | |
872 | ||
873 | for (command = 0; command < command_list_count; command++) | |
874 | if (Ustrcmp(buffer, command_list[command]) == 0) break; | |
875 | ||
876 | /* Handle the individual commands */ | |
877 | ||
878 | switch (command) | |
879 | { | |
880 | /* Add takes two arguments, separated by the word "to". Headers has two | |
881 | arguments, but the first must be "add", "remove", or "charset", and it gets | |
882 | stored in the second argument slot. Neither may be preceded by seen, unseen | |
883 | or noerror. */ | |
884 | ||
885 | case add_command: | |
886 | case headers_command: | |
887 | if (seen_force || noerror_force) | |
888 | { | |
889 | *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " | |
890 | "found before an \"%s\" command near line %d", | |
891 | command_list[command], line_number); | |
892 | yield = FALSE; | |
893 | } | |
894 | /* Fall through */ | |
895 | ||
896 | /* Logwrite, logfile, pipe, and testprint all take a single argument, save | |
897 | and logfile can have an option second argument for the mode, and deliver can | |
898 | have "errors_to <address>" in a system filter, or in a user filter if the | |
899 | address is the current one. */ | |
900 | ||
901 | case deliver_command: | |
902 | case logfile_command: | |
903 | case logwrite_command: | |
904 | case pipe_command: | |
905 | case save_command: | |
906 | case testprint_command: | |
907 | ||
908 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
909 | if (*buffer == 0) | |
910 | *error_pointer = string_sprintf("\"%s\" requires an argument " | |
911 | "near line %d of filter file", command_list[command], line_number); | |
912 | ||
913 | if (*error_pointer != NULL) yield = FALSE; else | |
914 | { | |
915 | union argtypes argument, second_argument; | |
916 | ||
917 | argument.u = second_argument.u = NULL; | |
918 | ||
919 | if (command == add_command) | |
920 | { | |
921 | argument.u = string_copy(buffer); | |
922 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
923 | if (*buffer == 0 || Ustrcmp(buffer, "to") != 0) | |
924 | *error_pointer = string_sprintf("\"to\" expected in \"add\" command " | |
925 | "near line %d of filter file", line_number); | |
926 | else | |
927 | { | |
928 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
929 | if (*buffer == 0) | |
930 | *error_pointer = string_sprintf("value missing after \"to\" " | |
931 | "near line %d of filter file", line_number); | |
932 | else second_argument.u = string_copy(buffer); | |
933 | } | |
934 | } | |
935 | ||
936 | else if (command == headers_command) | |
937 | { | |
938 | if (Ustrcmp(buffer, "add") == 0) | |
939 | second_argument.b = TRUE; | |
940 | else | |
941 | if (Ustrcmp(buffer, "remove") == 0) second_argument.b = FALSE; | |
942 | else | |
943 | if (Ustrcmp(buffer, "charset") == 0) | |
944 | second_argument.b = TRUE_UNSET; | |
945 | else | |
946 | { | |
947 | *error_pointer = string_sprintf("\"add\", \"remove\", or \"charset\" " | |
948 | "expected after \"headers\" near line %d of filter file", | |
949 | line_number); | |
950 | yield = FALSE; | |
951 | } | |
952 | ||
953 | if (!system_filtering && second_argument.b != TRUE_UNSET) | |
954 | { | |
955 | *error_pointer = string_sprintf("header addition and removal is " | |
956 | "available only in system filters: near line %d of filter file", | |
957 | line_number); | |
958 | yield = FALSE; | |
959 | break; | |
960 | } | |
961 | ||
962 | if (yield) | |
963 | { | |
964 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
965 | if (*buffer == 0) | |
966 | *error_pointer = string_sprintf("value missing after \"add\", " | |
967 | "\"remove\", or \"charset\" near line %d of filter file", | |
968 | line_number); | |
969 | else argument.u = string_copy(buffer); | |
970 | } | |
971 | } | |
972 | ||
973 | /* The argument for the logwrite command must end in a newline, and the save | |
974 | and logfile commands can have an optional mode argument. The deliver | |
975 | command can have an optional "errors_to <address>" for a system filter, | |
976 | or for a user filter if the address is the user's address. Accept the | |
977 | syntax here - the check is later. */ | |
978 | ||
979 | else | |
980 | { | |
981 | if (command == logwrite_command) | |
982 | { | |
983 | int len = Ustrlen(buffer); | |
984 | if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, "\n"); | |
985 | } | |
986 | ||
987 | argument.u = string_copy(buffer); | |
988 | ||
989 | if (command == save_command || command == logfile_command) | |
990 | { | |
991 | if (isdigit(*ptr)) | |
992 | { | |
993 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
994 | second_argument.i = (int)Ustrtol(buffer, NULL, 8); | |
995 | } | |
996 | else second_argument.i = -1; | |
997 | } | |
998 | ||
999 | else if (command == deliver_command) | |
1000 | { | |
1001 | uschar *save_ptr = ptr; | |
1002 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
1003 | if (Ustrcmp(buffer, "errors_to") == 0) | |
1004 | { | |
1005 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
1006 | second_argument.u = string_copy(buffer); | |
1007 | } | |
1008 | else ptr = save_ptr; | |
1009 | } | |
1010 | } | |
1011 | ||
1012 | /* Set up the command block. Seen defaults TRUE for delivery commands, | |
1013 | FALSE for logging commands, and it doesn't matter for testprint, as | |
1014 | that doesn't change the "delivered" status. */ | |
1015 | ||
1016 | if (*error_pointer != NULL) yield = FALSE; else | |
1017 | { | |
1018 | new = store_get(sizeof(filter_cmd) + sizeof(union argtypes)); | |
1019 | new->next = NULL; | |
1020 | **lastcmdptr = new; | |
1021 | *lastcmdptr = &(new->next); | |
1022 | new->command = command; | |
1023 | new->seen = seen_force? seen_value : command_exparg_count[command] >= 128; | |
1024 | new->noerror = noerror_force; | |
1025 | new->args[0] = argument; | |
1026 | new->args[1] = second_argument; | |
1027 | } | |
1028 | } | |
1029 | break; | |
1030 | ||
1031 | ||
1032 | /* Elif, else and endif just set a flag if expected. */ | |
1033 | ||
1034 | case elif_command: | |
1035 | case else_command: | |
1036 | case endif_command: | |
41609df5 PH |
1037 | if (seen_force || noerror_force) |
1038 | { | |
1039 | *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " | |
1040 | "near line %d is not followed by a command", line_number); | |
1041 | yield = FALSE; | |
1042 | } | |
1043 | ||
059ec3d9 PH |
1044 | if (expect_endif > 0) |
1045 | had_else_endif = (command == elif_command)? had_elif : | |
1046 | (command == else_command)? had_else : had_endif; | |
1047 | else | |
1048 | { | |
1049 | *error_pointer = string_sprintf("unexpected \"%s\" command near " | |
1050 | "line %d of filter file", buffer, line_number); | |
1051 | yield = FALSE; | |
1052 | } | |
1053 | break; | |
1054 | ||
1055 | ||
1056 | /* Defer, freeze, and fail are available only if permitted. */ | |
1057 | ||
1058 | case defer_command: | |
1059 | cmd_bit = RDO_DEFER; | |
1060 | goto DEFER_FREEZE_FAIL; | |
1061 | ||
1062 | case fail_command: | |
1063 | cmd_bit = RDO_FAIL; | |
1064 | goto DEFER_FREEZE_FAIL; | |
1065 | ||
1066 | case freeze_command: | |
1067 | cmd_bit = RDO_FREEZE; | |
1068 | ||
1069 | DEFER_FREEZE_FAIL: | |
1070 | if ((filter_options & cmd_bit) == 0) | |
1071 | { | |
1072 | *error_pointer = string_sprintf("filtering command \"%s\" is disabled: " | |
1073 | "near line %d of filter file", buffer, line_number); | |
1074 | yield = FALSE; | |
1075 | break; | |
1076 | } | |
1077 | ||
1078 | /* A text message can be provided after the "text" keyword, or | |
1079 | as a string in quotes. */ | |
1080 | ||
1081 | saveptr = ptr; | |
1082 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
1083 | if (*saveptr != '\"' && (*buffer == 0 || Ustrcmp(buffer, "text") != 0)) | |
1084 | { | |
1085 | ptr = saveptr; | |
1086 | fmsg = US""; | |
1087 | } | |
1088 | else | |
1089 | { | |
1090 | if (*saveptr != '\"') | |
1091 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
1092 | fmsg = string_copy(buffer); | |
1093 | } | |
1094 | ||
1095 | /* Drop through and treat as "finish", but never set "seen". */ | |
1096 | ||
1097 | seen_value = FALSE; | |
1098 | ||
1099 | /* Finish has no arguments; fmsg defaults to NULL */ | |
1100 | ||
1101 | case finish_command: | |
1102 | new = store_get(sizeof(filter_cmd)); | |
1103 | new->next = NULL; | |
1104 | **lastcmdptr = new; | |
1105 | *lastcmdptr = &(new->next); | |
1106 | new->command = command; | |
1107 | new->seen = seen_force? seen_value : FALSE; | |
1108 | new->args[0].u = fmsg; | |
1109 | break; | |
1110 | ||
1111 | ||
1112 | /* Seen, unseen, and noerror are not allowed before if, which takes a | |
1113 | condition argument and then and else sub-commands. */ | |
1114 | ||
1115 | case if_command: | |
1116 | if (seen_force || noerror_force) | |
1117 | { | |
1118 | *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" " | |
1119 | "found before an \"if\" command near line %d", | |
1120 | line_number); | |
1121 | yield = FALSE; | |
1122 | } | |
1123 | ||
1124 | /* Set up the command block for if */ | |
1125 | ||
1126 | new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes)); | |
1127 | new->next = NULL; | |
1128 | **lastcmdptr = new; | |
1129 | *lastcmdptr = &(new->next); | |
1130 | new->command = command; | |
1131 | new->seen = FALSE; | |
1132 | new->args[0].u = NULL; | |
1133 | new->args[1].u = new->args[2].u = NULL; | |
1134 | new->args[3].u = ptr; | |
1135 | ||
1136 | /* Read the condition */ | |
1137 | ||
1138 | ptr = read_condition(ptr, &(new->args[0].c), TRUE); | |
1139 | if (*error_pointer != NULL) { yield = FALSE; break; } | |
1140 | ||
1141 | /* Read the commands to be obeyed if the condition is true */ | |
1142 | ||
1143 | newlastcmdptr = &(new->args[1].f); | |
1144 | if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) yield = FALSE; | |
1145 | ||
1146 | /* If commands were successfully read, handle the various possible | |
1147 | terminators. There may be a number of successive "elif" sections. */ | |
1148 | ||
1149 | else | |
1150 | { | |
1151 | while (had_else_endif == had_elif) | |
1152 | { | |
1153 | filter_cmd *newnew = | |
1154 | store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes)); | |
1155 | new->args[2].f = newnew; | |
1156 | new = newnew; | |
1157 | new->next = NULL; | |
1158 | new->command = command; | |
1159 | new->seen = FALSE; | |
1160 | new->args[0].u = NULL; | |
1161 | new->args[1].u = new->args[2].u = NULL; | |
1162 | new->args[3].u = ptr; | |
1163 | ||
1164 | ptr = read_condition(ptr, &(new->args[0].c), TRUE); | |
1165 | if (*error_pointer != NULL) { yield = FALSE; break; } | |
1166 | newlastcmdptr = &(new->args[1].f); | |
1167 | if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) | |
1168 | yield = FALSE; | |
1169 | } | |
1170 | ||
1171 | if (yield == FALSE) break; | |
1172 | ||
1173 | /* Handle termination by "else", possibly following one or more | |
1174 | "elsif" sections. */ | |
1175 | ||
1176 | if (had_else_endif == had_else) | |
1177 | { | |
1178 | newlastcmdptr = &(new->args[2].f); | |
1179 | if (!read_command_list(&ptr, &newlastcmdptr, TRUE)) | |
1180 | yield = FALSE; | |
1181 | else if (had_else_endif != had_endif) | |
1182 | { | |
1183 | *error_pointer = string_sprintf("\"endif\" missing near line %d of " | |
1184 | "filter file", line_number); | |
1185 | yield = FALSE; | |
1186 | } | |
1187 | } | |
1188 | ||
1189 | /* Otherwise the terminator was "endif" - this is checked by | |
1190 | read_command_list(). The pointer is already set to NULL. */ | |
1191 | } | |
1192 | ||
1193 | /* Reset the terminator flag. */ | |
1194 | ||
1195 | had_else_endif = had_neither; | |
1196 | break; | |
1197 | ||
1198 | ||
1199 | /* The mail & vacation commands have a whole slew of keyworded arguments. | |
1200 | The final argument values are the file expand and return message booleans, | |
1201 | whose offsets are defined in mailarg_index_{expand,return}. Although they | |
1202 | are logically booleans, because they are stored in a uschar * value, we use | |
1203 | NULL and not FALSE, to keep 64-bit compilers happy. */ | |
1204 | ||
1205 | case mail_command: | |
1206 | case vacation_command: | |
1207 | new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes)); | |
1208 | new->next = NULL; | |
1209 | new->command = command; | |
1210 | new->seen = seen_force? seen_value : FALSE; | |
1211 | new->noerror = noerror_force; | |
1212 | for (i = 0; i < mailargs_total; i++) new->args[i].u = NULL; | |
1213 | ||
1214 | /* Read keyword/value pairs until we hit one that isn't. The data | |
1215 | must contain only printing chars plus tab, though the "text" value | |
1216 | can also contain newlines. The "file" keyword can be preceded by the | |
1217 | word "expand", and "return message" has no data. */ | |
1218 | ||
1219 | for (;;) | |
1220 | { | |
1221 | uschar *saveptr = ptr; | |
1222 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
1223 | if (*error_pointer != NULL) | |
1224 | { | |
1225 | yield = FALSE; | |
1226 | break; | |
1227 | } | |
1228 | ||
1229 | /* Ensure "return" is followed by "message"; that's a complete option */ | |
1230 | ||
1231 | if (Ustrcmp(buffer, "return") == 0) | |
1232 | { | |
1233 | new->args[mailarg_index_return].u = US""; /* not NULL => TRUE */ | |
1234 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
1235 | if (Ustrcmp(buffer, "message") != 0) | |
1236 | { | |
1237 | *error_pointer = string_sprintf("\"return\" not followed by \"message\" " | |
1238 | " near line %d of filter file", line_number); | |
1239 | yield = FALSE; | |
1240 | break; | |
1241 | } | |
1242 | continue; | |
1243 | } | |
1244 | ||
1245 | /* Ensure "expand" is followed by "file", then fall through to process the | |
1246 | file keyword. */ | |
1247 | ||
1248 | if (Ustrcmp(buffer, "expand") == 0) | |
1249 | { | |
1250 | new->args[mailarg_index_expand].u = US""; /* not NULL => TRUE */ | |
1251 | ptr = nextword(ptr, buffer, sizeof(buffer), FALSE); | |
1252 | if (Ustrcmp(buffer, "file") != 0) | |
1253 | { | |
1254 | *error_pointer = string_sprintf("\"expand\" not followed by \"file\" " | |
1255 | " near line %d of filter file", line_number); | |
1256 | yield = FALSE; | |
1257 | break; | |
1258 | } | |
1259 | } | |
1260 | ||
1261 | /* Scan for the keyword */ | |
1262 | ||
1263 | for (i = 0; i < MAILARGS_STRING_COUNT; i++) | |
1264 | if (Ustrcmp(buffer, mailargs[i]) == 0) break; | |
1265 | ||
1266 | /* Not found keyword; assume end of this command */ | |
1267 | ||
1268 | if (i >= MAILARGS_STRING_COUNT) | |
1269 | { | |
1270 | ptr = saveptr; | |
1271 | break; | |
1272 | } | |
1273 | ||
1274 | /* Found keyword, read the data item */ | |
1275 | ||
1276 | ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE); | |
1277 | if (*error_pointer != NULL) | |
1278 | { | |
1279 | yield = FALSE; | |
1280 | break; | |
1281 | } | |
1282 | else new->args[i].u = string_copy(buffer); | |
1283 | } | |
1284 | ||
1285 | /* If this is the vacation command, apply some default settings to | |
1286 | some of the arguments. */ | |
1287 | ||
1288 | if (command == vacation_command) | |
1289 | { | |
1290 | if (new->args[mailarg_index_file].u == NULL) | |
1291 | { | |
1292 | new->args[mailarg_index_file].u = string_copy(US".vacation.msg"); | |
1293 | new->args[mailarg_index_expand].u = US""; /* not NULL => TRUE */ | |
1294 | } | |
1295 | if (new->args[mailarg_index_log].u == NULL) | |
1296 | new->args[mailarg_index_log].u = string_copy(US".vacation.log"); | |
1297 | if (new->args[mailarg_index_once].u == NULL) | |
1298 | new->args[mailarg_index_once].u = string_copy(US".vacation"); | |
1299 | if (new->args[mailarg_index_once_repeat].u == NULL) | |
1300 | new->args[mailarg_index_once_repeat].u = string_copy(US"7d"); | |
1301 | if (new->args[mailarg_index_subject].u == NULL) | |
1302 | new->args[mailarg_index_subject].u = string_copy(US"On vacation"); | |
1303 | } | |
1304 | ||
1305 | /* Join the address on to the chain of generated addresses */ | |
1306 | ||
1307 | **lastcmdptr = new; | |
1308 | *lastcmdptr = &(new->next); | |
1309 | break; | |
1310 | ||
1311 | ||
1312 | /* Seen and unseen just set flags */ | |
1313 | ||
1314 | case seen_command: | |
1315 | case unseen_command: | |
41609df5 PH |
1316 | if (*ptr == 0) |
1317 | { | |
1318 | *error_pointer = string_sprintf("\"seen\" or \"unseen\" " | |
1319 | "near line %d is not followed by a command", line_number); | |
1320 | yield = FALSE; | |
1321 | } | |
059ec3d9 PH |
1322 | if (seen_force) |
1323 | { | |
1324 | *error_pointer = string_sprintf("\"seen\" or \"unseen\" repeated " | |
1325 | "near line %d", line_number); | |
1326 | yield = FALSE; | |
1327 | } | |
1328 | seen_value = (command == seen_command); | |
1329 | seen_force = TRUE; | |
1330 | was_seen_or_unseen = TRUE; | |
1331 | break; | |
1332 | ||
1333 | ||
1334 | /* So does noerror */ | |
1335 | ||
1336 | case noerror_command: | |
41609df5 PH |
1337 | if (*ptr == 0) |
1338 | { | |
1339 | *error_pointer = string_sprintf("\"noerror\" " | |
1340 | "near line %d is not followed by a command", line_number); | |
1341 | yield = FALSE; | |
1342 | } | |
059ec3d9 PH |
1343 | noerror_force = TRUE; |
1344 | was_noerror = TRUE; | |
1345 | break; | |
1346 | ||
1347 | ||
1348 | /* Oops */ | |
1349 | ||
1350 | default: | |
1351 | *error_pointer = string_sprintf("unknown filtering command \"%s\" " | |
1352 | "near line %d of filter file", buffer, line_number); | |
1353 | yield = FALSE; | |
1354 | break; | |
1355 | } | |
1356 | ||
1357 | if (!was_seen_or_unseen && !was_noerror) | |
1358 | { | |
1359 | seen_force = FALSE; | |
1360 | noerror_force = FALSE; | |
1361 | } | |
1362 | ||
1363 | *pptr = ptr; | |
1364 | return yield; | |
1365 | } | |
1366 | ||
1367 | ||
1368 | ||
1369 | /************************************************* | |
1370 | * Read a list of commands * | |
1371 | *************************************************/ | |
1372 | ||
4c04137d | 1373 | /* If conditional is TRUE, the list must be terminated |
059ec3d9 PH |
1374 | by the words "else" or "endif". |
1375 | ||
1376 | Arguments: | |
1377 | pptr points to pointer to next character; the pointer is updated | |
1378 | lastcmdptr points to pointer to pointer to previously-read command; used | |
1379 | for hanging on the new command | |
1380 | conditional TRUE if this command is the subject of a condition | |
1381 | ||
1382 | Returns: TRUE on success | |
1383 | */ | |
1384 | ||
1385 | static BOOL | |
1386 | read_command_list(uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional) | |
1387 | { | |
1388 | if (conditional) expect_endif++; | |
1389 | had_else_endif = had_neither; | |
1390 | while (**pptr != 0 && had_else_endif == had_neither) | |
1391 | { | |
1392 | if (!read_command(pptr, lastcmdptr)) return FALSE; | |
1393 | *pptr = nextsigchar(*pptr, TRUE); | |
1394 | } | |
1395 | if (conditional) | |
1396 | { | |
1397 | expect_endif--; | |
1398 | if (had_else_endif == had_neither) | |
1399 | { | |
1400 | *error_pointer = US"\"endif\" missing at end of filter file"; | |
1401 | return FALSE; | |
1402 | } | |
1403 | } | |
1404 | return TRUE; | |
1405 | } | |
1406 | ||
1407 | ||
1408 | ||
1409 | ||
1410 | /************************************************* | |
1411 | * Test a condition * | |
1412 | *************************************************/ | |
1413 | ||
1414 | /* | |
1415 | Arguments: | |
1416 | c points to the condition block; c->testfor indicated whether | |
1417 | it's a positive or negative condition | |
1418 | toplevel TRUE if called from "if" directly; FALSE otherwise | |
1419 | ||
1420 | Returns: TRUE if the condition is met | |
1421 | */ | |
1422 | ||
1423 | static BOOL | |
1424 | test_condition(condition_block *c, BOOL toplevel) | |
1425 | { | |
91ecef39 | 1426 | BOOL yield = FALSE; |
059ec3d9 PH |
1427 | const pcre *re; |
1428 | uschar *exp[2], *p, *pp; | |
1429 | const uschar *regcomp_error = NULL; | |
1430 | int regcomp_error_offset; | |
1431 | int val[2]; | |
1432 | int i; | |
1433 | ||
1434 | if (c == NULL) return TRUE; /* does this ever occur? */ | |
1435 | ||
1436 | switch (c->type) | |
1437 | { | |
1438 | case cond_and: | |
1439 | yield = test_condition(c->left.c, FALSE) && | |
1440 | *error_pointer == NULL && | |
1441 | test_condition(c->right.c, FALSE); | |
1442 | break; | |
1443 | ||
1444 | case cond_or: | |
1445 | yield = test_condition(c->left.c, FALSE) || | |
1446 | (*error_pointer == NULL && | |
1447 | test_condition(c->right.c, FALSE)); | |
1448 | break; | |
1449 | ||
1450 | /* The personal test is meaningless in a system filter. The tests are now in | |
1451 | a separate function (so Sieve can use them). However, an Exim filter does not | |
1452 | scan Cc: (hence the FALSE argument). */ | |
1453 | ||
1454 | case cond_personal: | |
1455 | yield = system_filtering? FALSE : filter_personal(c->left.a, FALSE); | |
1456 | break; | |
1457 | ||
1458 | case cond_delivered: | |
1459 | yield = filter_delivered; | |
1460 | break; | |
1461 | ||
1462 | /* Only TRUE if a message is actually being processed; FALSE for address | |
1463 | testing and verification. */ | |
1464 | ||
1465 | case cond_errormsg: | |
1466 | yield = message_id[0] != 0 && | |
1467 | (sender_address == NULL || sender_address[0] == 0); | |
1468 | break; | |
1469 | ||
1470 | /* Only FALSE if a message is actually being processed; TRUE for address | |
1471 | and filter testing and verification. */ | |
1472 | ||
1473 | case cond_firsttime: | |
f05da2e8 | 1474 | yield = filter_test != FTEST_NONE || message_id[0] == 0 || deliver_firsttime; |
059ec3d9 PH |
1475 | break; |
1476 | ||
1477 | /* Only TRUE if a message is actually being processed; FALSE for address | |
1478 | testing and verification. */ | |
1479 | ||
1480 | case cond_manualthaw: | |
1481 | yield = message_id[0] != 0 && deliver_manual_thaw; | |
1482 | break; | |
1483 | ||
1484 | /* The foranyaddress condition loops through a list of addresses */ | |
1485 | ||
1486 | case cond_foranyaddress: | |
1487 | p = c->left.u; | |
1488 | pp = expand_string(p); | |
1489 | if (pp == NULL) | |
1490 | { | |
1491 | *error_pointer = string_sprintf("failed to expand \"%s\" in " | |
1492 | "filter file: %s", p, expand_string_message); | |
1493 | return FALSE; | |
1494 | } | |
1495 | ||
1496 | yield = FALSE; | |
1497 | parse_allow_group = TRUE; /* Allow group syntax */ | |
1498 | ||
1499 | while (*pp != 0) | |
1500 | { | |
1501 | uschar *error; | |
1502 | int start, end, domain; | |
1503 | int saveend; | |
1504 | ||
1505 | p = parse_find_address_end(pp, FALSE); | |
1506 | saveend = *p; | |
1507 | ||
1508 | *p = 0; | |
1509 | filter_thisaddress = | |
1510 | parse_extract_address(pp, &error, &start, &end, &domain, FALSE); | |
1511 | *p = saveend; | |
1512 | ||
1513 | if (filter_thisaddress != NULL) | |
1514 | { | |
f05da2e8 | 1515 | if ((filter_test != FTEST_NONE && debug_selector != 0) || |
059ec3d9 PH |
1516 | (debug_selector & D_filter) != 0) |
1517 | { | |
1518 | indent(); | |
1519 | debug_printf("Extracted address %s\n", filter_thisaddress); | |
1520 | } | |
1521 | yield = test_condition(c->right.c, FALSE); | |
1522 | } | |
1523 | ||
1524 | if (yield) break; | |
1525 | if (saveend == 0) break; | |
1526 | pp = p + 1; | |
1527 | } | |
1528 | ||
1529 | parse_allow_group = FALSE; /* Reset group syntax flags */ | |
1530 | parse_found_group = FALSE; | |
1531 | break; | |
1532 | ||
1533 | /* All other conditions have left and right values that need expanding; | |
1534 | on error, it doesn't matter what value is returned. */ | |
1535 | ||
1536 | default: | |
1537 | p = c->left.u; | |
1538 | for (i = 0; i < 2; i++) | |
1539 | { | |
1540 | exp[i] = expand_string(p); | |
1541 | if (exp[i] == NULL) | |
1542 | { | |
1543 | *error_pointer = string_sprintf("failed to expand \"%s\" in " | |
1544 | "filter file: %s", p, expand_string_message); | |
1545 | return FALSE; | |
1546 | } | |
1547 | p = c->right.u; | |
1548 | } | |
1549 | ||
1550 | /* Inner switch for the different cases */ | |
1551 | ||
1552 | switch(c->type) | |
1553 | { | |
1554 | case cond_is: | |
1555 | yield = strcmpic(exp[0], exp[1]) == 0; | |
1556 | break; | |
1557 | ||
1558 | case cond_IS: | |
1559 | yield = Ustrcmp(exp[0], exp[1]) == 0; | |
1560 | break; | |
1561 | ||
1562 | case cond_contains: | |
1563 | yield = strstric(exp[0], exp[1], FALSE) != NULL; | |
1564 | break; | |
1565 | ||
1566 | case cond_CONTAINS: | |
1567 | yield = Ustrstr(exp[0], exp[1]) != NULL; | |
1568 | break; | |
1569 | ||
1570 | case cond_begins: | |
1571 | yield = strncmpic(exp[0], exp[1], Ustrlen(exp[1])) == 0; | |
1572 | break; | |
1573 | ||
1574 | case cond_BEGINS: | |
1575 | yield = Ustrncmp(exp[0], exp[1], Ustrlen(exp[1])) == 0; | |
1576 | break; | |
1577 | ||
1578 | case cond_ends: | |
1579 | case cond_ENDS: | |
1580 | { | |
1581 | int len = Ustrlen(exp[1]); | |
1582 | uschar *s = exp[0] + Ustrlen(exp[0]) - len; | |
1583 | yield = (s < exp[0])? FALSE : | |
1584 | ((c->type == cond_ends)? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0; | |
1585 | } | |
1586 | break; | |
1587 | ||
1588 | case cond_matches: | |
1589 | case cond_MATCHES: | |
f05da2e8 | 1590 | if ((filter_test != FTEST_NONE && debug_selector != 0) || |
059ec3d9 PH |
1591 | (debug_selector & D_filter) != 0) |
1592 | { | |
1593 | debug_printf("Match expanded arguments:\n"); | |
1594 | debug_printf(" Subject = %s\n", exp[0]); | |
1595 | debug_printf(" Pattern = %s\n", exp[1]); | |
1596 | } | |
1597 | ||
1598 | re = pcre_compile(CS exp[1], | |
1599 | PCRE_COPT | ((c->type == cond_matches)? PCRE_CASELESS : 0), | |
1600 | (const char **)®comp_error, ®comp_error_offset, NULL); | |
1601 | ||
1602 | if (re == NULL) | |
1603 | { | |
1604 | *error_pointer = string_sprintf("error while compiling " | |
1605 | "regular expression \"%s\": %s at offset %d", | |
1606 | exp[1], regcomp_error, regcomp_error_offset); | |
1607 | return FALSE; | |
1608 | } | |
1609 | ||
1610 | yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1); | |
1611 | break; | |
1612 | ||
1613 | /* For above and below, convert the strings to numbers */ | |
1614 | ||
1615 | case cond_above: | |
1616 | case cond_below: | |
1617 | for (i = 0; i < 2; i++) | |
1618 | { | |
1619 | val[i] = get_number(exp[i], &yield); | |
1620 | if (!yield) | |
1621 | { | |
1622 | *error_pointer = string_sprintf("malformed numerical string \"%s\"", | |
1623 | exp[i]); | |
1624 | return FALSE; | |
1625 | } | |
1626 | } | |
1627 | yield = (c->type == cond_above)? (val[0] > val[1]) : (val[0] < val[1]); | |
1628 | break; | |
1629 | } | |
1630 | break; | |
1631 | } | |
1632 | ||
f05da2e8 | 1633 | if ((filter_test != FTEST_NONE && debug_selector != 0) || |
059ec3d9 PH |
1634 | (debug_selector & D_filter) != 0) |
1635 | { | |
1636 | indent(); | |
1637 | debug_printf("%sondition is %s: ", | |
1638 | toplevel? "C" : "Sub-c", | |
1639 | (yield == c->testfor)? "true" : "false"); | |
1640 | print_condition(c, TRUE); | |
1641 | debug_printf("\n"); | |
1642 | } | |
1643 | ||
1644 | return yield == c->testfor; | |
1645 | } | |
1646 | ||
1647 | ||
1648 | ||
1649 | /************************************************* | |
1650 | * Interpret chain of commands * | |
1651 | *************************************************/ | |
1652 | ||
1653 | /* In testing state, just say what would be done rather than doing it. The | |
1654 | testprint command just expands and outputs its argument in testing state, and | |
1655 | does nothing otherwise. | |
1656 | ||
1657 | Arguments: | |
1658 | commands points to chain of commands to interpret | |
1659 | generated where to hang newly-generated addresses | |
1660 | ||
1661 | Returns: FF_DELIVERED success, a significant action was taken | |
1662 | FF_NOTDELIVERED success, no significant action | |
1663 | FF_DEFER defer requested | |
1664 | FF_FAIL fail requested | |
1665 | FF_FREEZE freeze requested | |
1666 | FF_ERROR there was a problem | |
1667 | */ | |
1668 | ||
1669 | static int | |
1670 | interpret_commands(filter_cmd *commands, address_item **generated) | |
1671 | { | |
1672 | uschar *s; | |
1673 | int mode; | |
1674 | address_item *addr; | |
1675 | BOOL condition_value; | |
1676 | ||
1677 | while (commands != NULL) | |
1678 | { | |
1679 | int ff_ret; | |
1680 | uschar *fmsg, *ff_name; | |
1681 | uschar *expargs[MAILARGS_STRING_COUNT]; | |
1682 | ||
1683 | int i, n[2]; | |
1684 | ||
1685 | /* Expand the relevant number of arguments for the command that are | |
1686 | not NULL. */ | |
1687 | ||
1688 | for (i = 0; i < (command_exparg_count[commands->command] & 15); i++) | |
1689 | { | |
1690 | uschar *ss = commands->args[i].u; | |
1691 | if (ss == NULL) | |
1692 | { | |
1693 | expargs[i] = NULL; | |
1694 | } | |
1695 | else | |
1696 | { | |
1697 | expargs[i] = expand_string(ss); | |
1698 | if (expargs[i] == NULL) | |
1699 | { | |
1700 | *error_pointer = string_sprintf("failed to expand \"%s\" in " | |
1701 | "%s command: %s", ss, command_list[commands->command], | |
1702 | expand_string_message); | |
1703 | return FF_ERROR; | |
1704 | } | |
1705 | } | |
1706 | } | |
1707 | ||
1708 | /* Now switch for each command, setting the "delivered" flag if any of them | |
1709 | have "seen" set. */ | |
1710 | ||
1711 | if (commands->seen) filter_delivered = TRUE; | |
1712 | ||
1713 | switch(commands->command) | |
1714 | { | |
1715 | case add_command: | |
1716 | for (i = 0; i < 2; i++) | |
1717 | { | |
1718 | uschar *ss = expargs[i]; | |
1719 | uschar *end; | |
1720 | ||
1721 | if (i == 1 && (*ss++ != 'n' || ss[1] != 0)) | |
1722 | { | |
1723 | *error_pointer = string_sprintf("unknown variable \"%s\" in \"add\" " | |
1724 | "command", expargs[i]); | |
1725 | return FF_ERROR; | |
1726 | } | |
1727 | ||
1728 | /* Allow for "--" at the start of the value (from -$n0) for example */ | |
1729 | if (i == 0) while (ss[0] == '-' && ss[1] == '-') ss += 2; | |
1730 | ||
1731 | n[i] = (int)Ustrtol(ss, &end, 0); | |
1732 | if (*end != 0) | |
1733 | { | |
1734 | *error_pointer = string_sprintf("malformed number \"%s\" in \"add\" " | |
1735 | "command", ss); | |
1736 | return FF_ERROR; | |
1737 | } | |
1738 | } | |
1739 | ||
1740 | filter_n[n[1]] += n[0]; | |
f05da2e8 | 1741 | if (filter_test != FTEST_NONE) printf("Add %d to n%d\n", n[0], n[1]); |
059ec3d9 PH |
1742 | break; |
1743 | ||
1744 | /* A deliver command's argument must be a valid address. Its optional | |
1745 | second argument (system filter only) must also be a valid address. */ | |
1746 | ||
1747 | case deliver_command: | |
1748 | for (i = 0; i < 2; i++) | |
1749 | { | |
1750 | s = expargs[i]; | |
1751 | if (s != NULL) | |
1752 | { | |
1753 | int start, end, domain; | |
1754 | uschar *error; | |
1755 | uschar *ss = parse_extract_address(s, &error, &start, &end, &domain, | |
1756 | FALSE); | |
1757 | if (ss != NULL) | |
1758 | expargs[i] = ((filter_options & RDO_REWRITE) != 0)? | |
1759 | rewrite_address(ss, TRUE, FALSE, global_rewrite_rules, | |
1760 | rewrite_existflags) : | |
1761 | rewrite_address_qualify(ss, TRUE); | |
1762 | else | |
1763 | { | |
1764 | *error_pointer = string_sprintf("malformed address \"%s\" in " | |
1765 | "filter file: %s", s, error); | |
1766 | return FF_ERROR; | |
1767 | } | |
1768 | } | |
1769 | } | |
1770 | ||
1771 | /* Stick the errors address into a simple variable, as it will | |
1772 | be referenced a few times. Check that the caller is permitted to | |
1773 | specify it. */ | |
1774 | ||
1775 | s = expargs[1]; | |
1776 | ||
1777 | if (s != NULL && !system_filtering) | |
1778 | { | |
1779 | uschar *ownaddress = expand_string(US"$local_part@$domain"); | |
1780 | if (strcmpic(ownaddress, s) != 0) | |
1781 | { | |
1782 | *error_pointer = US"errors_to must point to the caller's address"; | |
1783 | return FF_ERROR; | |
1784 | } | |
1785 | } | |
1786 | ||
1787 | /* Test case: report what would happen */ | |
1788 | ||
f05da2e8 | 1789 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
1790 | { |
1791 | indent(); | |
1792 | printf("%seliver message to: %s%s%s%s\n", | |
1793 | (commands->seen)? "D" : "Unseen d", | |
1794 | expargs[0], | |
1795 | commands->noerror? " (noerror)" : "", | |
1796 | (s != NULL)? " errors_to " : "", | |
1797 | (s != NULL)? s : US""); | |
1798 | } | |
1799 | ||
1800 | /* Real case. */ | |
1801 | ||
1802 | else | |
1803 | { | |
1804 | DEBUG(D_filter) debug_printf("Filter: %sdeliver message to: %s%s%s%s\n", | |
1805 | (commands->seen)? "" : "unseen ", | |
1806 | expargs[0], | |
1807 | commands->noerror? " (noerror)" : "", | |
1808 | (s != NULL)? " errors_to " : "", | |
1809 | (s != NULL)? s : US""); | |
1810 | ||
1811 | /* Create the new address and add it to the chain, setting the | |
1812 | af_ignore_error flag if necessary, and the errors address, which can be | |
1813 | set in a system filter and to the local address in user filters. */ | |
1814 | ||
1815 | addr = deliver_make_addr(expargs[0], TRUE); /* TRUE => copy s */ | |
d43cbe25 | 1816 | addr->prop.errors_address = (s == NULL)? |
059ec3d9 | 1817 | s : string_copy(s); /* Default is NULL */ |
7eb0e5d2 | 1818 | if (commands->noerror) addr->prop.ignore_error = TRUE; |
059ec3d9 PH |
1819 | addr->next = *generated; |
1820 | *generated = addr; | |
1821 | } | |
1822 | break; | |
1823 | ||
1824 | case save_command: | |
1825 | s = expargs[0]; | |
1826 | mode = commands->args[1].i; | |
1827 | ||
1828 | /* Test case: report what would happen */ | |
1829 | ||
f05da2e8 | 1830 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
1831 | { |
1832 | indent(); | |
1833 | if (mode < 0) | |
1834 | printf("%save message to: %s%s\n", (commands->seen)? | |
1835 | "S" : "Unseen s", s, commands->noerror? " (noerror)" : ""); | |
1836 | else | |
1837 | printf("%save message to: %s %04o%s\n", (commands->seen)? | |
1838 | "S" : "Unseen s", s, mode, commands->noerror? " (noerror)" : ""); | |
1839 | } | |
1840 | ||
1841 | /* Real case: Ensure save argument starts with / if there is a home | |
1842 | directory to prepend. */ | |
1843 | ||
1844 | else | |
1845 | { | |
4608d683 PH |
1846 | if (s[0] != '/' && (filter_options & RDO_PREPEND_HOME) != 0 && |
1847 | deliver_home != NULL && deliver_home[0] != 0) | |
1848 | s = string_sprintf("%s/%s", deliver_home, s); | |
059ec3d9 PH |
1849 | DEBUG(D_filter) debug_printf("Filter: %ssave message to: %s%s\n", |
1850 | (commands->seen)? "" : "unseen ", s, | |
1851 | commands->noerror? " (noerror)" : ""); | |
059ec3d9 PH |
1852 | |
1853 | /* Create the new address and add it to the chain, setting the | |
1854 | af_pfr and af_file flags, the af_ignore_error flag if necessary, and the | |
1855 | mode value. */ | |
1856 | ||
1857 | addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */ | |
7eb0e5d2 JH |
1858 | setflag(addr, af_pfr); |
1859 | setflag(addr, af_file); | |
1860 | if (commands->noerror) addr->prop.ignore_error = TRUE; | |
059ec3d9 PH |
1861 | addr->mode = mode; |
1862 | addr->next = *generated; | |
1863 | *generated = addr; | |
1864 | } | |
1865 | break; | |
1866 | ||
1867 | case pipe_command: | |
1868 | s = string_copy(commands->args[0].u); | |
f05da2e8 | 1869 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
1870 | { |
1871 | indent(); | |
1872 | printf("%sipe message to: %s%s\n", (commands->seen)? | |
1873 | "P" : "Unseen p", s, commands->noerror? " (noerror)" : ""); | |
1874 | } | |
1875 | else /* Ensure pipe command starts with | */ | |
1876 | { | |
1877 | DEBUG(D_filter) debug_printf("Filter: %spipe message to: %s%s\n", | |
1878 | (commands->seen)? "" : "unseen ", s, | |
1879 | commands->noerror? " (noerror)" : ""); | |
1880 | if (s[0] != '|') s = string_sprintf("|%s", s); | |
1881 | ||
1882 | /* Create the new address and add it to the chain, setting the | |
1883 | af_ignore_error flag if necessary. Set the af_expand_pipe flag so that | |
1884 | each command argument is expanded in the transport after the command | |
1885 | has been split up into separate arguments. */ | |
1886 | ||
1887 | addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */ | |
7eb0e5d2 JH |
1888 | setflag(addr, af_pfr); |
1889 | setflag(addr, af_expand_pipe); | |
1890 | if (commands->noerror) addr->prop.ignore_error = TRUE; | |
059ec3d9 PH |
1891 | addr->next = *generated; |
1892 | *generated = addr; | |
1893 | ||
1894 | /* If there are any numeric variables in existence (e.g. after a regex | |
1895 | condition), or if $thisaddress is set, take a copy for use in the | |
1896 | expansion. Note that we can't pass NULL for filter_thisaddress, because | |
1897 | NULL terminates the list. */ | |
1898 | ||
1899 | if (expand_nmax >= 0 || filter_thisaddress != NULL) | |
1900 | { | |
1901 | int i; | |
1902 | int ecount = (expand_nmax >= 0)? expand_nmax : -1; | |
1903 | uschar **ss = store_get(sizeof(uschar *) * (ecount + 3)); | |
1904 | addr->pipe_expandn = ss; | |
1905 | if (filter_thisaddress == NULL) filter_thisaddress = US""; | |
1906 | *ss++ = string_copy(filter_thisaddress); | |
1907 | for (i = 0; i <= expand_nmax; i++) | |
1908 | *ss++ = string_copyn(expand_nstring[i], expand_nlength[i]); | |
1909 | *ss = NULL; | |
1910 | } | |
1911 | } | |
1912 | break; | |
1913 | ||
1914 | /* Set up the file name and mode, and close any previously open | |
1915 | file. */ | |
1916 | ||
1917 | case logfile_command: | |
1918 | log_mode = commands->args[1].i; | |
1919 | if (log_mode == -1) log_mode = 0600; | |
1920 | if (log_fd >= 0) | |
1921 | { | |
f1e894f3 | 1922 | (void)close(log_fd); |
059ec3d9 PH |
1923 | log_fd = -1; |
1924 | } | |
1925 | log_filename = expargs[0]; | |
f05da2e8 | 1926 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
1927 | { |
1928 | indent(); | |
1929 | printf("%sogfile %s\n", (commands->seen)? "Seen l" : "L", log_filename); | |
1930 | } | |
1931 | break; | |
1932 | ||
1933 | case logwrite_command: | |
1934 | s = expargs[0]; | |
1935 | ||
f05da2e8 | 1936 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
1937 | { |
1938 | indent(); | |
1939 | printf("%sogwrite \"%s\"\n", (commands->seen)? "Seen l" : "L", | |
1940 | string_printing(s)); | |
1941 | } | |
1942 | ||
1943 | /* Attempt to write to a log file only if configured as permissible. | |
1944 | Logging may be forcibly skipped for verifying or testing. */ | |
1945 | ||
1946 | else if ((filter_options & RDO_LOG) != 0) /* Locked out */ | |
1947 | { | |
1948 | DEBUG(D_filter) | |
1949 | debug_printf("filter log command aborted: euid=%ld\n", | |
1950 | (long int)geteuid()); | |
1951 | *error_pointer = US"logwrite command forbidden"; | |
1952 | return FF_ERROR; | |
1953 | } | |
1954 | else if ((filter_options & RDO_REALLOG) != 0) | |
1955 | { | |
1956 | int len; | |
1957 | DEBUG(D_filter) debug_printf("writing filter log as euid %ld\n", | |
1958 | (long int)geteuid()); | |
1959 | if (log_fd < 0) | |
1960 | { | |
1961 | if (log_filename == NULL) | |
1962 | { | |
1963 | *error_pointer = US"attempt to obey \"logwrite\" command " | |
1964 | "without a previous \"logfile\""; | |
1965 | return FF_ERROR; | |
1966 | } | |
1967 | log_fd = Uopen(log_filename, O_CREAT|O_APPEND|O_WRONLY, log_mode); | |
1968 | if (log_fd < 0) | |
1969 | { | |
1970 | *error_pointer = string_open_failed(errno, "filter log file \"%s\"", | |
1971 | log_filename); | |
1972 | return FF_ERROR; | |
1973 | } | |
1974 | } | |
1975 | len = Ustrlen(s); | |
1976 | if (write(log_fd, s, len) != len) | |
1977 | { | |
1978 | *error_pointer = string_sprintf("write error on file \"%s\": %s", | |
1979 | log_filename, strerror(errno)); | |
1980 | return FF_ERROR; | |
1981 | } | |
1982 | } | |
1983 | else | |
1984 | { | |
1985 | DEBUG(D_filter) debug_printf("skipping logwrite (verifying or testing)\n"); | |
1986 | } | |
1987 | break; | |
1988 | ||
1989 | /* Header addition and removal is available only in the system filter. The | |
1990 | command is rejected at parse time otherwise. However "headers charset" is | |
1991 | always permitted. */ | |
1992 | ||
1993 | case headers_command: | |
1994 | { | |
1995 | int subtype = commands->args[1].i; | |
1996 | s = expargs[0]; | |
1997 | ||
f05da2e8 | 1998 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
1999 | printf("Headers %s \"%s\"\n", (subtype == TRUE)? "add" : |
2000 | (subtype == FALSE)? "remove" : "charset", string_printing(s)); | |
2001 | ||
2002 | if (subtype == TRUE) | |
2003 | { | |
2004 | while (isspace(*s)) s++; | |
2005 | if (s[0] != 0) | |
2006 | { | |
2007 | header_add(htype_other, "%s%s", s, (s[Ustrlen(s)-1] == '\n')? | |
2008 | "" : "\n"); | |
2009 | header_last->type = header_checkname(header_last, FALSE); | |
2010 | if (header_last->type >= 'a') header_last->type = htype_other; | |
2011 | } | |
2012 | } | |
2013 | ||
2014 | else if (subtype == FALSE) | |
2015 | { | |
2016 | int sep = 0; | |
2017 | uschar *ss; | |
55414b25 | 2018 | const uschar *list = s; |
059ec3d9 PH |
2019 | uschar buffer[128]; |
2020 | while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) | |
2021 | != NULL) | |
2022 | header_remove(0, ss); | |
2023 | } | |
2024 | ||
2025 | /* This setting lasts only while the filter is running; on exit, the | |
2026 | variable is reset to the previous value. */ | |
2027 | ||
2028 | else headers_charset = s; | |
2029 | } | |
2030 | break; | |
2031 | ||
2032 | /* Defer, freeze, and fail are available only when explicitly permitted. | |
2033 | These commands are rejected at parse time otherwise. The message can get | |
2034 | very long by the inclusion of message headers; truncate if it is, and also | |
2035 | ensure printing characters so as not to mess up log files. */ | |
2036 | ||
2037 | case defer_command: | |
2038 | ff_name = US"defer"; | |
2039 | ff_ret = FF_DEFER; | |
2040 | goto DEFERFREEZEFAIL; | |
2041 | ||
2042 | case fail_command: | |
2043 | ff_name = US"fail"; | |
2044 | ff_ret = FF_FAIL; | |
2045 | goto DEFERFREEZEFAIL; | |
2046 | ||
2047 | case freeze_command: | |
2048 | ff_name = US"freeze"; | |
2049 | ff_ret = FF_FREEZE; | |
2050 | ||
2051 | DEFERFREEZEFAIL: | |
2052 | fmsg = expargs[0]; | |
2053 | if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, " ... (truncated)"); | |
55414b25 | 2054 | fmsg = US string_printing(fmsg); |
059ec3d9 PH |
2055 | *error_pointer = fmsg; |
2056 | ||
f05da2e8 | 2057 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
2058 | { |
2059 | indent(); | |
2060 | printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg); | |
2061 | } | |
2062 | else DEBUG(D_filter) debug_printf("Filter: %s \"%s\"\n", ff_name, fmsg); | |
2063 | return ff_ret; | |
2064 | ||
2065 | case finish_command: | |
f05da2e8 | 2066 | if (filter_test != FTEST_NONE) |
059ec3d9 PH |
2067 | { |
2068 | indent(); | |
2069 | printf("%sinish\n", (commands->seen)? "Seen f" : "F"); | |
2070 | } | |
2071 | else | |
2072 | { | |
2073 | DEBUG(D_filter) debug_printf("Filter: %sfinish\n", | |
2074 | (commands->seen)? " Seen " : ""); | |
2075 | } | |
2076 | finish_obeyed = TRUE; | |
2077 | return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED; | |
2078 | ||
2079 | case if_command: | |
2080 | { | |
2081 | uschar *save_address = filter_thisaddress; | |
2082 | int ok = FF_DELIVERED; | |
2083 | condition_value = test_condition(commands->args[0].c, TRUE); | |
2084 | if (*error_pointer != NULL) ok = FF_ERROR; else | |
2085 | { | |
2086 | output_indent += 2; | |
2087 | ok = interpret_commands(commands->args[condition_value? 1:2].f, | |
2088 | generated); | |
2089 | output_indent -= 2; | |
2090 | } | |
2091 | filter_thisaddress = save_address; | |
2092 | if (finish_obeyed || (ok != FF_DELIVERED && ok != FF_NOTDELIVERED)) | |
2093 | return ok; | |
2094 | } | |
2095 | break; | |
2096 | ||
2097 | ||
2098 | /* To try to catch runaway loops, do not generate mail if the | |
2099 | return path is unset or if a non-trusted user supplied -f <> | |
2100 | as the return path. */ | |
2101 | ||
2102 | case mail_command: | |
2103 | case vacation_command: | |
acec9514 JH |
2104 | if (return_path == NULL || return_path[0] == 0) |
2105 | { | |
2106 | if (filter_test != FTEST_NONE) | |
2107 | printf("%s command ignored because return_path is empty\n", | |
2108 | command_list[commands->command]); | |
2109 | else DEBUG(D_filter) debug_printf("%s command ignored because return_path " | |
2110 | "is empty\n", command_list[commands->command]); | |
2111 | break; | |
2112 | } | |
2113 | ||
2114 | /* Check the contents of the strings. The type of string can be deduced | |
2115 | from the value of i. | |
2116 | ||
2117 | . If i is equal to mailarg_index_text it's a text string for the body, | |
2118 | where anything goes. | |
2119 | ||
2120 | . If i is > mailarg_index_text, we are dealing with a file name, which | |
2121 | cannot contain non-printing characters. | |
2122 | ||
2123 | . If i is less than mailarg_index_headers we are dealing with something | |
2124 | that will go in a single message header line, where newlines must be | |
2125 | followed by white space. | |
2126 | ||
2127 | . If i is equal to mailarg_index_headers, we have a string that contains | |
2128 | one or more headers. Newlines that are not followed by white space must | |
2129 | be followed by a header name. | |
2130 | */ | |
2131 | ||
2132 | for (i = 0; i < MAILARGS_STRING_COUNT; i++) | |
2133 | { | |
2134 | uschar *p; | |
2135 | uschar *s = expargs[i]; | |
2136 | ||
2137 | if (s == NULL) continue; | |
2138 | ||
2139 | if (i != mailarg_index_text) for (p = s; *p != 0; p++) | |
2140 | { | |
2141 | int c = *p; | |
2142 | if (i > mailarg_index_text) | |
2143 | { | |
2144 | if (!mac_isprint(c)) | |
2145 | { | |
2146 | *error_pointer = string_sprintf("non-printing character in \"%s\" " | |
2147 | "in %s command", string_printing(s), | |
2148 | command_list[commands->command]); | |
2149 | return FF_ERROR; | |
2150 | } | |
2151 | } | |
2152 | ||
2153 | /* i < mailarg_index_text */ | |
2154 | ||
2155 | else if (c == '\n' && !isspace(p[1])) | |
2156 | { | |
2157 | if (i < mailarg_index_headers) | |
2158 | { | |
2159 | *error_pointer = string_sprintf("\\n not followed by space in " | |
2160 | "\"%.1024s\" in %s command", string_printing(s), | |
2161 | command_list[commands->command]); | |
2162 | return FF_ERROR; | |
2163 | } | |
2164 | ||
2165 | /* Check for the start of a new header line within the string */ | |
2166 | ||
2167 | else | |
2168 | { | |
2169 | uschar *pp; | |
2170 | for (pp = p + 1;; pp++) | |
2171 | { | |
2172 | c = *pp; | |
2173 | if (c == ':' && pp != p + 1) break; | |
2174 | if (c == 0 || c == ':' || isspace(*pp)) | |
2175 | { | |
2176 | *error_pointer = string_sprintf("\\n not followed by space or " | |
2177 | "valid header name in \"%.1024s\" in %s command", | |
2178 | string_printing(s), command_list[commands->command]); | |
2179 | return FF_ERROR; | |
2180 | } | |
2181 | } | |
2182 | p = pp; | |
2183 | } | |
2184 | } | |
2185 | } /* Loop to scan the string */ | |
2186 | ||
2187 | /* The string is OK */ | |
2188 | ||
2189 | commands->args[i].u = s; | |
2190 | } | |
2191 | ||
2192 | /* Proceed with mail or vacation command */ | |
059ec3d9 | 2193 | |
acec9514 JH |
2194 | if (filter_test != FTEST_NONE) |
2195 | { | |
2196 | uschar *to = commands->args[mailarg_index_to].u; | |
2197 | indent(); | |
2198 | printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M", | |
2199 | to ? to : US"<default>", | |
2200 | commands->command == vacation_command ? " (vacation)" : "", | |
2201 | commands->noerror ? " (noerror)" : ""); | |
2202 | for (i = 1; i < MAILARGS_STRING_COUNT; i++) | |
2203 | { | |
2204 | uschar *arg = commands->args[i].u; | |
2205 | if (arg) | |
2206 | { | |
2207 | int len = Ustrlen(mailargs[i]); | |
2208 | int indent = (debug_selector != 0)? output_indent : 0; | |
2209 | while (len++ < 7 + indent) printf(" "); | |
2210 | printf("%s: %s%s\n", mailargs[i], string_printing(arg), | |
2211 | (commands->args[mailarg_index_expand].u != NULL && | |
2212 | Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : ""); | |
2213 | } | |
2214 | } | |
2215 | if (commands->args[mailarg_index_return].u) | |
2216 | printf("Return original message\n"); | |
2217 | } | |
7eb0e5d2 | 2218 | else |
acec9514 JH |
2219 | { |
2220 | uschar *tt; | |
2221 | uschar *to = commands->args[mailarg_index_to].u; | |
2222 | gstring * log_addr = NULL; | |
2223 | ||
2224 | if (!to) to = expand_string(US"$reply_address"); | |
2225 | while (isspace(*to)) to++; | |
2226 | ||
2227 | for (tt = to; *tt != 0; tt++) /* Get rid of newlines */ | |
2228 | if (*tt == '\n') *tt = ' '; | |
2229 | ||
2230 | DEBUG(D_filter) | |
2231 | { | |
2232 | debug_printf("Filter: %smail to: %s%s%s\n", | |
2233 | commands->seen ? "seen " : "", | |
2234 | to, | |
2235 | commands->command == vacation_command ? " (vacation)" : "", | |
2236 | commands->noerror ? " (noerror)" : ""); | |
2237 | for (i = 1; i < MAILARGS_STRING_COUNT; i++) | |
2238 | { | |
2239 | uschar *arg = commands->args[i].u; | |
2240 | if (arg != NULL) | |
2241 | { | |
2242 | int len = Ustrlen(mailargs[i]); | |
2243 | while (len++ < 15) debug_printf(" "); | |
2244 | debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg), | |
2245 | (commands->args[mailarg_index_expand].u != NULL && | |
2246 | Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : ""); | |
2247 | } | |
2248 | } | |
2249 | } | |
2250 | ||
2251 | /* Create the "address" for the autoreply. This is used only for logging, | |
2252 | as the actual recipients are extracted from the To: line by -t. We use the | |
2253 | same logic here to extract the working addresses (there may be more than | |
2254 | one). Just in case there are a vast number of addresses, stop when the | |
2255 | string gets too long. */ | |
2256 | ||
2257 | tt = to; | |
2258 | while (*tt != 0) | |
2259 | { | |
2260 | uschar *ss = parse_find_address_end(tt, FALSE); | |
2261 | uschar *recipient, *errmess; | |
2262 | int start, end, domain; | |
2263 | int temp = *ss; | |
2264 | ||
2265 | *ss = 0; | |
2266 | recipient = parse_extract_address(tt, &errmess, &start, &end, &domain, | |
2267 | FALSE); | |
2268 | *ss = temp; | |
2269 | ||
2270 | /* Ignore empty addresses and errors; an error will occur later if | |
2271 | there's something really bad. */ | |
2272 | ||
2273 | if (recipient) | |
2274 | { | |
2275 | log_addr = string_catn(log_addr, log_addr ? US"," : US">", 1); | |
2276 | log_addr = string_cat (log_addr, recipient); | |
2277 | } | |
2278 | ||
2279 | /* Check size */ | |
2280 | ||
2281 | if (log_addr && log_addr->ptr > 256) | |
2282 | { | |
2283 | log_addr = string_catn(log_addr, US", ...", 5); | |
2284 | break; | |
2285 | } | |
2286 | ||
2287 | /* Move on past this address */ | |
2288 | ||
2289 | tt = ss + (*ss ? 1 : 0); | |
2290 | while (isspace(*tt)) tt++; | |
2291 | } | |
2292 | ||
2293 | if (log_addr) | |
2294 | addr = deliver_make_addr(string_from_gstring(log_addr), FALSE); | |
2295 | else | |
2296 | { | |
2297 | addr = deliver_make_addr(US ">**bad-reply**", FALSE); | |
2298 | setflag(addr, af_bad_reply); | |
2299 | } | |
2300 | ||
2301 | setflag(addr, af_pfr); | |
2302 | if (commands->noerror) addr->prop.ignore_error = TRUE; | |
2303 | addr->next = *generated; | |
2304 | *generated = addr; | |
2305 | ||
2306 | addr->reply = store_get(sizeof(reply_item)); | |
2307 | addr->reply->from = NULL; | |
2308 | addr->reply->to = string_copy(to); | |
2309 | addr->reply->file_expand = | |
2310 | commands->args[mailarg_index_expand].u != NULL; | |
2311 | addr->reply->expand_forbid = expand_forbid; | |
2312 | addr->reply->return_message = | |
2313 | commands->args[mailarg_index_return].u != NULL; | |
2314 | addr->reply->once_repeat = 0; | |
2315 | ||
2316 | if (commands->args[mailarg_index_once_repeat].u != NULL) | |
2317 | { | |
2318 | addr->reply->once_repeat = | |
2319 | readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0, | |
2320 | FALSE); | |
2321 | if (addr->reply->once_repeat < 0) | |
2322 | { | |
2323 | *error_pointer = string_sprintf("Bad time value for \"once_repeat\" " | |
2324 | "in mail or vacation command: %s", | |
bb07bcd3 | 2325 | commands->args[mailarg_index_once_repeat].u); |
acec9514 JH |
2326 | return FF_ERROR; |
2327 | } | |
2328 | } | |
2329 | ||
2330 | /* Set up all the remaining string arguments (those other than "to") */ | |
2331 | ||
2332 | for (i = 1; i < mailargs_string_passed; i++) | |
2333 | { | |
2334 | uschar *ss = commands->args[i].u; | |
2335 | *(USS((US addr->reply) + reply_offsets[i])) = | |
2336 | ss ? string_copy(ss) : NULL; | |
2337 | } | |
2338 | } | |
2339 | break; | |
059ec3d9 PH |
2340 | |
2341 | case testprint_command: | |
acec9514 JH |
2342 | if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0) |
2343 | { | |
2344 | const uschar *s = string_printing(expargs[0]); | |
2345 | if (filter_test == FTEST_NONE) | |
2346 | debug_printf("Filter: testprint: %s\n", s); | |
2347 | else | |
2348 | printf("Testprint: %s\n", s); | |
2349 | } | |
059ec3d9 PH |
2350 | } |
2351 | ||
2352 | commands = commands->next; | |
2353 | } | |
2354 | ||
2355 | return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED; | |
2356 | } | |
2357 | ||
2358 | ||
2359 | ||
2360 | /************************************************* | |
2361 | * Test for a personal message * | |
2362 | *************************************************/ | |
2363 | ||
2364 | /* This function is global so that it can also be called from the code that | |
2365 | implements Sieve filters. | |
2366 | ||
2367 | Arguments: | |
2368 | aliases a chain of aliases | |
2369 | scan_cc TRUE if Cc: and Bcc: are to be scanned (Exim filters do not) | |
2370 | ||
2371 | Returns: TRUE if the message is deemed to be personal | |
2372 | */ | |
2373 | ||
2374 | BOOL | |
2375 | filter_personal(string_item *aliases, BOOL scan_cc) | |
2376 | { | |
2377 | uschar *self, *self_from, *self_to; | |
2378 | uschar *psself = NULL, *psself_from = NULL, *psself_to = NULL; | |
2379 | void *reset_point = store_get(0); | |
2380 | BOOL yield; | |
2381 | header_line *h; | |
2382 | int to_count = 2; | |
2383 | int from_count = 9; | |
2384 | ||
4b233853 PH |
2385 | /* If any header line in the message is a defined "List-" header field, it is |
2386 | not a personal message. We used to check for any header line that started with | |
2387 | "List-", but this was tightened up for release 4.54. The check is now for | |
2388 | "List-Id", defined in RFC 2929, or "List-Help", "List-Subscribe", "List- | |
2389 | Unsubscribe", "List-Post", "List-Owner" or "List-Archive", all of which are | |
2390 | defined in RFC 2369. We also scan for "Auto-Submitted"; if it is found to | |
2391 | contain any value other than "no", the message is not personal (RFC 3834). | |
2392 | Previously the test was for "auto-". */ | |
059ec3d9 PH |
2393 | |
2394 | for (h = header_list; h != NULL; h = h->next) | |
2395 | { | |
4b233853 PH |
2396 | uschar *s; |
2397 | if (h->type == htype_old) continue; | |
2398 | ||
2399 | if (strncmpic(h->text, US"List-", 5) == 0) | |
2400 | { | |
2401 | s = h->text + 5; | |
2402 | if (strncmpic(s, US"Id:", 3) == 0 || | |
2403 | strncmpic(s, US"Help:", 5) == 0 || | |
2404 | strncmpic(s, US"Subscribe:", 10) == 0 || | |
2405 | strncmpic(s, US"Unsubscribe:", 12) == 0 || | |
2406 | strncmpic(s, US"Post:", 5) == 0 || | |
2407 | strncmpic(s, US"Owner:", 6) == 0 || | |
2408 | strncmpic(s, US"Archive:", 8) == 0) | |
2409 | return FALSE; | |
2410 | } | |
2411 | ||
2412 | else if (strncmpic(h->text, US"Auto-submitted:", 15) == 0) | |
2413 | { | |
2414 | s = h->text + 15; | |
2415 | while (isspace(*s)) s++; | |
2416 | if (strncmpic(s, US"no", 2) != 0) return FALSE; | |
2417 | s += 2; | |
2418 | while (isspace(*s)) s++; | |
2419 | if (*s != 0) return FALSE; | |
2420 | } | |
059ec3d9 PH |
2421 | } |
2422 | ||
2423 | /* Set up "my" address */ | |
2424 | ||
2425 | self = string_sprintf("%s@%s", deliver_localpart, deliver_domain); | |
2426 | self_from = rewrite_one(self, rewrite_from, NULL, FALSE, US"", | |
2427 | global_rewrite_rules); | |
2428 | self_to = rewrite_one(self, rewrite_to, NULL, FALSE, US"", | |
2429 | global_rewrite_rules); | |
2430 | ||
2431 | ||
2432 | if (self_from == NULL) self_from = self; | |
2433 | if (self_to == NULL) self_to = self; | |
2434 | ||
2435 | /* If there's a prefix or suffix set, we must include the prefixed/ | |
2436 | suffixed version of the local part in the tests. */ | |
2437 | ||
2438 | if (deliver_localpart_prefix != NULL || deliver_localpart_suffix != NULL) | |
2439 | { | |
2440 | psself = string_sprintf("%s%s%s@%s", | |
2441 | (deliver_localpart_prefix == NULL)? US"" : deliver_localpart_prefix, | |
2442 | deliver_localpart, | |
2443 | (deliver_localpart_suffix == NULL)? US"" : deliver_localpart_suffix, | |
2444 | deliver_domain); | |
2445 | psself_from = rewrite_one(psself, rewrite_from, NULL, FALSE, US"", | |
2446 | global_rewrite_rules); | |
2447 | psself_to = rewrite_one(psself, rewrite_to, NULL, FALSE, US"", | |
2448 | global_rewrite_rules); | |
2449 | if (psself_from == NULL) psself_from = psself; | |
2450 | if (psself_to == NULL) psself_to = psself; | |
2451 | to_count += 2; | |
2452 | from_count += 2; | |
2453 | } | |
2454 | ||
2455 | /* Do all the necessary tests; the counts are adjusted for {pre,suf}fix */ | |
2456 | ||
2457 | yield = | |
2458 | ( | |
2459 | header_match(US"to:", TRUE, TRUE, aliases, to_count, self, self_to, psself, | |
2460 | psself_to) || | |
2461 | (scan_cc && | |
2462 | ( | |
2463 | header_match(US"cc:", TRUE, TRUE, aliases, to_count, self, self_to, | |
2464 | psself, psself_to) | |
2465 | || | |
2466 | header_match(US"bcc:", TRUE, TRUE, aliases, to_count, self, self_to, | |
2467 | psself, psself_to) | |
2468 | ) | |
2469 | ) | |
2470 | ) && | |
2471 | ||
2472 | header_match(US"from:", TRUE, FALSE, aliases, from_count, "^server@", | |
2473 | "^daemon@", "^root@", "^listserv@", "^majordomo@", "^.*?-request@", | |
2474 | "^owner-[^@]+@", self, self_from, psself, psself_from) && | |
2475 | ||
059ec3d9 PH |
2476 | header_match(US"precedence:", FALSE, FALSE, NULL, 3, "bulk","list","junk") && |
2477 | ||
2478 | (sender_address == NULL || sender_address[0] != 0); | |
2479 | ||
2480 | store_reset(reset_point); | |
2481 | return yield; | |
2482 | } | |
2483 | ||
2484 | ||
2485 | ||
2486 | /************************************************* | |
2487 | * Interpret a mail filter file * | |
2488 | *************************************************/ | |
2489 | ||
2490 | /* | |
2491 | Arguments: | |
2492 | filter points to the entire file, read into store as a single string | |
2493 | options controls whether various special things are allowed, and requests | |
2494 | special actions | |
2495 | generated where to hang newly-generated addresses | |
2496 | error where to pass back an error text | |
2497 | ||
2498 | Returns: FF_DELIVERED success, a significant action was taken | |
2499 | FF_NOTDELIVERED success, no significant action | |
2500 | FF_DEFER defer requested | |
2501 | FF_FAIL fail requested | |
2502 | FF_FREEZE freeze requested | |
2503 | FF_ERROR there was a problem | |
2504 | */ | |
2505 | ||
2506 | int | |
2507 | filter_interpret(uschar *filter, int options, address_item **generated, | |
2508 | uschar **error) | |
2509 | { | |
2510 | int i; | |
2511 | int yield = FF_ERROR; | |
2512 | uschar *ptr = filter; | |
2513 | uschar *save_headers_charset = headers_charset; | |
2514 | filter_cmd *commands = NULL; | |
2515 | filter_cmd **lastcmdptr = &commands; | |
2516 | ||
2517 | DEBUG(D_route) debug_printf("Filter: start of processing\n"); | |
2518 | ||
2519 | /* Initialize "not in an if command", set the global flag that is always TRUE | |
2520 | while filtering, and zero the variables. */ | |
2521 | ||
2522 | expect_endif = 0; | |
2523 | output_indent = 0; | |
2524 | filter_running = TRUE; | |
2525 | for (i = 0; i < FILTER_VARIABLE_COUNT; i++) filter_n[i] = 0; | |
2526 | ||
2527 | /* To save having to pass certain values about all the time, make them static. | |
2528 | Also initialize the line number, for error messages, and the log file | |
2529 | variables. */ | |
2530 | ||
2531 | filter_options = options; | |
2532 | filter_delivered = FALSE; | |
2533 | finish_obeyed = FALSE; | |
2534 | error_pointer = error; | |
2535 | *error_pointer = NULL; | |
2536 | line_number = 1; | |
2537 | log_fd = -1; | |
2538 | log_mode = 0600; | |
2539 | log_filename = NULL; | |
2540 | ||
2541 | /* Scan filter file for syntax and build up an interpretation thereof, and | |
2542 | interpret the compiled commands, and if testing, say whether we ended up | |
2543 | delivered or not, unless something went wrong. */ | |
2544 | ||
2545 | seen_force = FALSE; | |
2546 | ptr = nextsigchar(ptr, TRUE); | |
2547 | ||
2548 | if (read_command_list(&ptr, &lastcmdptr, FALSE)) | |
2549 | yield = interpret_commands(commands, generated); | |
2550 | ||
f05da2e8 | 2551 | if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0) |
059ec3d9 PH |
2552 | { |
2553 | uschar *s = US""; | |
2554 | switch(yield) | |
2555 | { | |
2556 | case FF_DEFER: | |
2557 | s = US"Filtering ended by \"defer\"."; | |
2558 | break; | |
2559 | ||
2560 | case FF_FREEZE: | |
2561 | s = US"Filtering ended by \"freeze\"."; | |
2562 | break; | |
2563 | ||
2564 | case FF_FAIL: | |
2565 | s = US"Filtering ended by \"fail\"."; | |
2566 | break; | |
2567 | ||
2568 | case FF_DELIVERED: | |
2569 | s = US"Filtering set up at least one significant delivery " | |
2570 | "or other action.\n" | |
2571 | "No other deliveries will occur."; | |
2572 | break; | |
2573 | ||
2574 | case FF_NOTDELIVERED: | |
2575 | s = US"Filtering did not set up a significant delivery.\n" | |
2576 | "Normal delivery will occur."; | |
2577 | break; | |
2578 | ||
2579 | case FF_ERROR: | |
2580 | s = string_sprintf("Filter error: %s", *error); | |
2581 | break; | |
2582 | } | |
2583 | ||
f05da2e8 | 2584 | if (filter_test != FTEST_NONE) printf("%s\n", CS s); |
059ec3d9 PH |
2585 | else debug_printf("%s\n", s); |
2586 | } | |
2587 | ||
2588 | /* Close the log file if it was opened, and kill off any numerical variables | |
2589 | before returning. Reset the header decoding charset. */ | |
2590 | ||
f1e894f3 | 2591 | if (log_fd >= 0) (void)close(log_fd); |
059ec3d9 PH |
2592 | expand_nmax = -1; |
2593 | filter_running = FALSE; | |
2594 | headers_charset = save_headers_charset; | |
2595 | ||
2596 | DEBUG(D_route) debug_printf("Filter: end of processing\n"); | |
2597 | return yield; | |
2598 | } | |
2599 | ||
2600 | ||
2601 | /* End of filter.c */ |