INT_MIN {/,%} -1 = INT_MAX for our purposes.
[exim.git] / src / src / filtertest.c
CommitLineData
0a49a7a4 1/* $Cambridge: exim/src/src/filtertest.c,v 1.12 2009/11/16 19:50:37 nm4 Exp $ */
059ec3d9
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
0a49a7a4 7/* Copyright (c) University of Cambridge 1995 - 2009 */
059ec3d9
PH
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/*************************************************
f05da2e8 18* Read message and set body/size variables *
059ec3d9
PH
19*************************************************/
20
f05da2e8
PH
21/* We have to read the remainder of the message in order to find its size, so
22we can set up the message_body variables at the same time (in normal use, the
23message_body variables are not set up unless needed). The reading code is
24written out here rather than having options in read_message_data, in order to
328895cc
PH
25keep that function as efficient as possible. (Later: this function is now
26global because it is also used by the -bem testing option.) Handling
27message_body_end is somewhat more tedious. Pile it all into a circular buffer
28and sort out at the end.
059ec3d9 29
8e669ac1 30Arguments:
059ec3d9
PH
31 dot_ended TRUE if message already terminated by '.'
32
f05da2e8 33Returns: nothing
059ec3d9 34*/
8e669ac1 35
328895cc 36void
48a53b7f 37read_message_body(BOOL dot_ended)
8e669ac1 38{
059ec3d9 39register int ch;
f05da2e8
PH
40int body_len, body_end_len, header_size;
41uschar *s;
059ec3d9
PH
42
43message_body = store_malloc(message_body_visible + 1);
44message_body_end = store_malloc(message_body_visible + 1);
45s = message_body_end;
46body_len = 0;
47body_linecount = 0;
48header_size = message_size;
49
50if (!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
105message_body[body_len] = 0;
106message_body_size = message_size - header_size;
107
108/* body_len stops at message_body_visible; it if got there, we may have
109wrapped round in message_body_end. */
110
111if (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;
126body_end_len = s - message_body_end;
127
128/* Convert newlines and nulls in the body variables to spaces */
129
130while (body_len > 0)
131 {
132 if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
133 message_body[body_len] = ' ';
134 }
135
136while (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 }
f05da2e8
PH
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
151running under an unprivileged uid/gid. A test message's headers have been read
152into store, and the body of the message is still accessible on the standard
153input if this is the first time this function has been called. It may be called
154twice if both system and user filters are being tested.
155
156Argument:
157 fd an fd containing the filter file
8e669ac1 158 filename the name of the filter file
f05da2e8
PH
159 is_system TRUE if testing is to be as a system filter
160 dot_ended TRUE if message already terminated by '.'
161
162Returns: TRUE if no errors
163*/
164
165BOOL
166filter_runtest(int fd, uschar *filename, BOOL is_system, BOOL dot_ended)
167{
168int rc, filter_type;
169BOOL yield;
170struct stat statbuf;
171address_item *generated = NULL;
172uschar *error, *filebuf;
173
174/* Read the filter file into store as will be done by the router in a real
175case. */
176
177if (fstat(fd, &statbuf) != 0)
178 {
179 printf("exim: failed to get size of %s: %s\n", filename, strerror(errno));
180 return FALSE;
181 }
182
183filebuf = store_get(statbuf.st_size + 1);
184rc = read(fd, filebuf, statbuf.st_size);
f1e894f3 185(void)close(fd);
f05da2e8
PH
186
187if (rc != statbuf.st_size)
188 {
189 printf("exim: error while reading %s: %s\n", filename, strerror(errno));
190 return FALSE;
191 }
192
193filebuf[statbuf.st_size] = 0;
194
195/* Check the filter type. User filters start with "# Exim filter" or "# Sieve
196filter". If the filter type is not recognized, the file is treated as an
197ordinary .forward file. System filters do not need the "# Exim filter" in order
198to be recognized as Exim filters. */
199
200filter_type = rda_is_filter(filebuf);
201if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
202
203printf("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
211if (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
8e669ac1 252/* For a filter, set up the message_body variables and the message size if this
f05da2e8
PH
253is the first time this function has been called. */
254
255if (message_body == NULL) read_message_body(dot_ended);
059ec3d9
PH
256
257/* Now pass the filter file to the function that interprets it. Because
f05da2e8
PH
258filter_test is not FILTER_NONE, the interpreter will output comments about what
259it is doing. No need to clean up store. Indeed, we must not, because we may be
260testing a system filter that is going to be followed by a user filter test. */
059ec3d9
PH
261
262if (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 }
272else
273 {
274 yield = (filter_type == FILTER_SIEVE)?
efd9a422 275 sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error)
059ec3d9
PH
276 :
277 filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
278 }
279
280return yield != FF_ERROR;
281}
282
283/* End of filtertest.c */