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