/*****************************************************************************
NAME:
bogoconfig.c -- process config file parameters
2003-02-12 - split out from config.c
AUTHOR:
David Relson <relson@osagesoftware.com>
CONTRIBUTORS:
David Saez -O option, helps embedding into Exim.
******************************************************************************
The call tree is (roughly):
bogoconfig.c process_parameters
bogoconfig.c process_arglist(PASS_1_CLI)
bogoconfig.c process_arg(PASS_1_CLI)
configfile.c process_config_files
configfile.c read_config_file
configfile.c process_config_option
configfile.c process_config_option_as_arg
bogoconfig.c process_arg(PASS_2_CFG)
bogoconfig.c process_arglist(PASS_2_CLI)
bogoconfig.c process_arg(PASS_3_CLI)
Note: bogolexer also uses configfile.c.
bogolexer.c calls process_config_files(), which calls back to it.
******************************************************************************/
#include "common.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "bogoconfig.h"
#include "bogofilter.h"
#include "bogoreader.h"
#include "bool.h"
#include "charset.h"
#include "datastore.h"
#include "datastore_db.h"
#include "error.h"
#include "find_home.h"
#include "format.h"
#include "lexer.h"
#include "longoptions.h"
#include "maint.h"
#include "paths.h"
#include "score.h"
#include "wordlists.h"
#include "wordlists_base.h"
#include "xatox.h"
#include "xmalloc.h"
#include "xstrdup.h"
#include "xstrlcpy.h"
#ifndef DEBUG_CONFIG
#define DEBUG_CONFIG(level) (verbose > level)
#endif
/*---------------------------------------------------------------------------*/
/* Global variables */
char outfname[PATH_LEN] = "";
bool run_classify = false;
bool run_register = false;
const char *logtag = NULL;
/* Local variables and declarations */
static int inv_terse_mode = 0;
static void display_tag_array(const char *label, FIELD *array);
static void process_arglist(int argc, char **argv, priority_t precedence, enum arg_pass_e pass);
static bool get_parsed_value(char **arg, double *parm);
static void comma_parse(char opt, const char *arg, double *parm1, double *parm2, double *parm3);
static bool token_count_conflict(void);
/*---------------------------------------------------------------------------*/
static struct option longopts_bogofilter[] = {
/* longoptions.h - common options */
LONGOPTIONS_COMMON
/* longoptions.h - bogofilter */
LONGOPTIONS_MAIN
LONGOPTIONS_MAIN_TUNE
/* longoptions.h - bogofilter/bogolexer options */
LONGOPTIONS_LEX
/* longoptions.h - bogofilter/bogoutil options */
LONGOPTIONS_DB
/* bogofilter specific options */
{ "classify-files", N, 0, 'B' },
{ "syslog-tag", R, 0, 'L' },
{ "classify-mbox", N, 0, 'M' },
{ "unregister-nonspam", N, 0, 'N' },
{ "dataframe", N, 0, 'R' },
{ "unregister-spam", N, 0, 'S' },
{ "fixed-terse-format", N, 0, 'T' },
{ "report-unsure", N, 0, 'U' },
{ "classify-stdin", N, 0, 'b' },
{ "bogofilter-dir", R, 0, 'd' },
{ "nonspam-exits-zero", N, 0, 'e' },
{ "use-syslog", N, 0, 'l' },
{ "register-ham", N, 0, 'n' },
{ "passthrough", N, 0, 'p' },
{ "register-spam", N, 0, 's' },
{ "update-as-scored", N, 0, 'u' },
{ "debug-flags", R, 0, 'x' },
{ "debug-to-stdout", N, 0, 'D' },
{ "no-header-tags", N, 0, 'H' },
{ "query", N, 0, 'Q' },
{ "db-cachesize", R, 0, 'k' },
{ "ns-esf", R, 0, O_NS_ESF },
{ "sp-esf", R, 0, O_SP_ESF },
{ "ham-cutoff", R, 0, O_HAM_CUTOFF },
{ "header-format", R, 0, O_HEADER_FORMAT },
{ "log-header-format", R, 0, O_LOG_HEADER_FORMAT },
{ "log-update-format", R, 0, O_LOG_UPDATE_FORMAT },
{ "min-dev", R, 0, O_MIN_DEV },
{ "robs", R, 0, O_ROBS },
{ "robx", R, 0, O_ROBX },
{ "spam-cutoff", R, 0, O_SPAM_CUTOFF },
{ "spam-header-name", R, 0, O_SPAM_HEADER_NAME },
{ "spam-header-place", R, 0, O_SPAM_HEADER_PLACE },
{ "spam-subject-tag", R, 0, O_SPAM_SUBJECT_TAG },
{ "spamicity-formats", R, 0, O_SPAMICITY_FORMATS },
{ "spamicity-tags", R, 0, O_SPAMICITY_TAGS },
{ "stats-in-header", R, 0, O_STATS_IN_HEADER },
{ "terse", R, 0, O_TERSE },
{ "terse-format", R, 0, O_TERSE_FORMAT },
{ "thresh-update", R, 0, O_THRESH_UPDATE },
{ "timestamp", R, 0, O_TIMESTAMP },
{ "unsure-subject-tag", R, 0, O_UNSURE_SUBJECT_TAG },
{ "wordlist", R, 0, O_WORDLIST },
/* end of list */
{ NULL, 0, 0, 0 }
};
/*---------------------------------------------------------------------------*/
static bool get_bool(const char *name, const char *arg)
{
bool b = str_to_bool(arg);
if (DEBUG_CONFIG(2))
fprintf(dbgout, "%s -> %s\n", name,
b ? "Yes" : "No");
return b;
}
static bool get_double(const char *name, const char *arg, double *d)
{
char *s = xstrdup(arg);
remove_comment(s);
if (!xatof(d, s)) {
xfree(s);
return false;
}
if (DEBUG_CONFIG(2))
fprintf(dbgout, "%s -> %f\n", name, *d);
xfree(s);
return true;
}
static char *get_string(const char *name, const char *arg)
{
char *s = xstrdup(arg);
remove_comment(s);
if (DEBUG_CONFIG(2))
fprintf(dbgout, "%s -> '%s'\n", name, s);
return s;
}
static e_txn get_txn(const char *name, const char *arg)
{
e_txn t = get_bool(name, arg) ? T_ENABLED : T_DISABLED;
if (DEBUG_CONFIG(2))
fprintf(dbgout, "%s -> %s\n", name,
t ? "enabled" : "disabled");
return t;
}
void process_parameters(int argc, char **argv, bool warn_on_error)
{
bogotest = 0;
verbose = 0;
run_type = RUN_UNKNOWN;
fpin = stdin;
set_today(); /* compute current date for token age */
#ifdef __EMX__
_response (&argc, &argv); /* expand response files (@filename) */
_wildcard (&argc, &argv); /* expand wildcards (*.*) */
#endif
process_arglist(argc, argv, PR_COMMAND, PASS_1_CLI);
process_config_files(warn_on_error, longopts_bogofilter);
process_arglist(argc, argv, PR_COMMAND, PASS_3_CLI);
/* directories from command line and config file are already handled */
wordlists_set_bogohome();
stats_prefix= stats_in_header ? " " : "# ";
return;
}
static bool get_parsed_value(char **arg, double *parm)
{
char *str = *arg;
bool ok = true;
if (parm && str && *str) {
if (*str == ',')
str += 1;
else {
ok = xatof(parm, str);
str = strchr(str+1, ',');
if (str)
str += 1;
}
*arg = str;
}
return ok;
}
static void comma_parse(char opt, const char *arg, double *parm1, double *parm2, double *parm3)
{
char *parse = xstrdup(arg);
char *copy = parse;
bool ok = ( get_parsed_value(©, parm1) &&
get_parsed_value(©, parm2) &&
get_parsed_value(©, parm3) );
if (!ok)
fprintf(stderr, "Cannot parse -%c option argument '%s'.\n", opt, arg);
xfree(parse);
}
static run_t check_run_type(run_t add_type, run_t conflict)
{
if (run_type & conflict) {
(void)fprintf(stderr, "Error: Invalid combination of options.\n");
exit(EX_ERROR);
}
return (run_t)(run_type | add_type);
}
static ex_t validate_args(void)
{
/* flags '-s', '-n', '-S', and '-N' are mutually exclusive with
flags '-p', '-u', '-e', and '-R'. */
run_classify = (run_type & (RUN_NORMAL | RUN_UPDATE)) != 0;
run_register = (run_type & (REG_SPAM | REG_GOOD | UNREG_SPAM | UNREG_GOOD)) != 0;
if (*outfname && !passthrough)
{
(void)fprintf(stderr,
"Warning: Option -O %s has no effect without -p\n",
outfname);
}
if (run_register && (run_classify || Rtable))
{
(void)fprintf(stderr,
"Error: Option '-u' may not be used with options '-s', '-n', '-S', or '-N'.\n"
);
return EX_ERROR;
}
return EX_OK;
}
static const char *help_text[] = {
"help options:\n",
" -h, --help - print this help message.\n",
" -V, --version - print version information and exit.\n",
" -Q, --query - query (display) base bogofilter configuration.\n",
" -QQ - display extended configuration info.\n",
"classification options:\n",
" -p, --passthrough - passthrough.\n",
" -e, --ham-true - in -p mode, exit with code 0 when the mail is not spam.\n",
" -u, --update-as-scored - score message as spam or non-spam and register accordingly.\n",
" -M, --classify-mbox - set mailbox mode. Classify multiple messages in an mbox formatted file.\n",
" -b, --classify-stdin - set streaming bulk mode. Process multiple messages (files or directories) read from STDIN.\n",
" -B, --classify-files=list - set bulk mode. Process multiple messages (files or directories) named on the command line.\n",
" -R, --dataframe - print an R data frame.\n",
"registration options:\n",
" -s, --register-spam - register message(s) as spam.\n",
" -n, --register-ham - register message(s) as non-spam.\n",
" -S, --unregister-spam - unregister message(s) from spam list.\n",
" -N, --unregister-nonspam - unregister message(s) from non-spam list.\n",
"general options:\n",
" -c, --config-file=file - read specified config file.\n",
" -C, --no-config-file - don't read standard config files.\n",
" -d, --bogofilter-dir=path - specify directory for wordlists.\n",
" -H, --no-header-tags - disables header line tagging.\n",
" -k, --db-cachesize=size - set Berkeley DB cache size (MB).\n",
" -l, --use-syslog - write messages to syslog.\n",
" -L, --syslog-tag=tag - specify the tag value for log messages.\n",
" -I, --input-file=file - read message from 'file' instead of stdin.\n",
" -O, --output-file=file - save message to 'file' in passthrough mode.\n",
"parameter options:\n",
" -mv1[,v2[,v3]] - set user defined min_dev, robs, and robx values.\n",
" --min-dev=v1, --robs=v2, --robx=v3\n",
" -ov1[,v2] - set user defined spam and non-spam cutoff values.\n",
" --spam-cutoff=v1, --ham-cutoff=v2\n",
"info options:\n",
" -t, --terse - set terse output mode.\n",
" -T, --fixed-terse-format - set invariant terse output mode.\n",
" -q, --quiet - suppress token statistics.\n",
" -U, --report-unsure - print statistics if spamicity is 'unsure'.\n",
" -v, --verbosity - set debug verbosity level.\n",
" -y, --timestamp-date - set date for token timestamps.\n",
" -D, --debug-to-stdout - direct debug output to stdout.\n",
" -x, --debug-flags=list - set flags to display debug information.\n",
"config file options:\n",
" --option=value - can be used to set the value of a config file option.\n",
" see bogofilter.cf.example for more info.\n",
" --block-on-subnets return class addr tokens\n",
" --bogofilter-dir directory for wordlists\n",
" --charset-default default character set\n",
" --db-cachesize Berkeley db cache in Mb\n",
#ifdef HAVE_DECL_DB_CREATE
" --db-log-autoremove enable/disable autoremoval of log files\n",
" --db-transaction enable/disable transactions\n",
#ifdef FUTURE_DB_OPTIONS
" --db-txn-durable \n",
#endif
#endif
" --ham-cutoff nonspam if score below this\n",
" --header-format spam header format\n",
" --log-header-format header written to log\n",
" --log-update-format logged on update\n",
" --min-dev ignore if score near\n",
" --min-token-len min len for single tokens\n",
" --max-token-len max len for single tokens\n",
" --max-multi-token-len max len for multi-word tokens\n",
" --multi-token-count number of tokens per multi-word token\n",
" --ns-esf effective size factor for ham\n",
" --replace-nonascii-characters substitute '?' if bit 8 is 1\n",
" --robs Robinson's s parameter\n",
" --robx Robinson's x parameter\n",
" --sp-esf effective size factor for spam\n",
" --spam-cutoff spam if score above this\n",
" --spam-header-name passthrough adds/replaces\n",
" --spam-subject-tag passthrough prepends Subject\n",
" --spamicity-formats spamicity output format\n",
" --spamicity-tags spamicity tag format\n",
" --stats-in-header use header not body\n",
" --terse report in short form\n",
" --terse-format short form\n",
" --thresh-update no update if near 0 or 1\n",
" --timestamp enable/disable token timestamps\n",
" --token-count fixed token count for scoring\n",
" --token-count-min min token count for scoring\n",
" --token-count-max max token count for scoring\n",
#ifndef DISABLE_UNICODE
" --unicode enable/disable unicode based wordlist\n",
#endif
" --unsure-subject-tag like spam-subject-tag\n",
" --user-config-file configuration file\n",
" --wordlist specify wordlist parameters\n",
"\n",
"bogofilter is a tool for classifying email as spam or non-spam.\n",
"\n",
"For updates and additional information, see\n",
"URL: http://bogofilter.sourceforge.net\n",
NULL
};
static void help(FILE *fp)
{
uint i;
(void)fprintf(fp,
"%s version %s\n"
"\n"
"Usage: %s [options] < message\n"
"\n",
progtype, version, PACKAGE
);
for (i=0; help_text[i] != NULL; i++)
(void)fprintf(fp, "%s", help_text[i]);
}
static void print_version(void)
{
(void)fprintf(stdout,
"%s version %s\n"
" Database: %s\n"
"Copyright (C) 2002-2013 David Relson\n"
"Copyright (C) 2002-2019 Matthias Andree\n"
"Copyright (C) 2002-2004 Greg Louis\n"
"Copyright (C) 2002-2003 Eric S. Raymond, Adrian Otto, Gyepi Sam\n\n"
"%s comes with ABSOLUTELY NO WARRANTY. "
"This is free software, and\nyou are welcome to "
"redistribute it under the General Public License. "
"See\nthe COPYING file with the source distribution for "
"details.\n"
"\n",
progtype, version, ds_version_str(), PACKAGE);
}
#define OPTIONS ":-:bBc:Cd:DehHI:k:lL:m:MnNo:O:pPqQRsStTuUvVx:X:y:"
/** These functions process command line arguments.
**
** They are called to perform passes 1 & 2 of command line switch processing.
** The config file is read in between the two function calls.
**
** The functions will exit if there's an error, for example if
** there are leftover command line arguments.
*/
static void process_arglist(int argc, char **argv, priority_t precedence, enum arg_pass_e pass)
{
ex_t exitcode;
if (pass != PASS_1_CLI) {
optind = opterr = 1;
#ifdef __EMX__
optind = 0;
#endif
/* don't use #ifdef here: */
#if HAVE_DECL_OPTRESET
optreset = 1;
#endif
}
while (1)
{
int option;
int option_index = 0;
int this_option_optind = optind ? optind : 1;
const char *name;
option = getopt_long_chk(argc, argv, OPTIONS,
longopts_bogofilter, &option_index);
if (option == -1)
break;
name = (option_index == 0) ? argv[this_option_optind] : longopts_bogofilter[option_index].name;
#ifdef EXCESSIVE_DEBUG
if (getenv("BOGOFILTER_DEBUG_OPTIONS")) {
fprintf(stderr, "config: option=%c (%d), optind=%d, opterr=%d, optarg=%s\n",
isprint((unsigned char)option) ? option : '_', option,
optind, opterr, optarg ? optarg : "(null)");
}
#endif
process_arg(option, name, optarg, precedence, pass);
}
if (pass == PASS_1_CLI) {
if (run_type == RUN_UNKNOWN)
run_type = RUN_NORMAL;
}
if (pass == PASS_3_CLI) {
exitcode = validate_args();
if (exitcode)
exit(exitcode);
if (bulk_mode == B_NORMAL && optind < argc) {
fprintf(stderr, "Extra arguments given, first: %s. Aborting.\n", argv[optind]);
exit(EX_ERROR);
}
if (inv_terse_mode) {
verbose = max(1, verbose); /* force printing */
set_terse_mode_format(inv_terse_mode);
}
if (token_count_conflict()) {
fprintf(stderr, "Conflicting token count arguments given.\n");
exit(EX_ERROR);
}
}
return;
}
int process_arg(int option, const char *name, const char *val, priority_t precedence, arg_pass_t pass)
{
switch (option)
{
case '?':
if (pass == PASS_1_CLI)
fprintf(stderr, "Unknown option '%s'.\n", name);
break;
case 'b':
bulk_mode = B_STDIN;
fpin = NULL; /* Ensure that input file isn't stdin */
break;
case 'B':
bulk_mode = B_CMDLINE;
break;
case 'c':
case O_CONFIG_FILE:
if (pass == PASS_1_CLI) {
if (!read_config_file(val, false, !quiet, PR_CFG_USER, longopts_bogofilter)) {
fprintf(stderr, "Cannot open %s: %s\n", val, strerror(errno));
exit(EX_ERROR);
}
}
/*@fallthrough@*/
/* fall through to suppress reading config files */
case 'C':
if (pass == PASS_1_CLI)
suppress_config_file = true;
break;
case O_USER_CONFIG_FILE:
user_config_file = get_string(name, val);
break;
case 'D':
dbgout = stdout;
break;
case 'e':
case O_HAM_TRUE:
nonspam_exits_zero = true;
break;
case 'h':
help(stdout);
exit(EX_OK);
case 'I':
if (pass == PASS_1_CLI)
bogoreader_name(val);
break;
case 'L':
logtag = xstrdup(val);
/*@fallthrough@*/
case 'l':
logflag = true;
break;
case 'M':
mbox_mode = true;
break;
case 'n':
run_type = check_run_type(REG_GOOD, (run_t)(REG_SPAM | UNREG_GOOD));
break;
case 'N':
run_type = check_run_type(UNREG_GOOD, (run_t)(REG_GOOD | UNREG_SPAM));
break;
case 'O':
if (pass == PASS_1_CLI)
xstrlcpy(outfname, val, sizeof(outfname));
break;
case 'p':
passthrough = true;
break;
case 'q':
quiet = true;
break;
case 'Q':
if (pass == PASS_1_CLI)
query += 1;
break;
case 'R':
Rtable = true;
break;
case 's':
run_type = check_run_type(REG_SPAM, (run_t)(REG_GOOD | UNREG_SPAM));
break;
case 'S':
run_type = check_run_type(UNREG_SPAM, (run_t)(REG_SPAM | UNREG_GOOD));
break;
case 'u':
run_type = (run_t)(run_type | RUN_UPDATE);
break;
case 'U':
unsure_stats = (val == NULL) ? true : get_bool(name, val);
break;
case 'v':
if (pass == PASS_1_CLI)
verbose++;
break;
case 'x':
set_debug_mask( val );
break;
case 'X':
set_bogotest( val );
break;
case 'y': /* date as YYYYMMDD */
{
const char *str = val ? val : optarg;
YYYYMMDD date = string_to_date(str);
if (date != 0 && date < 19990000) {
fprintf(stderr, "Date format for '-y' option is YYYYMMDD\n");
exit(EX_ERROR);
}
set_date( date );
break;
}
case ':':
fprintf(stderr, "Option %s requires an argument.\n", name);
exit(EX_ERROR);
case '-':
if (pass == PASS_3_CLI)
process_config_option(val, true, precedence, longopts_bogofilter);
break;
case 'd':
if (pass != PASS_1_CLI)
set_wordlist_dir(val, precedence);
break;
case O_NS_ESF:
if (pass != PASS_1_CLI)
get_double(name, val, &ns_esf);
break;
case O_SP_ESF:
if (pass != PASS_1_CLI)
get_double(name, val, &sp_esf);
break;
case 'H':
header_line_markup = (val == NULL) ? false : get_bool(name, val);
break;
case 'k':
db_cachesize=atoi(val);
break;
case 'm':
if (pass != PASS_1_CLI) {
comma_parse(option, val, &min_dev, &robs, &robx);
if (DEBUG_CONFIG(1))
fprintf(dbgout, "md %6.4f, rs %6.4f, rx %6.4f\n", min_dev, robs, robx);
}
break;
case O_MIN_DEV:
if (pass != PASS_1_CLI)
get_double(name, val, &min_dev);
break;
case O_ROBS:
if (pass != PASS_1_CLI)
get_double(name, val, &robs);
break;
case O_ROBX:
if (pass != PASS_1_CLI)
get_double(name, val, &robx);
break;
case 'o':
if (pass != PASS_1_CLI) {
comma_parse(option, val, &spam_cutoff, &ham_cutoff, NULL);
if (DEBUG_CONFIG(1))
fprintf(dbgout, "sc %6.4f, hc %6.4f\n", spam_cutoff, ham_cutoff);
}
break;
case O_SPAM_CUTOFF:
if (pass != PASS_1_CLI)
get_double(name, val, &spam_cutoff);
break;
case O_HAM_CUTOFF:
if (pass != PASS_1_CLI)
get_double(name, val, &ham_cutoff);
break;
case 't':
terse = true;
break;
case 'T': /* invariant terse mode */
terse = true;
if (pass == PASS_1_CLI)
inv_terse_mode += 1;
break;
case 'V':
print_version();
exit(EX_OK);
case O_BLOCK_ON_SUBNETS: block_on_subnets = get_bool(name, val); break;
case O_CHARSET_DEFAULT: xfree(charset_default); charset_default = get_string(name, val); break;
case O_HEADER_FORMAT: xfree(header_format); header_format = get_string(name, val); break;
case O_LOG_HEADER_FORMAT: xfree(log_header_format); log_header_format = get_string(name, val); break;
case O_LOG_UPDATE_FORMAT: xfree(log_update_format); log_update_format = get_string(name, val); break;
case O_MAX_TOKEN_LEN: max_token_len=atoi(val); break;
case O_MIN_TOKEN_LEN: min_token_len=atoi(val); break;
case O_MAX_MULTI_TOKEN_LEN: max_multi_token_len=atoi(val); break;
case O_MULTI_TOKEN_COUNT: multi_token_count=atoi(val); break;
case O_REPLACE_NONASCII_CHARACTERS: replace_nonascii_characters = get_bool(name, val); break;
case O_SPAMICITY_FORMATS: set_spamicity_formats(val); break;
case O_SPAMICITY_TAGS: set_spamicity_tags(val); break;
case O_SPAM_HEADER_NAME: xfree(spam_header_name); spam_header_name = get_string(name, val); break;
case O_SPAM_HEADER_PLACE: xfree(spam_header_place); spam_header_place = get_string(name, val); break;
case O_SPAM_SUBJECT_TAG: xfree(spam_subject_tag); spam_subject_tag = get_string(name, val); break;
case O_STATS_IN_HEADER: stats_in_header = get_bool(name, val); break;
case O_TERSE: terse = get_bool(name, val); break;
case O_TERSE_FORMAT: xfree(terse_format); terse_format = get_string(name, val); break;
case O_THRESH_UPDATE: get_double(name, val, &thresh_update); break;
case O_TIMESTAMP: timestamp_tokens = get_bool(name, val); break;
case O_TOKEN_COUNT_FIX: token_count_fix = atoi(val); break;
case O_TOKEN_COUNT_MIN: token_count_min = atoi(val); break;
case O_TOKEN_COUNT_MAX: token_count_max = atoi(val); break;
case O_UNSURE_SUBJECT_TAG: xfree(unsure_subject_tag); unsure_subject_tag = get_string(name, val); break;
case O_UNICODE: encoding = get_bool(name, val) ? E_UNICODE : E_RAW; break;
case O_WORDLIST: configure_wordlist(val); break;
case O_DB_TRANSACTION: eTransaction = get_txn(name, val); break;
default:
#ifndef DISABLE_TRANSACTIONS
if (!dsm_options_bogofilter(option, name, val))
#endif
{
fprintf(stderr, "Invalid option '%s'.\n", name);
exit(EX_ERROR);
}
}
return 0;
}
#define Q1 if (query >= 1)
#define Q2 if (query >= 2)
#define Q3 if (query >= 3)
#define YN(b) (b ? "Yes" : "No")
#define NB(b) ((b != NULL && *b != '\0') ? b : "''")
rc_t query_config(void)
{
Q1 fprintf(stdout, "# %s version %s\n", progname, version);
Q1 fprintf(stdout, "\n");
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "robx", robx, robx);
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "robs", robs, robs);
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "min_dev", min_dev, min_dev);
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "ham_cutoff", ham_cutoff, ham_cutoff);
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "spam_cutoff", spam_cutoff, spam_cutoff);
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "ns_esf", ns_esf, ns_esf);
Q1 fprintf(stdout, "%-11s = %0.6f # (%8.2e)\n", "sp_esf", sp_esf, sp_esf);
Q1 fprintf(stdout, "\n");
Q3 fprintf(stdout, "%-17s = %d\n", "token-count", token_count_fix);
Q3 fprintf(stdout, "%-17s = %d\n", "token-count-min", token_count_min);
Q3 fprintf(stdout, "%-17s = %d\n", "token-count-max", token_count_max);
Q3 fprintf(stdout, "\n");
Q1 fprintf(stdout, "%-17s = %s\n", "block-on-subnets", YN(block_on_subnets));
Q1 fprintf(stdout, "%-17s = %s\n", "encoding", (encoding != E_UNICODE) ? "raw" : "utf-8");
Q1 fprintf(stdout, "%-17s = %s\n", "charset-default", charset_default);
Q1 fprintf(stdout, "%-17s = %s\n", "replace-nonascii-characters", YN(replace_nonascii_characters));
Q2 fprintf(stdout, "%-17s = %s\n", "no-header-tags", YN(header_line_markup));
Q1 fprintf(stdout, "%-17s = %s\n", "stats-in-header", YN(stats_in_header));
Q2 fprintf(stdout, "%-17s = %s\n", "report-unsure", YN(unsure_stats));
Q1 fprintf(stdout, "%-17s = %0.6f\n", "thresh-update", thresh_update);
Q1 fprintf(stdout, "%-17s = %s\n", "timestamp", YN(timestamp_tokens));
Q2 fprintf(stdout, "%-17s = %ld\n", "timestamp-date", (long int)today);
Q1 fprintf(stdout, "\n");
Q1 fprintf(stdout, "%-17s = %s\n", "spam-header-name", spam_header_name);
Q1 fprintf(stdout, "%-17s = %s\n", "spam-header-place", NB(spam_header_place));
Q1 fprintf(stdout, "%-17s = %s\n", "spam-subject-tag", NB(spam_subject_tag));
Q1 fprintf(stdout, "%-17s = %s\n", "unsure-subject-tag", NB(unsure_subject_tag));
Q2 fprintf(stdout, "%-18s = %s\n", "syslog-tag", NB(logtag));
Q1 fprintf(stdout, "%-17s = %s\n", "header-format", header_format);
Q1 fprintf(stdout, "%-17s = %s\n", "terse-format", terse_format);
Q1 fprintf(stdout, "%-17s = %s\n", "log-header-format", log_header_format);
Q1 fprintf(stdout, "%-17s = %s\n", "log-update-format", log_update_format);
Q1 display_tag_array("spamicity-tags ", spamicity_tags);
Q1 display_tag_array("spamicity-formats", spamicity_formats);
Q2 fprintf(stdout, "\n");
Q2 fprintf(stdout, "%-18s = %s\n", "no-config-file", YN(suppress_config_file));
Q2 fprintf(stdout, "%-18s = %s\n", "config-file", NB(config_file_name));
Q2 fprintf(stdout, "%-18s = %s\n", "user-config-file", NB(user_config_file));
Q2 fprintf(stdout, "\n");
Q2 fprintf(stdout, "%-18s = %s\n", "bogofilter-dir", bogohome);
Q2 display_wordlists(word_lists, "%-18s ");
Q2 fprintf(stdout, "\n");
#ifndef DISABLE_TRANSACTIONS
Q2 fprintf(stdout, "%-18s = %lu\n", "db-cachesize", (unsigned long)db_cachesize);
#ifdef ENABLE_TRANSACTIONS
#ifdef HAVE_DECL_DB_CREATE
Q2 fprintf(stdout, "%-18s = %s\n", "db-log-autoremove", YN(db_log_autoremove));
#ifdef FUTURE_DB_OPTIONS
Q2 fprintf(stdout, "%-18s = %s\n", "db-log-txn-durable", YN(db_txn_durable));
#endif
#endif
#endif
#endif
return RC_OK;
}
static void display_tag_array(const char *label, FIELD *array)
{
int i;
int count = (ham_cutoff < EPS) ? 2 : 3;
fprintf(stdout, "%s =", label);
for (i = 0; i < count; i += 1)
fprintf(stdout, "%s %s", (i == 0) ? "" : ",", array[i]);
fprintf(stdout, "\n");
}
static bool token_count_conflict(void)
{
if (token_count_fix != 0) {
if (token_count_fix < token_count_min)
return true;
}
if (token_count_max != 0) {
if (token_count_max < token_count_min)
return true;
if (token_count_max < token_count_fix)
return true;
}
return false;
}