/*****************************************************************************
NAME:
passthrough.c -- handle writing out message in passthrough ('-p') mode.
******************************************************************************/
#include "common.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#include "passthrough.h"
#include "bogofilter.h"
#include "bogoreader.h" /* for is_eol() */
#include "fgetsl.h"
#include "format.h"
#include "textblock.h"
#include "xmalloc.h"
#include "globals.h"
#include "lexer.h" /* need have_body */
static const char *eol;
char msg_register[256];
static char msg_bogofilter[256];
static char msg_spam_header[256];
size_t msg_register_size = sizeof(msg_register);
/* Function Definitions */
static void cleanup_exit(ex_t exitcode, int killfiles)
__attribute__ ((noreturn));
static void cleanup_exit(ex_t exitcode, int killfiles) {
if (fpo) {
output_cleanup();
}
if (killfiles && outfname[0] != '\0') unlink(outfname);
exit(exitcode);
}
#define ISSPACE(ch) (isspace(ch) || (ch == '\b'))
/* check for non-empty blank line */
static bool is_blank_line(const char *line, size_t len)
{
if (have_body)
return len == 0;
while (len-- > 0) {
byte ch = *line++;
if (!ISSPACE(ch))
return false;
}
return true;
}
/** check if the line we're looking at is a header/body delimiter */
static bool is_hb_delim(const char *line, size_t len, bool strict_body)
{
if (strict_body)
return len == 0;
return is_blank_line(line, len);
}
static int read_mem(char **out, void *in) {
textdata_t **text = (textdata_t **)in;
if ((*text)->next) {
int s = (*text)->size;
*out = (char *)(*text)->data;
*text = (*text)->next;
return s;
}
return 0;
}
typedef int (*readfunc_t)(char **, void *);
static void write_spam_info(void)
{
if (eol == NULL) /* special treatment of empty input */
eol = NL;
/* print spam-status at the end of the header
* then mark the beginning of the message body */
if (passthrough || verbose || terse)
fprintf(fpo, "%s%s", msg_spam_header, eol);
if (passthrough || verbose || Rtable) {
verbose += passthrough;
print_stats( fpo );
verbose -= passthrough;
}
}
static bool write_header(rc_t status, readfunc_t rf, void *rfarg)
{
ssize_t rd;
char *out;
bool hadlf = true;
bool seen_subj = false;
bool seen_place_header = false;
int bogolen = strlen(spam_header_name);
const char *subjstr = "Subject:";
int subjlen = strlen(subjstr);
eol = NULL;
/* print headers */
while ((rd = rf(&out, rfarg)) > 0)
{
if (eol == NULL) {
if (memcmp(out+rd-1, NL, 1) == 0)
eol = NL;
if (rd >=2 && memcmp(out+rd-2, CRLF, 2) == 0)
eol = CRLF;
}
/* skip over spam_header_name ("X-Bogosity:") lines */
while (rd >= bogolen &&
memcmp(out, spam_header_name, bogolen) == 0) {
while (((rd = rf(&out, rfarg)) > 0) &&
(out[0] == ' ' || out[0] == '\t') )
/* empty loop */ ;
}
/* detect spam_header_place line */
if ( !seen_place_header &&
*spam_header_place != '\0' &&
memcmp(out, spam_header_place, strlen(spam_header_place)) == 0)
{
seen_place_header = true;
write_spam_info();
}
/* detect end of headers */
if (is_eol(out, rd) ||
is_hb_delim(out, rd, have_body))
/* check for non-empty blank line */
break;
/* rewrite "Subject: " line */
if (!seen_subj && rd >= subjlen) {
const char *tag = NULL;
if (status == RC_SPAM && spam_subject_tag != NULL)
tag = spam_subject_tag;
if (status == RC_UNSURE && unsure_subject_tag != NULL)
tag = unsure_subject_tag;
if (tag != NULL && strncasecmp(out, subjstr, subjlen) == 0) {
seen_subj = true;
(void) fprintf(fpo, "%.*s %s", subjlen, out, tag);
if (out[subjlen] != ' ')
fputc(' ', fpo);
(void) fwrite(out + subjlen, 1, rd - subjlen, fpo);
continue;
}
}
hadlf = (out[rd-1] == '\n');
(void) fwrite(out, 1, rd, fpo);
if (ferror(fpo)) cleanup_exit(EX_ERROR, 1);
}
if (!seen_place_header)
write_spam_info();
if (!hadlf)
(void) fputs(eol, fpo);
return seen_subj;
}
static void write_body(readfunc_t rf, void *rfarg)
{
ssize_t rd;
char *out;
int hadlf = 1;
/* If the message terminated early (without body or blank
* line between header and body), enforce a blank line to
* prevent anything past us from choking. */
(void) fputs(eol, fpo);
/* print body */
while ((rd = rf(&out, rfarg)) > 0)
{
(void) fwrite(out, 1, rd, fpo);
hadlf = (out[rd-1] == '\n');
if (ferror(fpo)) cleanup_exit(EX_ERROR, 1);
}
if (!hadlf) (void) fputs(eol, fpo);
}
static void build_spam_header(void)
{
if (passthrough || verbose || terse) {
typedef char *formatter(char *buff, size_t size);
formatter *fcn = terse ? format_terse : format_header;
(*fcn)(msg_spam_header, sizeof(msg_spam_header));
}
}
void write_message(rc_t status)
{
readfunc_t rf = NULL; /* assignment to quench warning */
void *rfarg = 0; /* assignment to quench warning */
textdata_t *text;
bool seen_subj = false;
build_spam_header();
if (!passthrough)
{
write_spam_info();
}
else
{
eol = NULL;
/* initialize */
rf = read_mem;
text = textblock_head();
rfarg = &text;
seen_subj = write_header(status, rf, rfarg);
if (!seen_subj) {
if (status == RC_SPAM && spam_subject_tag != NULL)
(void) fprintf(fpo, "Subject: %s%s", spam_subject_tag, eol);
if (status == RC_UNSURE && unsure_subject_tag != NULL)
(void) fprintf(fpo, "Subject: %s%s", unsure_subject_tag, eol);
}
write_body(rf, rfarg);
if (verbose || Rtable) {
if (fflush(fpo) || ferror(fpo))
cleanup_exit(EX_ERROR, 1);
}
}
return;
}
void write_log_message(rc_t status)
{
#ifdef HAVE_SYSLOG_H
format_log_header(msg_bogofilter, sizeof(msg_bogofilter));
switch (run_type) {
case RUN_NORMAL:
syslog(LOG_INFO, "%s\n", msg_bogofilter);
break;
case RUN_UPDATE:
if (status == RC_UNSURE || msg_register[0] == '\0')
syslog(LOG_INFO, "%s\n", msg_bogofilter);
else {
syslog(LOG_INFO, "%s, %s\n", msg_bogofilter, msg_register);
msg_register[0] = '\0';
}
break;
default:
syslog(LOG_INFO, "%s", msg_register);
msg_register[0] = '\0';
}
closelog();
#endif
}
/* we need to take care not to fclose() a shared file descriptor, if,
* for instance we've fdopen()ed STDOUT_FILENO, becase we'd prevent
* other streams accessing the same file descriptor from flushing their
* caches. Prominent symptom: t.bogodir failure.
*/
static bool fpo_mayclose; /* if output_cleanup can close the file */
void output_setup(void)
{
assert(fpo == NULL);
if (*outfname && passthrough) {
fpo = fopen(outfname,"w");
fpo_mayclose = true;
} else {
fpo = fdopen(STDOUT_FILENO, "w");
fpo_mayclose = false;
}
if (fpo == NULL) {
if (*outfname)
fprintf(stderr, "Cannot open %s: %s\n",
outfname, strerror(errno));
else
fprintf(stderr, "Cannot fdopen STDOUT: %s\n", strerror(errno));
exit(EX_ERROR);
}
/* if we're not in passthrough mode, set line buffered mode just in
* case some program that calls uses waits for our output in -T mode */
if (!passthrough) {
setvbuf(fpo, NULL, _IOLBF, BUFSIZ);
}
}
void output_cleanup(void) {
int rc;
rc = fflush(fpo);
if (fpo_mayclose)
rc |= fclose(fpo);
fpo = NULL;
if (rc)
cleanup_exit(EX_ERROR, 1);
}
void passthrough_setup(void)
{
if (!passthrough)
return;
textblock_init();
}
int passthrough_keepopen(void)
{
return false;
}
void passthrough_cleanup(void)
{
if (!passthrough)
return;
textblock_free();
}
/* End */