Avoid calling gettimeofday(), select() per char for cmdline message submission. ...
[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 && !stdin_feof())
49   {
50   if (!f.dot_ends)
51     {
52     while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != 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 = stdin_getc(GETC_BUFFER_UNLIMITED)) != 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: ;
99     }
100   if (s == message_body_end || s[-1] != '\n') body_linecount++;
101   }
102 debug_printf("%s %d\n", __FUNCTION__, __LINE__);
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, TRUE);
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, is_tainted(filename));
183 rc = read(fd, filebuf, statbuf.st_size);
184 (void)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) 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   f.system_filtering = TRUE;
264   f.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   f.enable_dollar_recipients = FALSE;
269   f.system_filtering = FALSE;
270   }
271 else
272   {
273   yield = filter_type == FILTER_SIEVE
274     ? sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error)
275     : filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
276   }
277
278 return yield != FF_ERROR;
279 }
280
281 /* End of filtertest.c */