Testing tweak to improve repeatability.
[exim.git] / src / src / filtertest.c
CommitLineData
059ec3d9
PH
1/* $Cambridge: exim/src/src/filtertest.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
7/* Copyright (c) University of Cambridge 1995 - 2004 */
8/* See the file NOTICE for conditions of use and distribution. */
9
10
11/* Code for the filter test function. */
12
13#include "exim.h"
14
15
16
17/*************************************************
18* Test a mail filter *
19*************************************************/
20
21/* This is called when exim is run with the -bf option. The name
22of the filter file is in filter_test, and we are running under an
23unprivileged uid/gid. A test message's headers have been read into
24store, and the body of the message is still accessible on the
25standard input.
26
27Argument:
28 fd the standard input fd, containing the message body
29 is_system TRUE if testing is to be as a system filter
30 dot_ended TRUE if message already terminated by '.'
31
32Returns: TRUE if no errors
33*/
34
35BOOL
36filter_runtest(int fd, BOOL is_system, BOOL dot_ended)
37{
38int rc, body_len, body_end_len, filter_type, header_size;
39register int ch;
40BOOL yield;
41struct stat statbuf;
42address_item *generated = NULL;
43uschar *error, *filebuf, *s;
44
45/* Read the filter file into store as will be done by the router in a real
46case. */
47
48if (fstat(fd, &statbuf) != 0)
49 {
50 printf("exim: failed to get size of %s: %s\n", filter_test, strerror(errno));
51 return FALSE;
52 }
53
54filebuf = store_get(statbuf.st_size + 1);
55rc = read(fd, filebuf, statbuf.st_size);
56close(fd);
57
58if (rc != statbuf.st_size)
59 {
60 printf("exim: error while reading %s: %s\n", filter_test, strerror(errno));
61 return FALSE;
62 }
63
64filebuf[statbuf.st_size] = 0;
65
66/* Check the filter type. User filters start with "# Exim filter" or "# Sieve
67filter". If the filter type is not recognized, the file is treated as an
68ordinary .forward file. System filters do not need the "# Exim filter" in order
69to be recognized as Exim filters. */
70
71filter_type = rda_is_filter(filebuf);
72if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
73
74printf("Testing %s file \"%s\"\n\n",
75 (filter_type == FILTER_EXIM)? "Exim filter" :
76 (filter_type == FILTER_SIEVE)? "Sieve filter" :
77 "forward file",
78 filter_test);
79
80/* Handle a plain .forward file */
81
82if (filter_type == FILTER_FORWARD)
83 {
84 yield = parse_forward_list(filebuf,
85 RDO_REWRITE,
86 &generated, /* for generated addresses */
87 &error, /* for errors */
88 deliver_domain, /* incoming domain for \name */
89 NULL, /* no check on includes */
90 NULL); /* fail on syntax errors */
91
92 switch(yield)
93 {
94 case FF_FAIL:
95 printf("exim: forward file contains \":fail:\"\n");
96 break;
97
98 case FF_BLACKHOLE:
99 printf("exim: forwardfile contains \":blackhole:\"\n");
100 break;
101
102 case FF_ERROR:
103 printf("exim: error in forward file: %s\n", error);
104 return FALSE;
105 }
106
107 if (generated == NULL)
108 printf("exim: no addresses generated from forward file\n");
109
110 else
111 {
112 printf("exim: forward file generated:\n");
113 while (generated != NULL)
114 {
115 printf(" %s\n", generated->address);
116 generated = generated->next;
117 }
118 }
119
120 return TRUE;
121 }
122
123/* For a filter, we have to read the remainder of the message in order to find
124its size, so we might as well set up the message_body variable at the same time
125(when *really* filtering this is not read unless needed). The reading code is
126written out here rather than having options in read_message_data, in order to
127keep that function as efficient as possible. Handling message_body_end is
128somewhat more tedious. Pile it all into a circular buffer and sort out at the
129end. */
130
131message_body = store_malloc(message_body_visible + 1);
132message_body_end = store_malloc(message_body_visible + 1);
133s = message_body_end;
134body_len = 0;
135body_linecount = 0;
136header_size = message_size;
137
138if (!dot_ended && !feof(stdin))
139 {
140 if (!dot_ends)
141 {
142 while ((ch = getc(stdin)) != EOF)
143 {
144 if (ch == 0) body_zerocount++;
145 if (ch == '\n') body_linecount++;
146 if (body_len < message_body_visible) message_body[body_len++] = ch;
147 *s++ = ch;
148 if (s > message_body_end + message_body_visible) s = message_body_end;
149 message_size++;
150 }
151 }
152 else
153 {
154 int ch_state = 1;
155 while ((ch = getc(stdin)) != EOF)
156 {
157 if (ch == 0) body_zerocount++;
158 switch (ch_state)
159 {
160 case 0: /* Normal state */
161 if (ch == '\n') { body_linecount++; ch_state = 1; }
162 break;
163
164 case 1: /* After "\n" */
165 if (ch == '.')
166 {
167 ch_state = 2;
168 continue;
169 }
170 if (ch != '\n') ch_state = 0;
171 break;
172
173 case 2: /* After "\n." */
174 if (ch == '\n') goto READ_END;
175 if (body_len < message_body_visible) message_body[body_len++] = '.';
176 *s++ = '.';
177 if (s > message_body_end + message_body_visible)
178 s = message_body_end;
179 message_size++;
180 ch_state = 0;
181 break;
182 }
183 if (body_len < message_body_visible) message_body[body_len++] = ch;
184 *s++ = ch;
185 if (s > message_body_end + message_body_visible) s = message_body_end;
186 message_size++;
187 }
188 READ_END: ch = ch; /* Some compilers don't like null statements */
189 }
190 if (s == message_body_end || s[-1] != '\n') body_linecount++;
191 }
192
193message_body[body_len] = 0;
194message_body_size = message_size - header_size;
195
196/* body_len stops at message_body_visible; it if got there, we may have
197wrapped round in message_body_end. */
198
199if (body_len >= message_body_visible)
200 {
201 int below = s - message_body_end;
202 int above = message_body_visible - below;
203 if (above > 0)
204 {
205 uschar *temp = store_get(below);
206 memcpy(temp, message_body_end, below);
207 memmove(message_body_end, s+1, above);
208 memcpy(message_body_end + above, temp, below);
209 s = message_body_end + message_body_visible;
210 }
211 }
212
213*s = 0;
214body_end_len = s - message_body_end;
215
216/* Convert newlines and nulls in the body variables to spaces */
217
218while (body_len > 0)
219 {
220 if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
221 message_body[body_len] = ' ';
222 }
223
224while (body_end_len > 0)
225 {
226 if (message_body_end[--body_end_len] == '\n' ||
227 message_body_end[body_end_len] == 0)
228 message_body_end[body_end_len] = ' ';
229 }
230
231/* Now pass the filter file to the function that interprets it. Because
232filter_test is not NULL, the interpreter will output comments about what
233it is doing, but an error message will have to be output here. No need to
234clean up store. The last argument is 0 because Exim has given up root privilege
235when running a filter test, and in any case, as it is a test, is isn't going to
236try writing any files. */
237
238if (is_system)
239 {
240 system_filtering = TRUE;
241 enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
242 yield = filter_interpret
243 (filebuf,
244 RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
245 enable_dollar_recipients = FALSE;
246 system_filtering = FALSE;
247 }
248else
249 {
250 yield = (filter_type == FILTER_SIEVE)?
251 sieve_interpret(filebuf, RDO_REWRITE, NULL, &generated, &error)
252 :
253 filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
254 }
255
256return yield != FF_ERROR;
257}
258
259/* End of filtertest.c */