1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2021 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2009 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
11 /* Code for the filter test function. */
17 /*************************************************
18 * Read message and set body/size variables *
19 *************************************************/
21 /* We have to read the remainder of the message in order to find its size, so
22 we can set up the message_body variables at the same time (in normal use, the
23 message_body variables are not set up unless needed). The reading code is
24 written out here rather than having options in read_message_data, in order to
25 keep that function as efficient as possible. (Later: this function is now
26 global because it is also used by the -bem testing option.) Handling
27 message_body_end is somewhat more tedious. Pile it all into a circular buffer
28 and sort out at the end.
31 dot_ended TRUE if message already terminated by '.'
37 read_message_body(BOOL dot_ended)
40 int body_len, body_end_len, header_size;
43 message_body = store_malloc(message_body_visible + 1);
44 message_body_end = store_malloc(message_body_visible + 1);
48 header_size = message_size;
50 if (!dot_ended && !stdin_feof())
54 while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != EOF)
56 if (ch == 0) body_zerocount++;
57 if (ch == '\n') body_linecount++;
58 if (body_len < message_body_visible) message_body[body_len++] = ch;
60 if (s > message_body_end + message_body_visible) s = message_body_end;
67 while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != EOF)
69 if (ch == 0) body_zerocount++;
72 case 0: /* Normal state */
73 if (ch == '\n') { body_linecount++; ch_state = 1; }
76 case 1: /* After "\n" */
82 if (ch != '\n') ch_state = 0;
85 case 2: /* After "\n." */
86 if (ch == '\n') goto READ_END;
87 if (body_len < message_body_visible) message_body[body_len++] = '.';
89 if (s > message_body_end + message_body_visible)
95 if (body_len < message_body_visible) message_body[body_len++] = ch;
97 if (s > message_body_end + message_body_visible) s = message_body_end;
102 if (s == message_body_end || s[-1] != '\n') body_linecount++;
104 debug_printf("%s %d\n", __FUNCTION__, __LINE__);
106 message_body[body_len] = 0;
107 message_body_size = message_size - header_size;
109 /* body_len stops at message_body_visible; it if got there, we may have
110 wrapped round in message_body_end. */
112 if (body_len >= message_body_visible)
114 int below = s - message_body_end;
115 int above = message_body_visible - below;
118 uschar * temp = store_get(below, GET_UNTAINTED);
119 memcpy(temp, message_body_end, below);
120 memmove(message_body_end, s+1, above);
121 memcpy(message_body_end + above, temp, below);
122 s = message_body_end + message_body_visible;
127 body_end_len = s - message_body_end;
129 /* Convert newlines and nulls in the body variables to spaces */
133 if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
134 message_body[body_len] = ' ';
137 while (body_end_len > 0)
139 if (message_body_end[--body_end_len] == '\n' ||
140 message_body_end[body_end_len] == 0)
141 message_body_end[body_end_len] = ' ';
147 /*************************************************
148 * Test a mail filter *
149 *************************************************/
151 /* This is called when exim is run with the -bf option. At this point it is
152 running under an unprivileged uid/gid. A test message's headers have been read
153 into store, and the body of the message is still accessible on the standard
154 input if this is the first time this function has been called. It may be called
155 twice if both system and user filters are being tested.
158 fd an fd containing the filter file
159 filename the name of the filter file
160 is_system TRUE if testing is to be as a system filter
161 dot_ended TRUE if message already terminated by '.'
163 Returns: TRUE if no errors
167 filter_runtest(int fd, const uschar * filename, BOOL is_system, BOOL dot_ended)
172 address_item *generated = NULL;
173 uschar *error, *filebuf;
175 /* Read the filter file into store as will be done by the router in a real
178 if (fstat(fd, &statbuf) != 0)
180 printf("exim: failed to get size of %s: %s\n", filename, strerror(errno));
184 filebuf = store_get(statbuf.st_size + 1, filename);
185 rc = read(fd, filebuf, statbuf.st_size);
188 if (rc != statbuf.st_size)
190 printf("exim: error while reading %s: %s\n", filename, strerror(errno));
194 filebuf[statbuf.st_size] = 0;
196 /* Check the filter type. User filters start with "# Exim filter" or "# Sieve
197 filter". If the filter type is not recognized, the file is treated as an
198 ordinary .forward file. System filters do not need the "# Exim filter" in order
199 to be recognized as Exim filters. */
201 filter_type = rda_is_filter(filebuf);
202 if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
204 printf("Testing %s file \"%s\"\n\n",
205 (filter_type == FILTER_EXIM)? "Exim filter" :
206 (filter_type == FILTER_SIEVE)? "Sieve filter" :
210 /* Handle a plain .forward file */
212 if (filter_type == FILTER_FORWARD)
214 yield = parse_forward_list(filebuf,
216 &generated, /* for generated addresses */
217 &error, /* for errors */
218 deliver_domain, /* incoming domain for \name */
219 NULL, /* no check on includes */
220 NULL); /* fail on syntax errors */
225 printf("exim: forward file contains \":fail:\"\n");
229 printf("exim: forwardfile contains \":blackhole:\"\n");
233 printf("exim: error in forward file: %s\n", error);
237 if (generated == NULL)
238 printf("exim: no addresses generated from forward file\n");
242 printf("exim: forward file generated:\n");
243 while (generated != NULL)
245 printf(" %s\n", generated->address);
246 generated = generated->next;
253 /* For a filter, set up the message_body variables and the message size if this
254 is the first time this function has been called. */
256 if (!message_body) read_message_body(dot_ended);
258 /* Now pass the filter file to the function that interprets it. Because
259 filter_test is not FILTER_NONE, the interpreter will output comments about what
260 it is doing. No need to clean up store. Indeed, we must not, because we may be
261 testing a system filter that is going to be followed by a user filter test. */
265 f.system_filtering = TRUE;
266 f.enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
267 yield = filter_interpret
269 RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
270 f.enable_dollar_recipients = FALSE;
271 f.system_filtering = FALSE;
275 yield = filter_type == FILTER_SIEVE
276 ? sieve_interpret(filebuf, RDO_REWRITE, NULL, &generated, &error)
277 : filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
280 return yield != FF_ERROR;
283 /* End of filtertest.c */