Start
[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 */