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