Commit | Line | Data |
---|---|---|
059ec3d9 PH |
1 | /************************************************* |
2 | * Exim - an Internet mail transport agent * | |
3 | *************************************************/ | |
4 | ||
0a49a7a4 | 5 | /* Copyright (c) University of Cambridge 1995 - 2009 */ |
059ec3d9 PH |
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 | /************************************************* | |
f05da2e8 | 16 | * Read message and set body/size variables * |
059ec3d9 PH |
17 | *************************************************/ |
18 | ||
f05da2e8 PH |
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 | |
328895cc PH |
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. | |
059ec3d9 | 27 | |
8e669ac1 | 28 | Arguments: |
059ec3d9 PH |
29 | dot_ended TRUE if message already terminated by '.' |
30 | ||
f05da2e8 | 31 | Returns: nothing |
059ec3d9 | 32 | */ |
8e669ac1 | 33 | |
328895cc | 34 | void |
48a53b7f | 35 | read_message_body(BOOL dot_ended) |
8e669ac1 | 36 | { |
059ec3d9 | 37 | register int ch; |
f05da2e8 PH |
38 | int body_len, body_end_len, header_size; |
39 | uschar *s; | |
059ec3d9 PH |
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 (!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 | } | |
f05da2e8 PH |
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 | |
8e669ac1 | 156 | filename the name of the filter file |
f05da2e8 PH |
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); | |
f1e894f3 | 183 | (void)close(fd); |
f05da2e8 PH |
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 | ||
8e669ac1 | 250 | /* For a filter, set up the message_body variables and the message size if this |
f05da2e8 PH |
251 | is the first time this function has been called. */ |
252 | ||
253 | if (message_body == NULL) read_message_body(dot_ended); | |
059ec3d9 PH |
254 | |
255 | /* Now pass the filter file to the function that interprets it. Because | |
f05da2e8 PH |
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. */ | |
059ec3d9 PH |
259 | |
260 | if (is_system) | |
261 | { | |
262 | system_filtering = TRUE; | |
263 | 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 | enable_dollar_recipients = FALSE; | |
268 | system_filtering = FALSE; | |
269 | } | |
270 | else | |
271 | { | |
272 | yield = (filter_type == FILTER_SIEVE)? | |
efd9a422 | 273 | sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error) |
059ec3d9 PH |
274 | : |
275 | filter_interpret(filebuf, RDO_REWRITE, &generated, &error); | |
276 | } | |
277 | ||
278 | return yield != FF_ERROR; | |
279 | } | |
280 | ||
281 | /* End of filtertest.c */ |