1 /*****************************************************************************
2
3 NAME:
4 passthrough.c -- handle writing out message in passthrough ('-p') mode.
5
6 ******************************************************************************/
7
8 #include "common.h"
9
10 #include <assert.h>
11 #include <ctype.h>
12 #include <errno.h>
13 #include <stdlib.h>
14 #ifdef HAVE_SYSLOG_H
15 #include <syslog.h>
16 #endif
17
18 #include "passthrough.h"
19 #include "bogofilter.h"
20 #include "bogoreader.h" /* for is_eol() */
21 #include "fgetsl.h"
22 #include "format.h"
23 #include "textblock.h"
24 #include "xmalloc.h"
25 #include "globals.h"
26
27 #include "lexer.h" /* need have_body */
28
29 static const char *eol;
30 char msg_register[256];
31 static char msg_bogofilter[256];
32 static char msg_spam_header[256];
33 size_t msg_register_size = sizeof(msg_register);
34
35 /* Function Definitions */
36
37 static void cleanup_exit(ex_t exitcode, int killfiles)
38 __attribute__ ((noreturn));
cleanup_exit(ex_t exitcode,int killfiles)39 static void cleanup_exit(ex_t exitcode, int killfiles) {
40 if (fpo) {
41 output_cleanup();
42 }
43 if (killfiles && outfname[0] != '\0') unlink(outfname);
44 exit(exitcode);
45 }
46
47 #define ISSPACE(ch) (isspace(ch) || (ch == '\b'))
48
49 /* check for non-empty blank line */
is_blank_line(const char * line,size_t len)50 static bool is_blank_line(const char *line, size_t len)
51 {
52 if (have_body)
53 return len == 0;
54
55 while (len-- > 0) {
56 byte ch = *line++;
57 if (!ISSPACE(ch))
58 return false;
59 }
60
61 return true;
62 }
63
64 /** check if the line we're looking at is a header/body delimiter */
is_hb_delim(const char * line,size_t len,bool strict_body)65 static bool is_hb_delim(const char *line, size_t len, bool strict_body)
66 {
67 if (strict_body)
68 return len == 0;
69
70 return is_blank_line(line, len);
71 }
72
read_mem(char ** out,void * in)73 static int read_mem(char **out, void *in) {
74 textdata_t **text = (textdata_t **)in;
75 if ((*text)->next) {
76 int s = (*text)->size;
77 *out = (char *)(*text)->data;
78 *text = (*text)->next;
79 return s;
80 }
81 return 0;
82 }
83
84 typedef int (*readfunc_t)(char **, void *);
85
write_spam_info(void)86 static void write_spam_info(void)
87 {
88 if (eol == NULL) /* special treatment of empty input */
89 eol = NL;
90
91 /* print spam-status at the end of the header
92 * then mark the beginning of the message body */
93 if (passthrough || verbose || terse)
94 fprintf(fpo, "%s%s", msg_spam_header, eol);
95
96 if (passthrough || verbose || Rtable) {
97 verbose += passthrough;
98 print_stats( fpo );
99 verbose -= passthrough;
100 }
101 }
102
write_header(rc_t status,readfunc_t rf,void * rfarg)103 static bool write_header(rc_t status, readfunc_t rf, void *rfarg)
104 {
105 ssize_t rd;
106 char *out;
107
108 bool hadlf = true;
109 bool seen_subj = false;
110
111 bool seen_place_header = false;
112
113 int bogolen = strlen(spam_header_name);
114 const char *subjstr = "Subject:";
115 int subjlen = strlen(subjstr);
116
117 eol = NULL;
118
119 /* print headers */
120 while ((rd = rf(&out, rfarg)) > 0)
121 {
122 if (eol == NULL) {
123 if (memcmp(out+rd-1, NL, 1) == 0)
124 eol = NL;
125 if (rd >=2 && memcmp(out+rd-2, CRLF, 2) == 0)
126 eol = CRLF;
127 }
128
129 /* skip over spam_header_name ("X-Bogosity:") lines */
130 while (rd >= bogolen &&
131 memcmp(out, spam_header_name, bogolen) == 0) {
132 while (((rd = rf(&out, rfarg)) > 0) &&
133 (out[0] == ' ' || out[0] == '\t') )
134 /* empty loop */ ;
135 }
136
137 /* detect spam_header_place line */
138 if ( !seen_place_header &&
139 *spam_header_place != '\0' &&
140 memcmp(out, spam_header_place, strlen(spam_header_place)) == 0)
141 {
142 seen_place_header = true;
143 write_spam_info();
144 }
145
146 /* detect end of headers */
147 if (is_eol(out, rd) ||
148 is_hb_delim(out, rd, have_body))
149 /* check for non-empty blank line */
150 break;
151
152 /* rewrite "Subject: " line */
153 if (!seen_subj && rd >= subjlen) {
154 const char *tag = NULL;
155
156 if (status == RC_SPAM && spam_subject_tag != NULL)
157 tag = spam_subject_tag;
158
159 if (status == RC_UNSURE && unsure_subject_tag != NULL)
160 tag = unsure_subject_tag;
161
162 if (tag != NULL && strncasecmp(out, subjstr, subjlen) == 0) {
163 seen_subj = true;
164 (void) fprintf(fpo, "%.*s %s", subjlen, out, tag);
165 if (out[subjlen] != ' ')
166 fputc(' ', fpo);
167 (void) fwrite(out + subjlen, 1, rd - subjlen, fpo);
168 continue;
169 }
170 }
171
172 hadlf = (out[rd-1] == '\n');
173 (void) fwrite(out, 1, rd, fpo);
174 if (ferror(fpo)) cleanup_exit(EX_ERROR, 1);
175 }
176
177 if (!seen_place_header)
178 write_spam_info();
179
180 if (!hadlf)
181 (void) fputs(eol, fpo);
182
183 return seen_subj;
184 }
185
write_body(readfunc_t rf,void * rfarg)186 static void write_body(readfunc_t rf, void *rfarg)
187 {
188 ssize_t rd;
189 char *out;
190
191 int hadlf = 1;
192 /* If the message terminated early (without body or blank
193 * line between header and body), enforce a blank line to
194 * prevent anything past us from choking. */
195 (void) fputs(eol, fpo);
196
197 /* print body */
198 while ((rd = rf(&out, rfarg)) > 0)
199 {
200 (void) fwrite(out, 1, rd, fpo);
201 hadlf = (out[rd-1] == '\n');
202 if (ferror(fpo)) cleanup_exit(EX_ERROR, 1);
203 }
204
205 if (!hadlf) (void) fputs(eol, fpo);
206 }
207
build_spam_header(void)208 static void build_spam_header(void)
209 {
210 if (passthrough || verbose || terse) {
211 typedef char *formatter(char *buff, size_t size);
212 formatter *fcn = terse ? format_terse : format_header;
213 (*fcn)(msg_spam_header, sizeof(msg_spam_header));
214 }
215 }
216
write_message(rc_t status)217 void write_message(rc_t status)
218 {
219 readfunc_t rf = NULL; /* assignment to quench warning */
220 void *rfarg = 0; /* assignment to quench warning */
221 textdata_t *text;
222 bool seen_subj = false;
223
224 build_spam_header();
225
226 if (!passthrough)
227 {
228 write_spam_info();
229 }
230 else
231 {
232 eol = NULL;
233 /* initialize */
234 rf = read_mem;
235 text = textblock_head();
236 rfarg = &text;
237
238 seen_subj = write_header(status, rf, rfarg);
239
240 if (!seen_subj) {
241 if (status == RC_SPAM && spam_subject_tag != NULL)
242 (void) fprintf(fpo, "Subject: %s%s", spam_subject_tag, eol);
243 if (status == RC_UNSURE && unsure_subject_tag != NULL)
244 (void) fprintf(fpo, "Subject: %s%s", unsure_subject_tag, eol);
245 }
246
247 write_body(rf, rfarg);
248
249 if (verbose || Rtable) {
250 if (fflush(fpo) || ferror(fpo))
251 cleanup_exit(EX_ERROR, 1);
252 }
253 }
254
255 return;
256 }
257
write_log_message(rc_t status)258 void write_log_message(rc_t status)
259 {
260 #ifdef HAVE_SYSLOG_H
261 format_log_header(msg_bogofilter, sizeof(msg_bogofilter));
262
263 switch (run_type) {
264 case RUN_NORMAL:
265 syslog(LOG_INFO, "%s\n", msg_bogofilter);
266 break;
267 case RUN_UPDATE:
268 if (status == RC_UNSURE || msg_register[0] == '\0')
269 syslog(LOG_INFO, "%s\n", msg_bogofilter);
270 else {
271 syslog(LOG_INFO, "%s, %s\n", msg_bogofilter, msg_register);
272 msg_register[0] = '\0';
273 }
274 break;
275 default:
276 syslog(LOG_INFO, "%s", msg_register);
277 msg_register[0] = '\0';
278 }
279
280 closelog();
281 #endif
282 }
283
284 /* we need to take care not to fclose() a shared file descriptor, if,
285 * for instance we've fdopen()ed STDOUT_FILENO, becase we'd prevent
286 * other streams accessing the same file descriptor from flushing their
287 * caches. Prominent symptom: t.bogodir failure.
288 */
289 static bool fpo_mayclose; /* if output_cleanup can close the file */
290
output_setup(void)291 void output_setup(void)
292 {
293 assert(fpo == NULL);
294
295 if (*outfname && passthrough) {
296 fpo = fopen(outfname,"w");
297 fpo_mayclose = true;
298 } else {
299 fpo = fdopen(STDOUT_FILENO, "w");
300 fpo_mayclose = false;
301 }
302
303 if (fpo == NULL) {
304 if (*outfname)
305 fprintf(stderr, "Cannot open %s: %s\n",
306 outfname, strerror(errno));
307 else
308 fprintf(stderr, "Cannot fdopen STDOUT: %s\n", strerror(errno));
309
310 exit(EX_ERROR);
311 }
312
313 /* if we're not in passthrough mode, set line buffered mode just in
314 * case some program that calls uses waits for our output in -T mode */
315 if (!passthrough) {
316 setvbuf(fpo, NULL, _IOLBF, BUFSIZ);
317 }
318 }
319
output_cleanup(void)320 void output_cleanup(void) {
321 int rc;
322
323 rc = fflush(fpo);
324 if (fpo_mayclose)
325 rc |= fclose(fpo);
326 fpo = NULL;
327 if (rc)
328 cleanup_exit(EX_ERROR, 1);
329 }
330
passthrough_setup(void)331 void passthrough_setup(void)
332 {
333 if (!passthrough)
334 return;
335
336 textblock_init();
337 }
338
passthrough_keepopen(void)339 int passthrough_keepopen(void)
340 {
341 return false;
342 }
343
passthrough_cleanup(void)344 void passthrough_cleanup(void)
345 {
346 if (!passthrough)
347 return;
348
349 textblock_free();
350 }
351
352 /* End */
353