1 /* $Cambridge: exim/src/src/filtertest.c,v 1.12 2009/11/16 19:50:37 nm4 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2009 */
8 /* See the file NOTICE for conditions of use and distribution. */
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 && !feof(stdin))
54 while ((ch = getc(stdin)) != 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 = getc(stdin)) != 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;
100 READ_END: ch = ch; /* Some compilers don't like null statements */
102 if (s == message_body_end || s[-1] != '\n') body_linecount++;
105 message_body[body_len] = 0;
106 message_body_size = message_size - header_size;
108 /* body_len stops at message_body_visible; it if got there, we may have
109 wrapped round in message_body_end. */
111 if (body_len >= message_body_visible)
113 int below = s - message_body_end;
114 int above = message_body_visible - below;
117 uschar *temp = store_get(below);
118 memcpy(temp, message_body_end, below);
119 memmove(message_body_end, s+1, above);
120 memcpy(message_body_end + above, temp, below);
121 s = message_body_end + message_body_visible;
126 body_end_len = s - message_body_end;
128 /* Convert newlines and nulls in the body variables to spaces */
132 if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
133 message_body[body_len] = ' ';
136 while (body_end_len > 0)
138 if (message_body_end[--body_end_len] == '\n' ||
139 message_body_end[body_end_len] == 0)
140 message_body_end[body_end_len] = ' ';
146 /*************************************************
147 * Test a mail filter *
148 *************************************************/
150 /* This is called when exim is run with the -bf option. At this point it is
151 running under an unprivileged uid/gid. A test message's headers have been read
152 into store, and the body of the message is still accessible on the standard
153 input if this is the first time this function has been called. It may be called
154 twice if both system and user filters are being tested.
157 fd an fd containing the filter file
158 filename the name of the filter file
159 is_system TRUE if testing is to be as a system filter
160 dot_ended TRUE if message already terminated by '.'
162 Returns: TRUE if no errors
166 filter_runtest(int fd, uschar *filename, BOOL is_system, BOOL dot_ended)
171 address_item *generated = NULL;
172 uschar *error, *filebuf;
174 /* Read the filter file into store as will be done by the router in a real
177 if (fstat(fd, &statbuf) != 0)
179 printf("exim: failed to get size of %s: %s\n", filename, strerror(errno));
183 filebuf = store_get(statbuf.st_size + 1);
184 rc = read(fd, filebuf, statbuf.st_size);
187 if (rc != statbuf.st_size)
189 printf("exim: error while reading %s: %s\n", filename, strerror(errno));
193 filebuf[statbuf.st_size] = 0;
195 /* Check the filter type. User filters start with "# Exim filter" or "# Sieve
196 filter". If the filter type is not recognized, the file is treated as an
197 ordinary .forward file. System filters do not need the "# Exim filter" in order
198 to be recognized as Exim filters. */
200 filter_type = rda_is_filter(filebuf);
201 if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
203 printf("Testing %s file \"%s\"\n\n",
204 (filter_type == FILTER_EXIM)? "Exim filter" :
205 (filter_type == FILTER_SIEVE)? "Sieve filter" :
209 /* Handle a plain .forward file */
211 if (filter_type == FILTER_FORWARD)
213 yield = parse_forward_list(filebuf,
215 &generated, /* for generated addresses */
216 &error, /* for errors */
217 deliver_domain, /* incoming domain for \name */
218 NULL, /* no check on includes */
219 NULL); /* fail on syntax errors */
224 printf("exim: forward file contains \":fail:\"\n");
228 printf("exim: forwardfile contains \":blackhole:\"\n");
232 printf("exim: error in forward file: %s\n", error);
236 if (generated == NULL)
237 printf("exim: no addresses generated from forward file\n");
241 printf("exim: forward file generated:\n");
242 while (generated != NULL)
244 printf(" %s\n", generated->address);
245 generated = generated->next;
252 /* For a filter, set up the message_body variables and the message size if this
253 is the first time this function has been called. */
255 if (message_body == NULL) read_message_body(dot_ended);
257 /* Now pass the filter file to the function that interprets it. Because
258 filter_test is not FILTER_NONE, the interpreter will output comments about what
259 it is doing. No need to clean up store. Indeed, we must not, because we may be
260 testing a system filter that is going to be followed by a user filter test. */
264 system_filtering = TRUE;
265 enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
266 yield = filter_interpret
268 RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
269 enable_dollar_recipients = FALSE;
270 system_filtering = FALSE;
274 yield = (filter_type == FILTER_SIEVE)?
275 sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error)
277 filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
280 return yield != FF_ERROR;
283 /* End of filtertest.c */