SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / filtertest.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2009 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-only */
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 && !stdin_feof())
51   {
52   if (!f.dot_ends)
53     {
54     while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != 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 = stdin_getc(GETC_BUFFER_UNLIMITED)) != 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: ;
101     }
102   if (s == message_body_end || s[-1] != '\n') body_linecount++;
103   }
104 debug_printf("%s %d\n", __FUNCTION__, __LINE__);
105
106 message_body[body_len] = 0;
107 message_body_size = message_size - header_size;
108
109 /* body_len stops at message_body_visible; it if got there, we may have
110 wrapped round in message_body_end. */
111
112 if (body_len >= message_body_visible)
113   {
114   int below = s - message_body_end;
115   int above = message_body_visible - below;
116   if (above > 0)
117     {
118     uschar * temp = store_get(below, GET_UNTAINTED);
119     memcpy(temp, message_body_end, below);
120     memmove(message_body_end, s+1, above);
121     memcpy(message_body_end + above, temp, below);
122     s = message_body_end + message_body_visible;
123     }
124   }
125
126 *s = 0;
127 body_end_len = s - message_body_end;
128
129 /* Convert newlines and nulls in the body variables to spaces */
130
131 while (body_len > 0)
132   {
133   if (message_body[--body_len] == '\n' || message_body[body_len] == 0)
134     message_body[body_len] = ' ';
135   }
136
137 while (body_end_len > 0)
138   {
139   if (message_body_end[--body_end_len] == '\n' ||
140       message_body_end[body_end_len] == 0)
141     message_body_end[body_end_len] = ' ';
142   }
143 }
144
145
146
147 /*************************************************
148 *            Test a mail filter                  *
149 *************************************************/
150
151 /* This is called when exim is run with the -bf option. At this point it is
152 running under an unprivileged uid/gid. A test message's headers have been read
153 into store, and the body of the message is still accessible on the standard
154 input if this is the first time this function has been called. It may be called
155 twice if both system and user filters are being tested.
156
157 Argument:
158   fd          an fd containing the filter file
159   filename    the name of the filter file
160   is_system   TRUE if testing is to be as a system filter
161   dot_ended   TRUE if message already terminated by '.'
162
163 Returns:      TRUE if no errors
164 */
165
166 BOOL
167 filter_runtest(int fd, uschar *filename, BOOL is_system, BOOL dot_ended)
168 {
169 int rc, filter_type;
170 BOOL yield;
171 struct stat statbuf;
172 address_item *generated = NULL;
173 uschar *error, *filebuf;
174
175 /* Read the filter file into store as will be done by the router in a real
176 case. */
177
178 if (fstat(fd, &statbuf) != 0)
179   {
180   printf("exim: failed to get size of %s: %s\n", filename, strerror(errno));
181   return FALSE;
182   }
183
184 filebuf = store_get(statbuf.st_size + 1, filename);
185 rc = read(fd, filebuf, statbuf.st_size);
186 (void)close(fd);
187
188 if (rc != statbuf.st_size)
189   {
190   printf("exim: error while reading %s: %s\n", filename, strerror(errno));
191   return FALSE;
192   }
193
194 filebuf[statbuf.st_size] = 0;
195
196 /* Check the filter type. User filters start with "# Exim filter" or "# Sieve
197 filter". If the filter type is not recognized, the file is treated as an
198 ordinary .forward file. System filters do not need the "# Exim filter" in order
199 to be recognized as Exim filters. */
200
201 filter_type = rda_is_filter(filebuf);
202 if (is_system && filter_type == FILTER_FORWARD) filter_type = FILTER_EXIM;
203
204 printf("Testing %s file \"%s\"\n\n",
205   (filter_type == FILTER_EXIM)? "Exim filter" :
206   (filter_type == FILTER_SIEVE)? "Sieve filter" :
207   "forward file",
208   filename);
209
210 /* Handle a plain .forward file */
211
212 if (filter_type == FILTER_FORWARD)
213   {
214   yield = parse_forward_list(filebuf,
215     RDO_REWRITE,
216     &generated,                     /* for generated addresses */
217     &error,                         /* for errors */
218     deliver_domain,                 /* incoming domain for \name */
219     NULL,                           /* no check on includes */
220     NULL);                          /* fail on syntax errors */
221
222   switch(yield)
223     {
224     case FF_FAIL:
225     printf("exim: forward file contains \":fail:\"\n");
226     break;
227
228     case FF_BLACKHOLE:
229     printf("exim: forwardfile contains \":blackhole:\"\n");
230     break;
231
232     case FF_ERROR:
233     printf("exim: error in forward file: %s\n", error);
234     return FALSE;
235     }
236
237   if (generated == NULL)
238     printf("exim: no addresses generated from forward file\n");
239
240   else
241     {
242     printf("exim: forward file generated:\n");
243     while (generated != NULL)
244       {
245       printf("  %s\n", generated->address);
246       generated = generated->next;
247       }
248     }
249
250   return TRUE;
251   }
252
253 /* For a filter, set up the message_body variables and the message size if this
254 is the first time this function has been called. */
255
256 if (!message_body) read_message_body(dot_ended);
257
258 /* Now pass the filter file to the function that interprets it. Because
259 filter_test is not FILTER_NONE, the interpreter will output comments about what
260 it is doing. No need to clean up store. Indeed, we must not, because we may be
261 testing a system filter that is going to be followed by a user filter test. */
262
263 if (is_system)
264   {
265   f.system_filtering = TRUE;
266   f.enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
267   yield = filter_interpret
268     (filebuf,
269     RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
270   f.enable_dollar_recipients = FALSE;
271   f.system_filtering = FALSE;
272   }
273 else
274   {
275   yield = filter_type == FILTER_SIEVE
276     ? sieve_interpret(filebuf, RDO_REWRITE, NULL, NULL, NULL, NULL, &generated, &error)
277     : filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
278   }
279
280 return yield != FF_ERROR;
281 }
282
283 /* End of filtertest.c */