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