/* $Id: dspam_stats.c,v 1.36 2011/06/28 00:13:48 sbajic Exp $ */
/*
DSPAM
COPYRIGHT (C) 2002-2012 DSPAM PROJECT
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef _WIN32
#include
#include
#endif
#include "config.h"
#include "libdspam.h"
#include "read_config.h"
#include "config_api.h"
#include "language.h"
#include "util.h"
#define TSYNTAX "syntax: dspam_stats [-h]\|[--profile=PROFILE] [-HrsSt] [user [user...]]"
#ifdef _WIN32
/* no trusted users under Windows */
#undef TRUSTED_USER_SECURITY
#endif
DSPAM_CTX *open_ctx, *open_mtx;
int opt_humanfriendly;
int opt_reset;
int opt_snapshot;
int opt_stats;
int opt_total;
int stat_user (const char *username, struct _ds_spam_totals *totals);
int process_all_users (struct _ds_spam_totals *totals);
void dieout (int signal);
void usage (void);
int
main (int argc, char **argv)
{
int ch, i, users = 0;
#ifndef HAVE_GETOPT
int optind = 1;
#endif
struct _ds_spam_totals totals;
#ifdef TRUSTED_USER_SECURITY
struct passwd *p = getpwuid (getuid ());
int trusted = 0;
#endif
memset(&totals, 0, sizeof(struct _ds_spam_totals));
/* Read dspam.conf */
agent_config = read_config(NULL);
if (!agent_config) {
LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
fprintf (stderr, ERR_AGENT_READ_CONFIG "\n");
exit(EXIT_FAILURE);
}
if (!_ds_read_attribute(agent_config, "Home")) {
LOG(LOG_ERR, ERR_AGENT_DSPAM_HOME);
fprintf (stderr, ERR_AGENT_DSPAM_HOME "\n");
_ds_destroy_config(agent_config);
exit(EXIT_FAILURE);
}
if (libdspam_init(_ds_read_attribute(agent_config, "StorageDriver")) != 0) {
LOG(LOG_ERR, ERR_DRV_INIT);
fprintf (stderr, ERR_DRV_INIT "\n");
_ds_destroy_config(agent_config);
exit(EXIT_FAILURE);
}
#ifdef TRUSTED_USER_SECURITY
if (_ds_match_attribute(agent_config, "Trust", p->pw_name) || !p->pw_uid) {
trusted = 1;
}
#endif
for(i=0;ipw_uid, p->pw_name);
fprintf (stderr, ERR_TRUSTED_PRIV "\n", "--profile", p->pw_uid, p->pw_name);
_ds_destroy_config(agent_config);
goto BAIL;
}
#endif
if (!_ds_match_attribute(agent_config, "Profile", argv[i]+10)) {
LOG(LOG_ERR, ERR_AGENT_NO_SUCH_PROFILE, argv[i]+10);
fprintf (stderr, ERR_AGENT_NO_SUCH_PROFILE "\n", argv[i]+10);
_ds_destroy_config(agent_config);
goto BAIL;
} else {
_ds_overwrite_attribute(agent_config, "DefaultProfile", argv[i]+10);
}
break;
}
}
open_ctx = open_mtx = NULL;
signal (SIGINT, dieout);
#ifndef _WIN32
signal (SIGPIPE, dieout);
#endif
signal (SIGTERM, dieout);
dspam_init_driver (NULL);
/* Process command line */
ch = opt_humanfriendly = 0;
opt_reset = opt_snapshot = opt_stats = opt_total = 0;
#ifndef HAVE_GETOPT
while ( argv[optind] &&
argv[optind][0] == '-' &&
(ch = argv[optind][1]) &&
argv[optind][2] == '\0' )
#else
while((ch = getopt(argc, argv, "hHrsS")) != -1)
#endif
{
switch(ch) {
case 'h':
/* print help, and then exit. usage exits for us */
usage();
break;
case 'H':
opt_humanfriendly = 1;
break;
case 'r':
opt_reset = 1;
break;
case 's':
opt_snapshot = 1;
break;
case 'S':
opt_stats = 1;
break;
case 't':
opt_total = 1;
break;
#ifndef HAVE_GETOPT
default:
fprintf(stderr, "%s: unknown option \"%s\".\n",
argv[0], argv[optind] + 1);
usage();
#endif
}
#ifndef HAVE_GETOPT
optind++;
#endif
}
#ifndef HAVE_GETOPT
/* reset our option array and index to where we are after getopt */
argv += optind;
argc -= optind;
#endif
/* process arguments */
for (i=0; i < argc; i++)
{
if (argv[i] && strncmp(argv[i], "--", 2)) {
#ifdef TRUSTED_USER_SECURITY
if ( !trusted && strcmp(argv[i], p->pw_name) )
{
fprintf(stderr, ERR_TRUSTED_MODE "\n");
_ds_destroy_config(agent_config);
goto BAIL;
}
#endif
stat_user(argv[i], &totals);
users++;
}
}
if (!users)
{
#ifdef TRUSTED_USER_SECURITY
if ( !trusted )
{
fprintf(stderr, ERR_TRUSTED_MODE "\n");
_ds_destroy_config(agent_config);
goto BAIL;
}
#endif
process_all_users (&totals);
}
if (opt_total)
stat_user(NULL, &totals);
dspam_shutdown_driver (NULL);
_ds_destroy_config(agent_config);
libdspam_shutdown();
exit (EXIT_SUCCESS);
BAIL:
libdspam_shutdown();
exit(EXIT_FAILURE);
}
int
process_all_users (struct _ds_spam_totals *totals)
{
DSPAM_CTX *CTX;
char *user;
CTX = dspam_create (NULL, NULL, _ds_read_attribute(agent_config, "Home"), DSM_TOOLS, 0);
open_ctx = CTX;
if (CTX == NULL)
{
fprintf (stderr, "Could not initialize context: %s\n", strerror (errno));
return EFAILURE;
}
set_libdspam_attributes(CTX);
if (dspam_attach(CTX, NULL)) {
LOG (LOG_WARNING, "unable to attach dspam context");
fprintf (stderr, "Unable to attach DSPAM context\n");
dspam_destroy(CTX);
return EFAILURE;
}
user = _ds_get_nextuser (CTX);
while (user != NULL)
{
stat_user (user, totals);
user = _ds_get_nextuser (CTX);
}
dspam_destroy (CTX);
open_ctx = NULL;
return 0;
}
int
stat_user (const char *username, struct _ds_spam_totals *totals)
{
DSPAM_CTX *MTX = NULL;
long total_spam, total_innocent, spam_misclassified, innocent_misclassified, spam_corpusfed, innocent_corpusfed, all_spam, all_innocent;
char filename[MAX_FILENAME_LENGTH];
FILE *file;
struct _ds_spam_totals *tptr;
if (username) {
MTX = dspam_create (username, NULL, _ds_read_attribute(agent_config, "Home"), DSM_CLASSIFY, 0);
open_mtx = MTX;
if (MTX == NULL)
{
fprintf (stderr, "Could not init context: %s\n", strerror (errno));
return EUNKNOWN;
}
set_libdspam_attributes(MTX);
if (dspam_attach(MTX, NULL)) {
LOG (LOG_WARNING, "unable to attach dspam context");
fprintf (stderr, "Unable to attach DSPAM context\n");
return EUNKNOWN;
}
tptr = &MTX->totals;
} else {
tptr = totals;
}
/* Convenience variables. Compiling with optimization will cause this to
have 0 slowdown, as it is essentially dead code */
total_spam =
MAX(0, (tptr->spam_learned + tptr->spam_classified) -
(tptr->spam_misclassified + tptr->spam_corpusfed));
total_innocent =
MAX(0, (tptr->innocent_learned + tptr->innocent_classified) -
(tptr->innocent_misclassified + tptr->innocent_corpusfed));
spam_misclassified = tptr->spam_misclassified;
innocent_misclassified = tptr->innocent_misclassified;
spam_corpusfed = tptr->spam_corpusfed;
innocent_corpusfed = tptr->innocent_corpusfed;
if (MTX) {
totals->spam_learned += MTX->totals.spam_learned;
totals->innocent_learned += MTX->totals.innocent_learned;
totals->spam_misclassified += MTX->totals.spam_misclassified;
totals->innocent_misclassified += MTX->totals.innocent_misclassified;
totals->spam_corpusfed += MTX->totals.spam_corpusfed;
totals->innocent_corpusfed += MTX->totals.innocent_corpusfed;
}
/* Subtract the snapshot from the current totals to get stats "since last
reset" for the user */
if (opt_snapshot && username) {
long s_total_spam, s_total_innocent, s_spam_misclassified,
s_innocent_misclassified, s_spam_corpusfed, s_innocent_corpusfed;
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
username, "rstats");
_ds_prepare_path_for (filename);
file = fopen (filename, "r");
if (file != NULL) {
if (fscanf(file, "%ld,%ld,%ld,%ld,%ld,%ld",
&s_total_spam,
&s_total_innocent,
&s_spam_misclassified,
&s_innocent_misclassified,
&s_spam_corpusfed,
&s_innocent_corpusfed)==6) {
total_spam -= s_total_spam;
total_innocent -= s_total_innocent;
spam_misclassified -= s_spam_misclassified;
innocent_misclassified -= s_innocent_misclassified;
spam_corpusfed -= s_spam_corpusfed;
innocent_corpusfed -= s_innocent_corpusfed;
}
fclose(file);
}
}
all_spam = total_spam + spam_misclassified,
all_innocent = total_innocent + innocent_misclassified;
if (opt_humanfriendly)
{
printf("%s:\n\
\tTP True Positives: %6ld\n\
\tTN True Negatives: %6ld\n\
\tFP False Positives: %6ld\n\
\tFN False Negatives: %6ld\n\
\tSC Spam Corpusfed: %6ld\n\
\tNC Nonspam Corpusfed: %6ld\n\
\tTL Training Left: %6ld\n\
\tSHR Spam Hit Rate % 7.2f%%\n\
\tHSR Ham Strike Rate: % 7.2f%%\n\
\tPPV Positive predictive value: % 7.2f%%\n\
\tOCA Overall Accuracy: % 7.2f%%\n\
\n",
(username) ? username : "TOTAL",
total_spam, total_innocent,
innocent_misclassified, spam_misclassified,
spam_corpusfed, innocent_corpusfed,
MAX(0, 2500 - (tptr->innocent_learned +
tptr->innocent_classified)),
(all_spam) ?
(100.0-((float)spam_misclassified / (float)all_spam )*100.0)
: 100.0,
(all_innocent) ?
100-(100.0-((float)innocent_misclassified / (float)all_innocent )*100.0)
: 100.0,
(total_spam + innocent_misclassified) ?
100-(100.0-((float)total_spam /
(float)(total_spam + innocent_misclassified))*100)
: 100.0,
(all_spam + all_innocent) ?
(100.0-(((float)spam_misclassified +(float)innocent_misclassified) /
(float)(all_spam + all_innocent))*100.0)
: 100.0);
}
else
{
#ifdef LONG_USERNAMES
printf ("%s\n TP:%6ld TN:%6ld FP:%6ld FN:%6ld SC:%6ld NC:%6ld\n",
#else
printf ("%-16s TP:%6ld TN:%6ld FP:%6ld FN:%6ld SC:%6ld NC:%6ld\n",
#endif
(username) ? username : "TOTAL",
total_spam, total_innocent,
innocent_misclassified, spam_misclassified,
spam_corpusfed, innocent_corpusfed);
if (opt_stats)
printf (
#ifdef LONG_USERNAMES
" "
#else
" "
#endif
"SHR: % 7.2f%% HSR: % 7.2f%% OCA: % 7.2f%%\n",
(all_spam) ?
(100.0-((float)spam_misclassified / (float)all_spam )*100.0)
: 100.0,
(all_innocent) ?
100.0-
(100.0-((float)innocent_misclassified / (float)all_innocent )*100.0)
: 0.0,
(all_spam + all_innocent) ?
(100.0-(((float)spam_misclassified +(float)innocent_misclassified) /
(float)(all_spam + all_innocent))*100.0)
: 100.0);
}
if (opt_reset && username) {
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
username, "rstats");
_ds_prepare_path_for (filename);
file = fopen (filename, "w");
if (file == NULL)
{
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
if (MTX)
dspam_destroy (MTX);
open_mtx = NULL;
return EFILE;
}
fprintf (file, "%ld,%ld,%ld,%ld,%ld,%ld\n",
MAX(0,(tptr->spam_learned + tptr->spam_classified) -
(tptr->spam_misclassified + tptr->spam_corpusfed)),
MAX(0,(tptr->innocent_learned + tptr->innocent_classified) -
(tptr->innocent_misclassified + tptr->innocent_corpusfed)),
tptr->spam_misclassified,
tptr->innocent_misclassified,
tptr->spam_corpusfed,
tptr->innocent_corpusfed);
fclose(file);
}
if (MTX)
dspam_destroy (MTX);
open_mtx = NULL;
return 0;
}
void
dieout (int signal)
{
signal = signal; /* Keep compile happy */
fprintf (stderr, "terminated.\n");
if (open_ctx != NULL)
dspam_destroy (open_ctx);
if (open_mtx != NULL)
dspam_destroy (open_mtx);
_ds_destroy_config(agent_config);
exit (EXIT_SUCCESS);
}
void
usage (void)
{
(void)fprintf (stderr,
"usage: dspam_stats [-h]|[--profile=PROFILE] [-HrsSt] [user [user...]]\n\
\tPrint dspam statistics for users.\n\
\tIf no users are specified, stats for all users are printed.\n\
\t-h: print this message\n\
\t-H: print stats in \"human friendly\" format\n\
\t-r: Resets the current snapshot\n\
\t-s: Displays stats since last snapshot (instead of since epoch)\n\
\t-S: Displays accuracy percentages in addition to stats\n\
\t-t: Displays a total of all statistics displayed\n");
_ds_destroy_config(agent_config);
exit(EXIT_FAILURE);
}