(1) Last-minute sieve patch (updates to latest spec).
[exim.git] / src / src / filtertest.c
1 /* $Cambridge: exim/src/src/filtertest.c,v 1.4 2005/02/17 11:58:26 ph10 Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2005 */
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 *    Read message and set body/size variables    *
19 *************************************************/
20
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.
28
29 Arguments:
30   dot_ended   TRUE if message already terminated by '.'
31
32 Returns:      nothing
33 */
34
35 static void
36 read_message_body(dot_ended)
37 {
38 register int ch;
39 int body_len, body_end_len, header_size;
40 uschar *s;
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   }
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
157   filename    the name of the filter file
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
251 /* For a filter, set up the message_body variables and the message size if this
252 is the first time this function has been called. */
253
254 if (message_body == NULL) read_message_body(dot_ended);
255
256 /* Now pass the filter file to the function that interprets it. Because
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. */
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 */