Commit | Line | Data |
---|---|---|
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 |
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 | |
328895cc PH |
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. | |
059ec3d9 | 29 | |
8e669ac1 | 30 | Arguments: |
059ec3d9 PH |
31 | dot_ended TRUE if message already terminated by '.' |
32 | ||
f05da2e8 | 33 | Returns: nothing |
059ec3d9 | 34 | */ |
8e669ac1 | 35 | |
328895cc | 36 | void |
48a53b7f | 37 | read_message_body(BOOL dot_ended) |
8e669ac1 | 38 | { |
059ec3d9 | 39 | register int ch; |
f05da2e8 PH |
40 | int body_len, body_end_len, header_size; |
41 | uschar *s; | |
059ec3d9 PH |
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 | } | |
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 | |
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 | |
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 | ||
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); | |
f1e894f3 | 185 | (void)close(fd); |
f05da2e8 PH |
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 | ||
8e669ac1 | 252 | /* For a filter, set up the message_body variables and the message size if this |
f05da2e8 PH |
253 | is the first time this function has been called. */ |
254 | ||
255 | if (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 |
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. */ | |
059ec3d9 PH |
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)? | |
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 | ||
280 | return yield != FF_ERROR; | |
281 | } | |
282 | ||
283 | /* End of filtertest.c */ |