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