Commit | Line | Data |
---|---|---|
059ec3d9 PH |
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 */ |