0cdee0304dc7902664626b5bea973fc3775e53ed
[exim.git] / src / src / filtertest.c
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
22 of the filter file is in filter_test, and we are running under an
23 unprivileged uid/gid. A test message's headers have been read into
24 store, and the body of the message is still accessible on the
25 standard input.
26
27 Argument:
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
32 Returns: TRUE if no errors
33 */
34
35 BOOL
36 filter_runtest(int fd, BOOL is_system, BOOL dot_ended)
37 {
38 int rc, body_len, body_end_len, filter_type, header_size;
39 register int ch;
40 BOOL yield;
41 struct stat statbuf;
42 address_item *generated = NULL;
43 uschar *error, *filebuf, *s;
44
45 /* Read the filter file into store as will be done by the router in a real
46 case. */
47
48 if (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
54 filebuf = store_get(statbuf.st_size + 1);
55 rc = read(fd, filebuf, statbuf.st_size);
56 close(fd);
57
58 if (rc != statbuf.st_size)
59 {
60 printf("exim: error while reading %s: %s\n", filter_test, strerror(errno));
61 return FALSE;
62 }
63
64 filebuf[statbuf.st_size] = 0;
65
66 /* Check the filter type. User filters start with "# Exim filter" or "# Sieve
67 filter". If the filter type is not recognized, the file is treated as an
68 ordinary .forward file. System filters do not need the "# Exim filter" in order
69 to be recognized as Exim filters. */
70
71 filter_type = rda_is_filter(filebuf);
72 if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
73
74 printf("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
82 if (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
124 its 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
126 written out here rather than having options in read_message_data, in order to
127 keep that function as efficient as possible. Handling message_body_end is
128 somewhat more tedious. Pile it all into a circular buffer and sort out at the
129 end. */
130
131 message_body = store_malloc(message_body_visible + 1);
132 message_body_end = store_malloc(message_body_visible + 1);
133 s = message_body_end;
134 body_len = 0;
135 body_linecount = 0;
136 header_size = message_size;
137
138 if (!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
193 message_body[body_len] = 0;
194 message_body_size = message_size - header_size;
195
196 /* body_len stops at message_body_visible; it if got there, we may have
197 wrapped round in message_body_end. */
198
199 if (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;
214 body_end_len = s - message_body_end;
215
216 /* Convert newlines and nulls in the body variables to spaces */
217
218 while (body_len > 0)
219 {
220 if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
221 message_body[body_len] = ' ';
222 }
223
224 while (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
232 filter_test is not NULL, the interpreter will output comments about what
233 it is doing, but an error message will have to be output here. No need to
234 clean up store. The last argument is 0 because Exim has given up root privilege
235 when running a filter test, and in any case, as it is a test, is isn't going to
236 try writing any files. */
237
238 if (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 }
248 else
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
256 return yield != FF_ERROR;
257 }
258
259 /* End of filtertest.c */