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