6eb92b2ea7091a1203fd1801c016a68df8884331
[exim.git] / src / src / filtertest.c
1 /* $Cambridge: exim/src/src/filtertest.c,v 1.12 2009/11/16 19:50:37 nm4 Exp $ */
2
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2009 */
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. (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.
29
30 Arguments:
31 dot_ended TRUE if message already terminated by '.'
32
33 Returns: nothing
34 */
35
36 void
37 read_message_body(BOOL dot_ended)
38 {
39 register int ch;
40 int body_len, body_end_len, header_size;
41 uschar *s;
42
43 message_body = store_malloc(message_body_visible + 1);
44 message_body_end = store_malloc(message_body_visible + 1);
45 s = message_body_end;
46 body_len = 0;
47 body_linecount = 0;
48 header_size = message_size;
49
50 if (!dot_ended && !feof(stdin))
51 {
52 if (!dot_ends)
53 {
54 while ((ch = getc(stdin)) != EOF)
55 {
56 if (ch == 0) body_zerocount++;
57 if (ch == '\n') body_linecount++;
58 if (body_len < message_body_visible) message_body[body_len++] = ch;
59 *s++ = ch;
60 if (s > message_body_end + message_body_visible) s = message_body_end;
61 message_size++;
62 }
63 }
64 else
65 {
66 int ch_state = 1;
67 while ((ch = getc(stdin)) != EOF)
68 {
69 if (ch == 0) body_zerocount++;
70 switch (ch_state)
71 {
72 case 0: /* Normal state */
73 if (ch == '\n') { body_linecount++; ch_state = 1; }
74 break;
75
76 case 1: /* After "\n" */
77 if (ch == '.')
78 {
79 ch_state = 2;
80 continue;
81 }
82 if (ch != '\n') ch_state = 0;
83 break;
84
85 case 2: /* After "\n." */
86 if (ch == '\n') goto READ_END;
87 if (body_len < message_body_visible) message_body[body_len++] = '.';
88 *s++ = '.';
89 if (s > message_body_end + message_body_visible)
90 s = message_body_end;
91 message_size++;
92 ch_state = 0;
93 break;
94 }
95 if (body_len < message_body_visible) message_body[body_len++] = ch;
96 *s++ = ch;
97 if (s > message_body_end + message_body_visible) s = message_body_end;
98 message_size++;
99 }
100 READ_END: ch = ch; /* Some compilers don't like null statements */
101 }
102 if (s == message_body_end || s[-1] != '\n') body_linecount++;
103 }
104
105 message_body[body_len] = 0;
106 message_body_size = message_size - header_size;
107
108 /* body_len stops at message_body_visible; it if got there, we may have
109 wrapped round in message_body_end. */
110
111 if (body_len >= message_body_visible)
112 {
113 int below = s - message_body_end;
114 int above = message_body_visible - below;
115 if (above > 0)
116 {
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;
122 }
123 }
124
125 *s = 0;
126 body_end_len = s - message_body_end;
127
128 /* Convert newlines and nulls in the body variables to spaces */
129
130 while (body_len > 0)
131 {
132 if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
133 message_body[body_len] = ' ';
134 }
135
136 while (body_end_len > 0)
137 {
138 if (message_body_end[--body_end_len] == '\n' ||
139 message_body_end[body_end_len] == 0)
140 message_body_end[body_end_len] = ' ';
141 }
142 }
143
144
145
146 /*************************************************
147 * Test a mail filter *
148 *************************************************/
149
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.
155
156 Argument:
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 '.'
161
162 Returns: TRUE if no errors
163 */
164
165 BOOL
166 filter_runtest(int fd, uschar *filename, BOOL is_system, BOOL dot_ended)
167 {
168 int rc, filter_type;
169 BOOL yield;
170 struct stat statbuf;
171 address_item *generated = NULL;
172 uschar *error, *filebuf;
173
174 /* Read the filter file into store as will be done by the router in a real
175 case. */
176
177 if (fstat(fd, &statbuf) != 0)
178 {
179 printf("exim: failed to get size of %s: %s\n", filename, strerror(errno));
180 return FALSE;
181 }
182
183 filebuf = store_get(statbuf.st_size + 1);
184 rc = read(fd, filebuf, statbuf.st_size);
185 (void)close(fd);
186
187 if (rc != statbuf.st_size)
188 {
189 printf("exim: error while reading %s: %s\n", filename, strerror(errno));
190 return FALSE;
191 }
192
193 filebuf[statbuf.st_size] = 0;
194
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. */
199
200 filter_type = rda_is_filter(filebuf);
201 if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
202
203 printf("Testing %s file \"%s\"\n\n",
204 (filter_type == FILTER_EXIM)? "Exim filter" :
205 (filter_type == FILTER_SIEVE)? "Sieve filter" :
206 "forward file",
207 filename);
208
209 /* Handle a plain .forward file */
210
211 if (filter_type == FILTER_FORWARD)
212 {
213 yield = parse_forward_list(filebuf,
214 RDO_REWRITE,
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 */
220
221 switch(yield)
222 {
223 case FF_FAIL:
224 printf("exim: forward file contains \":fail:\"\n");
225 break;
226
227 case FF_BLACKHOLE:
228 printf("exim: forwardfile contains \":blackhole:\"\n");
229 break;
230
231 case FF_ERROR:
232 printf("exim: error in forward file: %s\n", error);
233 return FALSE;
234 }
235
236 if (generated == NULL)
237 printf("exim: no addresses generated from forward file\n");
238
239 else
240 {
241 printf("exim: forward file generated:\n");
242 while (generated != NULL)
243 {
244 printf(" %s\n", generated->address);
245 generated = generated->next;
246 }
247 }
248
249 return TRUE;
250 }
251
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. */
254
255 if (message_body == NULL) read_message_body(dot_ended);
256
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. */
261
262 if (is_system)
263 {
264 system_filtering = TRUE;
265 enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
266 yield = filter_interpret
267 (filebuf,
268 RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
269 enable_dollar_recipients = FALSE;
270 system_filtering = FALSE;
271 }
272 else
273 {
274 yield = (filter_type == FILTER_SIEVE)?
275 sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error)
276 :
277 filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
278 }
279
280 return yield != FF_ERROR;
281 }
282
283 /* End of filtertest.c */