ChangeLog entry JH/18; applying to 4.89 release
[users/heiko/exim.git] / src / src / filtertest.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
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 /*************************************************
16 *    Read message and set body/size variables    *
17 *************************************************/
18
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
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.
27
28 Arguments:
29   dot_ended   TRUE if message already terminated by '.'
30
31 Returns:      nothing
32 */
33
34 void
35 read_message_body(BOOL dot_ended)
36 {
37 register int ch;
38 int body_len, body_end_len, header_size;
39 uschar *s;
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   }
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
156   filename    the name of the filter file
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);
183 (void)close(fd);
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
250 /* For a filter, set up the message_body variables and the message size if this
251 is the first time this function has been called. */
252
253 if (message_body == NULL) read_message_body(dot_ended);
254
255 /* Now pass the filter file to the function that interprets it. Because
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. */
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)?
273     sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error)
274     :
275     filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
276   }
277
278 return yield != FF_ERROR;
279 }
280
281 /* End of filtertest.c */