/* $Id: dspam.c,v 1.412 2011/11/10 00:26:00 tomhendr 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 .
*/
/*
* dspam.c - primary dspam processing agent
*
* DESCRIPTION
* The agent provides a commandline interface to the libdspam core engine
* and also provides advanced functions such as a daemonized LMTP server,
* extended groups, and other agent features outlined in the documentation.
*
* This codebase is the full client/processing engine. See dspamc.c for
* the lightweight client-only codebase.
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#ifdef HAVE_UNISTD_H
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#ifdef _WIN32
#include
#include
#define WIDEXITED(x) 1
#define WEXITSTATUS(x) (x)
#include
#else
#include
#include
#endif
#include "config.h"
#include "util.h"
#include "read_config.h"
#ifdef DAEMON
#include
#include "daemon.h"
#include "client.h"
#endif
#ifdef TIME_WITH_SYS_TIME
# include
# include
#else
# ifdef HAVE_SYS_TIME_H
# include
# else
# include
# endif
#endif
#ifdef EXT_LOOKUP
#include "external_lookup.h"
int verified_user = 0;
#endif
#include "dspam.h"
#include "agent_shared.h"
#include "pref.h"
#include "libdspam.h"
#include "language.h"
#include "buffer.h"
#include "base64.h"
#include "heap.h"
#include "pref.h"
#include "config_api.h"
#define USE_LMTP (_ds_read_attribute(agent_config, "DeliveryProto") && !strcmp(_ds_read_attribute(agent_config, "DeliveryProto"), "LMTP"))
#define USE_SMTP (_ds_read_attribute(agent_config, "DeliveryProto") && !strcmp(_ds_read_attribute(agent_config, "DeliveryProto"), "SMTP"))
#define LOOKUP(A, B) ((_ds_pref_val(A, "localStore")[0]) ? _ds_pref_val(A, "localStore") : B)
int
main (int argc, char *argv[])
{
AGENT_CTX ATX; /* agent configuration */
buffer *message = NULL; /* input message */
int agent_init = 0; /* agent is initialized */
int driver_init = 0; /* storage driver is initialized */
int pwent_cache_init = 0; /* cache for username and uid is initialized */
int exitcode = EXIT_SUCCESS;
struct nt_node *node_nt;
struct nt_c c_nt;
srand ((long) time(NULL) ^ (long) getpid ());
umask (006); /* rw-rw---- */
setbuf (stdout, NULL); /* unbuffered output */
#ifdef DEBUG
DO_DEBUG = 0;
#endif
#ifdef DAEMON
pthread_mutex_init(&__syslog_lock, NULL);
#endif
/* Cache my username and uid for trusted user security */
if (!init_pwent_cache()) {
LOG(LOG_ERR, ERR_AGENT_RUNTIME_USER);
exitcode = EXIT_FAILURE;
goto BAIL;
} else
pwent_cache_init = 1;
/* Read dspam.conf into global config structure (ds_config_t) */
agent_config = read_config(NULL);
if (!agent_config) {
LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
exitcode = EXIT_FAILURE;
goto BAIL;
}
if (!_ds_read_attribute(agent_config, "Home")) {
LOG(LOG_ERR, ERR_AGENT_DSPAM_HOME);
exitcode = EXIT_FAILURE;
goto BAIL;
}
/* Set up an agent context to define the behavior of the processor */
if (initialize_atx(&ATX)) {
LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
exitcode = EXIT_FAILURE;
goto BAIL;
} else {
agent_init = 1;
}
if (process_arguments(&ATX, argc, argv)) {
LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
exitcode = EXIT_FAILURE;
goto BAIL;
}
/* Switch into daemon mode if --daemon was specified on the commandline */
#ifdef DAEMON
#ifdef TRUSTED_USER_SECURITY
if (ATX.operating_mode == DSM_DAEMON && ATX.trusted)
#else
if (ATX.operating_mode == DSM_DAEMON)
#endif
{
exitcode = daemon_start(&ATX);
if (agent_init) {
nt_destroy(ATX.users);
nt_destroy(ATX.recipients);
}
if (agent_config)
_ds_destroy_config(agent_config);
pthread_mutex_destroy(&__syslog_lock);
if (pwent_cache_init)
free(__pw_name);
exit(exitcode);
}
#endif
if (apply_defaults(&ATX)) {
LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
exitcode = EXIT_FAILURE;
goto BAIL;
}
if (check_configuration(&ATX)) {
LOG(LOG_ERR, ERR_AGENT_MISCONFIGURED);
exitcode = EXIT_FAILURE;
goto BAIL;
}
/* Read the message in and apply ParseTo services */
message = read_stdin(&ATX);
if (message == NULL) {
exitcode = EXIT_FAILURE;
goto BAIL;
}
if (ATX.users->items == 0)
{
LOG(LOG_ERR, ERR_AGENT_USER_UNDEFINED);
fprintf (stderr, "%s\n", SYNTAX);
exitcode = EXIT_FAILURE;
goto BAIL;
}
/* Perform client-based processing of message if --client was specified */
#ifdef DAEMON
if (ATX.client_mode &&
_ds_read_attribute(agent_config, "ClientIdent") &&
(_ds_read_attribute(agent_config, "ClientHost") ||
_ds_read_attribute(agent_config, "ServerDomainSocketPath")))
{
exitcode = client_process(&ATX, message);
if (exitcode<0) {
LOG(LOG_ERR, ERR_CLIENT_EXIT, exitcode);
}
} else {
#endif
/* Primary (non-client) processing procedure */
if (libdspam_init(_ds_read_attribute(agent_config, "StorageDriver"))) {
LOG(LOG_CRIT, ERR_DRV_INIT);
exitcode = EXIT_FAILURE;
goto BAIL;
}
if (dspam_init_driver (NULL))
{
LOG (LOG_WARNING, ERR_DRV_INIT);
exitcode = EXIT_FAILURE;
goto BAIL;
} else {
driver_init = 1;
}
ATX.results = nt_create(NT_PTR);
if (ATX.results == NULL) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
exitcode = EUNKNOWN;
goto BAIL;
}
exitcode = process_users(&ATX, message);
if (exitcode) {
LOGDEBUG("process_users() failed on error %d", exitcode);
} else {
exitcode = 0;
node_nt = c_nt_first(ATX.results, &c_nt);
while(node_nt) {
agent_result_t result = (agent_result_t) node_nt->ptr;
if (result->exitcode)
exitcode--;
node_nt = c_nt_next(ATX.results, &c_nt);
}
}
nt_destroy(ATX.results);
node_nt = NULL;
#ifdef DAEMON
}
#endif
BAIL:
if (message)
buffer_destroy(message);
if (agent_init) {
nt_destroy(ATX.users);
nt_destroy(ATX.recipients);
}
#ifdef DAEMON
if (agent_init) {
if (!ATX.client_mode) {
#endif
if (driver_init)
dspam_shutdown_driver(NULL);
libdspam_shutdown();
#ifdef DAEMON
}
}
#endif
if (agent_config)
_ds_destroy_config(agent_config);
if (pwent_cache_init)
free(__pw_name);
#ifdef DAEMON
pthread_mutex_destroy(&__syslog_lock);
// pthread_exit(0);
#endif
exit(exitcode);
}
/*
* process_message(AGENT_CTX *ATX, buffer *message, const char *username)
*
* DESCRIPTION
* Core message processing / interface to libdspam
* This function should be called once for each destination user
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* message Buffer structure containing the message
* username Destination user
*
* RETURN VALUES
* The processing result is returned:
*
* DSR_ISINNOCENT Message is innocent
* DSR_ISSPAM Message is spam
* (other) Error code (see libdspam.h)
*/
int
process_message (
AGENT_CTX *ATX,
buffer * message,
const char *username,
char **result_string)
{
DSPAM_CTX *CTX = NULL; /* (lib)dspam context */
ds_message_t components;
char *copyback;
int have_signature = 0;
int result, i;
int internally_canned = 0;
ATX->timestart = _ds_gettime(); /* set tick count to get run time */
if (message->data == NULL) {
LOGDEBUG("empty message provided");
return EINVAL;
}
/* Create a dspam context based on the agent context */
CTX = ctx_init(ATX, username);
if (CTX == NULL) {
LOG (LOG_WARNING, ERR_CORE_INIT);
result = EUNKNOWN;
goto RETURN;
}
/* Configure libdspam's storage properties, then attach storage */
set_libdspam_attributes(CTX);
if (ATX->sockfd && ATX->dbh == NULL)
ATX->dbh = _ds_connect(CTX);
/* Re-Establish database connection (if failed) */
if (attach_context(CTX, ATX->dbh)) {
if (ATX->sockfd) {
ATX->dbh = _ds_connect(CTX);
LOG(LOG_ERR, ERR_CORE_REATTACH);
if (attach_context(CTX, ATX->dbh)) {
LOG(LOG_ERR, ERR_CORE_ATTACH);
result = EUNKNOWN;
goto RETURN;
}
} else {
LOG(LOG_ERR, ERR_CORE_ATTACH);
result = EUNKNOWN;
goto RETURN;
}
}
/* Parse and decode the message into our message structure (ds_message_t) */
components = _ds_actualize_message (message->data);
if (components == NULL) {
LOG (LOG_ERR, ERR_AGENT_PARSER_FAILED);
result = EUNKNOWN;
goto RETURN;
}
CTX->message = components;
#ifdef CLAMAV
/* Check for viruses */
if (_ds_read_attribute(agent_config, "ClamAVPort") &&
_ds_read_attribute(agent_config, "ClamAVHost") &&
CTX->source != DSS_ERROR &&
strcmp(_ds_pref_val(ATX->PTX, "optOutClamAV"), "on"))
{
if (has_virus(message)) {
char ip[32];
CTX->result = DSR_ISSPAM;
CTX->probability = 1.0;
CTX->confidence = 1.0;
STATUS("A virus was detected in the message contents");
result = DSR_ISSPAM;
strcpy(CTX->class, LANG_CLASS_VIRUS);
internally_canned = 1;
if(!_ds_match_attribute(agent_config, "TrackSources", "virus")) {
if (!dspam_getsource (CTX, ip, sizeof (ip)))
{
LOG(LOG_WARNING, "virus warning: infected message from %s", ip);
}
}
}
}
#endif
/* Check for a domain blocklist (user-based setting) */
if (is_blocklisted(CTX, ATX)) {
CTX->result = DSR_ISSPAM;
CTX->probability = 1.0;
CTX->confidence = 1.0;
strcpy(CTX->class, LANG_CLASS_BLOCKLISTED);
internally_canned = 1;
}
/* Check for an RBL blacklist (system-based setting) */
if (CTX->classification == DSR_NONE &&
_ds_read_attribute(agent_config, "Lookup"))
{
if(strcasecmp(_ds_pref_val(ATX->PTX, "ignoreRBLLookups"), "on")) {
int bad = is_blacklisted(CTX, ATX);
if (bad) {
if ((_ds_match_attribute(agent_config, "RBLInoculate", "on") ||
!strcasecmp(_ds_pref_val(ATX->PTX, "RBLInoculate"), "on")) &&
strcasecmp(_ds_pref_val(ATX->PTX, "RBLInoculate"), "off")) {
LOGDEBUG("source address is blacklisted. learning as spam.");
CTX->classification = DSR_ISSPAM;
CTX->source = DSS_INOCULATION;
} else {
CTX->result = DSR_ISSPAM;
CTX->probability = 1.0;
CTX->confidence = 1.0;
strcpy(CTX->class, LANG_CLASS_BLACKLISTED);
internally_canned = 1;
}
}
}
}
/* Process a signature if one was provided */
have_signature = find_signature(CTX, ATX);
if (ATX->source == DSS_CORPUS || ATX->source == DSS_NONE)
have_signature = 0; /* ignore sigs from corpusfed and inbound email */
char *original_username = strdup(CTX->username);
if (have_signature)
{
if (_ds_get_signature (CTX, &ATX->SIG, ATX->signature))
{
LOG(LOG_WARNING, ERR_AGENT_SIG_RET_FAILED, ATX->signature);
have_signature = 0;
}
else {
/* uid-based signatures will change the active username, so reload
preferences if it has changed */
CTX->signature = &ATX->SIG;
if (!strcasecmp(CTX->username, original_username)) {
if (ATX->PTX)
_ds_pref_free(ATX->PTX);
free(ATX->PTX);
ATX->PTX = NULL;
ATX->PTX = load_aggregated_prefs(ATX, CTX->username);
ATX->train_pristine = 0;
if ((_ds_match_attribute(agent_config, "TrainPristine", "on") ||
!strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on")) &&
strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "off")) {
ATX->train_pristine = 1;
}
/* Change also the mail recipient */
ATX->recipient = CTX->username;
}
}
} else if (CTX->operating_mode == DSM_CLASSIFY ||
CTX->classification != DSR_NONE)
{
CTX->flags = CTX->flags ^ DSF_SIGNATURE;
CTX->signature = NULL;
}
if (have_signature && CTX->classification != DSR_NONE) {
/*
* Reclassify (or retrain) message by signature
*/
if (retrain_message(CTX, ATX) != 0) {
have_signature = 0;
result = EFAILURE;
goto RETURN;
}
} else {
CTX->signature = NULL;
if (! ATX->train_pristine) {
if (CTX->classification != DSR_NONE && CTX->source == DSS_ERROR) {
LOG(LOG_WARNING, ERR_AGENT_NO_VALID_SIG);
result = EFAILURE;
goto RETURN;
}
}
/*
* Call libdspam to process the environment we've configured
*/
if (!internally_canned) {
result = dspam_process (CTX, message->data);
if (result != 0) {
result = EFAILURE;
goto RETURN;
}
}
}
result = CTX->result;
if (result == DSR_ISINNOCENT && !strcmp(CTX->class, LANG_CLASS_WHITELISTED)) {
STATUS("Auto-Whitelisted");
}
/*
* Send any relevant notifications to the user (first spam, etc)
* Only if the process was successful
*/
if (result == DSR_ISINNOCENT || result == DSR_ISSPAM)
{
do_notifications(CTX, ATX);
}
/* Consult global group or classification network */
result = ensure_confident_result(CTX, ATX, result);
if (result < 0)
goto RETURN;
/* Inoculate other users (signature) */
if (have_signature &&
CTX->classification == DSR_ISSPAM &&
CTX->source != DSS_CORPUS &&
ATX->inoc_users->items > 0)
{
struct nt_node *node_int;
struct nt_c c_i;
node_int = c_nt_first (ATX->inoc_users, &c_i);
while (node_int != NULL)
{
inoculate_user (ATX, (const char *) node_int->ptr, &ATX->SIG, NULL);
node_int = c_nt_next (ATX->inoc_users, &c_i);
}
}
/* Inoculate other users (message) */
if (!have_signature &&
CTX->classification == DSR_ISSPAM &&
CTX->source != DSS_CORPUS &&
ATX->inoc_users->items > 0)
{
struct nt_node *node_int;
struct nt_c c_i;
node_int = c_nt_first (ATX->inoc_users, &c_i);
while (node_int != NULL)
{
inoculate_user (ATX, (const char *) node_int->ptr, NULL, message->data);
node_int = c_nt_next (ATX->inoc_users, &c_i);
}
inoculate_user (ATX, CTX->username, NULL, message->data);
result = DSR_ISSPAM;
CTX->result = DSR_ISSPAM;
goto RETURN;
}
/* Generate a signature id for the message and store */
if (internally_canned) {
if (CTX->signature) {
free(CTX->signature->data);
free(CTX->signature);
CTX->signature = NULL;
}
CTX->signature = calloc(1, sizeof(struct _ds_spam_signature));
if (CTX->signature) {
CTX->signature->length = 8;
CTX->signature->data = calloc(1, (CTX->signature->length));
}
}
if (internally_canned || (CTX->operating_mode == DSM_PROCESS &&
CTX->classification == DSR_NONE &&
CTX->signature != NULL))
{
int valid = 0;
while (!valid)
{
_ds_create_signature_id (CTX, ATX->signature, sizeof (ATX->signature));
if (_ds_verify_signature (CTX, ATX->signature))
valid = 1;
}
LOGDEBUG ("saving signature as %s", ATX->signature);
if (CTX->classification == DSR_NONE && CTX->training_mode != DST_NOTRAIN)
{
if (!ATX->train_pristine) {
int x = _ds_set_signature (CTX, CTX->signature, ATX->signature);
if (x) {
LOG(LOG_WARNING, "_ds_set_signature() failed with error %d", x);
}
}
}
}
/* Restore original username if necessary */
if (CTX->group != NULL && strcasecmp(CTX->username, original_username) != 0)
{
LOGDEBUG ("restoring original username %s after group processing as %s", original_username, CTX->username);
CTX->username = original_username;
}
/* Write .stats file for web interface */
if (CTX->training_mode != DST_NOTRAIN && _ds_match_attribute(agent_config, "WebStats", "on")) {
write_web_stats (
ATX,
(CTX->group == NULL || CTX->flags & DSF_MERGED) ?
CTX->username : CTX->group,
(CTX->group != NULL && CTX->flags & DSF_MERGED) ?
CTX->group: NULL,
&CTX->totals);
}
LOGDEBUG ("libdspam returned probability of %f", CTX->probability);
LOGDEBUG ("message result: %s", (result != DSR_ISSPAM) ? "NOT SPAM" : "SPAM");
/* System and User logging */
if (CTX->operating_mode != DSM_CLASSIFY &&
(_ds_match_attribute(agent_config, "SystemLog", "on") ||
_ds_match_attribute(agent_config, "UserLog", "on")))
{
log_events(CTX, ATX);
}
/* Fragment Store - Store 1k fragments of each message for web users who
* want to be able to see them from history. This requires some type of
* find recipe for purging
*/
if (ATX->PTX != NULL
&& !strcmp(_ds_pref_val(ATX->PTX, "storeFragments"), "on")
&& CTX->source != DSS_ERROR)
{
char dirname[MAX_FILENAME_LENGTH];
char corpusfile[MAX_FILENAME_LENGTH];
char output[1024];
FILE *file;
_ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group :
CTX->username), "frag");
snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s.frag",
dirname, ATX->signature);
LOGDEBUG("writing to frag file %s", corpusfile);
_ds_prepare_path_for(corpusfile);
file = fopen(corpusfile, "w");
if (file != NULL) {
char *body = strstr(message->data, "\n\n");
if (!body)
body = message->data;
strlcpy(output, body, sizeof(output));
fputs(output, file);
fputs("\n", file);
fclose(file);
}
}
/* Corpus Maker - Build a corpus in DSPAM_HOME/data/USERPATH/USER.corpus */
if (ATX->PTX != NULL && !strcmp(_ds_pref_val(ATX->PTX, "makeCorpus"), "on")) {
if (ATX->source != DSS_ERROR) {
char dirname[MAX_FILENAME_LENGTH];
char corpusfile[MAX_FILENAME_LENGTH];
FILE *file;
_ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
: CTX->username), "corpus");
snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
dirname, (result == DSR_ISSPAM) ? "spam" : "nonspam",
ATX->signature);
LOGDEBUG("writing to corpus file %s", corpusfile);
_ds_prepare_path_for(corpusfile);
file = fopen(corpusfile, "w");
if (file != NULL) {
fputs(message->data, file);
fclose(file);
}
} else {
char dirname[MAX_FILENAME_LENGTH];
char corpusfile[MAX_FILENAME_LENGTH];
char corpusdest[MAX_FILENAME_LENGTH];
_ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
: CTX->username), "corpus");
snprintf(corpusdest, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
dirname, (result == DSR_ISSPAM) ? "spam" : "nonspam",
ATX->signature);
snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
dirname, (result == DSR_ISSPAM) ? "nonspam" : "spam",
ATX->signature);
LOGDEBUG("moving corpusfile %s -> %s", corpusfile, corpusdest);
_ds_prepare_path_for(corpusdest);
rename(corpusfile, corpusdest);
}
}
/* False positives and spam misses should return here */
if (CTX->message == NULL)
goto RETURN;
/* Add headers, tag, and deliver if necessary */
{
add_xdspam_headers(CTX, ATX);
}
if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag") &&
result == DSR_ISSPAM)
{
tag_message(ATX, CTX->message);
}
if (
(!strcmp(_ds_pref_val(ATX->PTX, "tagSpam"), "on")
&& CTX->result == DSR_ISSPAM)
||
(!strcmp(_ds_pref_val(ATX->PTX, "tagNonspam"), "on")
&& CTX->result == DSR_ISINNOCENT)
)
{
i = embed_msgtag(CTX, ATX);
if (i<0) {
result = i;
goto RETURN;
}
}
if (strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers") &&
!ATX->train_pristine &&
(CTX->classification == DSR_NONE || internally_canned))
{
i = embed_signature(CTX, ATX);
if (i<0) {
result = i;
goto RETURN;
}
}
/* Reassemble message from components */
copyback = _ds_assemble_message (CTX->message, (USE_LMTP || USE_SMTP) ? "\r\n" : "\n");
buffer_clear (message);
buffer_cat (message, copyback);
free (copyback);
/* Track source address and report to syslog, RABL */
if ( _ds_read_attribute(agent_config, "TrackSources") &&
CTX->operating_mode == DSM_PROCESS &&
CTX->source != DSS_CORPUS &&
CTX->source != DSS_ERROR)
{
tracksource(CTX);
}
/* Print --classify output */
if (CTX->operating_mode == DSM_CLASSIFY || ATX->flags & DAF_SUMMARY)
{
char data[128];
FILE *fout;
switch (CTX->result) {
case DSR_ISSPAM:
strcpy(data, "Spam");
break;
default:
strcpy(data, "Innocent");
break;
}
if (ATX->sockfd) {
fout = ATX->sockfd;
ATX->sockfd_output = 1;
}
else {
fout = stdout;
}
fprintf(fout, "X-DSPAM-Result: %s; result=\"%s\"; class=\"%s\"; "
"probability=%01.4f; confidence=%02.2f; signature=%s\n",
CTX->username,
data,
CTX->class,
CTX->probability,
CTX->confidence,
(ATX->signature[0]) ? ATX->signature : "N/A");
}
ATX->learned = CTX->learned;
if (result_string)
*result_string = strdup(CTX->class);
RETURN:
if (have_signature) {
if (ATX->SIG.data != NULL) {
free(ATX->SIG.data);
ATX->SIG.data = NULL;
}
}
ATX->signature[0] = 0;
nt_destroy (ATX->inoc_users);
nt_destroy (ATX->classify_users);
if (CTX) {
if (CTX->signature == &ATX->SIG) {
CTX->signature = NULL;
} else if (CTX->signature != NULL) {
if (CTX->signature->data != NULL) {
free (CTX->signature->data);
}
free (CTX->signature);
CTX->signature = NULL;
}
dspam_destroy (CTX);
}
return result;
}
/*
* deliver_message(AGENT_CTX *ATX, const char *message,
* const char *mailer_args, const char *username, FILE *stream,
* int result)
*
* DESCRIPTION
* Deliver message to the appropriate destination. This could be one of:
* - Trusted/Untrusted Delivery Agent
* - Delivery Host (SMTP/LMTP)
* - Quarantine Agent
* - File stream (--stdout)
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* message Message to be delivered
* mailer_args Arguments to pass to local agents via pipe()
* username Destination username
* stream File stream (if any) for stdout delivery
* result Message classification result (DSR_)
*
* RETURN VALUES
* returns 0 on success
* EINVAL on permanent failure
* EFAILURE on temporary failure
* EFILE local agent failure
*/
int
deliver_message (
AGENT_CTX *ATX,
const char *message,
const char *mailer_args,
const char *username,
FILE *stream,
int result)
{
char args[1024];
char *margs, *mmargs, *arg;
FILE *file;
int rc;
#ifdef DAEMON
/* If QuarantineMailbox defined and delivering a spam, get
* name of recipient, truncate possible "+detail", and
* add the QuarantineMailbox name (that must include the "+")
*/
if ((_ds_read_attribute(agent_config, "QuarantineMailbox")) &&
(result == DSR_ISSPAM)) {
strlcpy(args, ATX->recipient, sizeof(args));
/* strip trailing @domain, if present: */
arg=index(args, '@');
if (arg) *arg = '\0';
arg=index(args,'+');
if (arg != NULL) *arg='\0';
strlcat(args,_ds_read_attribute(agent_config, "QuarantineMailbox"),
sizeof(args));
/* append trailing @domain again, if it was present: */
arg=index(ATX->recipient, '@');
if (arg) strlcat (args, arg, sizeof(args));
ATX->recipient=args;
}
/* If (using LMTP or SMTP) and (not delivering to stdout) and
* (we shouldn't be delivering this to a quarantine agent)
* then call deliver_socket to deliver to DeliveryHost
*/
if (
(USE_LMTP || USE_SMTP) && ! (ATX->flags & DAF_STDOUT) &&
(!(result == DSR_ISSPAM &&
_ds_read_attribute(agent_config, "QuarantineAgent") &&
ATX->PTX && !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "quarantine")))
)
{
return deliver_socket(ATX, message, (USE_LMTP) ? DDP_LMTP : DDP_SMTP);
}
#endif
if (message == NULL)
return EINVAL;
/* If we're delivering to stdout, we need to provide a classification for
* use by client/daemon setups where the client needs to know the result
* in order to support broken returnCodes.
*/
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
fprintf(stream, "X-Daemon-Classification: %s\n",
(result == DSR_ISSPAM) ? "SPAM" : "INNOCENT");
if (mailer_args == NULL) {
fputs (message, stream);
return 0;
}
/* Prepare local mailer args and interpolate all special symbols */
args[0] = 0;
margs = strdup (mailer_args);
mmargs = margs;
arg = strsep (&margs, " ");
while (arg != NULL)
{
char a[256], b[256];
/* Destination user */
if (!strcmp (arg, "$u") || !strcmp (arg, "\\$u") ||
!strcmp (arg, "%u") || !strcmp(arg, "\\%u"))
{
strlcpy(a, username, sizeof(a));
}
/* Recipient (from RCPT TO)*/
else if (!strcmp (arg, "%r") || !strcmp (arg, "\\%r"))
{
if (ATX->recipient)
strlcpy(a, ATX->recipient, sizeof(a));
else
strlcpy(a, username, sizeof(a));
}
/* Sender (from MAIL FROM) */
else if (!strcmp (arg, "%s") || !strcmp (arg, "\\%s"))
strlcpy(a, ATX->mailfrom, sizeof(a));
else
strlcpy(a, arg, sizeof(a));
/* Escape special characters */
if (strcmp(a, "\"\"")) {
size_t i;
for(i=0;icomponents->first->ptr;
struct nt_node *node_header = block->headers->first;
int tagged = 0;
char spam_subject[16];
strcpy(spam_subject, "[SPAM]");
if (_ds_pref_val(ATX->PTX, "spamSubject")[0] != '\n' &&
_ds_pref_val(ATX->PTX, "spamSubject")[0] != 0)
{
strlcpy(spam_subject, _ds_pref_val(ATX->PTX, "spamSubject"),
sizeof(spam_subject));
}
/* Only scan the first (primary) header of the message. */
while (node_header != NULL)
{
ds_header_t head;
head = (ds_header_t) node_header->ptr;
if (head->heading && !strcasecmp(head->heading, "Subject"))
{
/* CURRENT HEADER: Is this header already tagged? */
if (strncmp(head->data, spam_subject, strlen(spam_subject)))
{
/* Not tagged, so tag it */
long subject_length = strlen(head->data)+strlen(spam_subject)+2;
char *subject = malloc(subject_length);
if (subject != NULL) {
snprintf(subject,
subject_length, "%s %s",
spam_subject,
head->data);
free(head->data);
head->data = subject;
}
}
/* ORIGINAL HEADER: Is this header already tagged? */
if (head->original_data != NULL &&
strncmp(head->original_data, spam_subject, strlen(spam_subject)))
{
/* Not tagged => tag it. */
long subject_length = strlen(head->original_data)+strlen(spam_subject)+2;
char *subject = malloc(subject_length);
if (subject != NULL) {
snprintf(subject,
subject_length, "%s %s",
spam_subject,
head->original_data);
free(head->original_data);
head->original_data = subject;
}
}
tagged = 1;
}
node_header = node_header->next;
}
/* There doesn't seem to be a subject field, so make one */
if (!tagged)
{
char text[80];
ds_header_t header;
snprintf(text, sizeof(text), "Subject: %s", spam_subject);
header = _ds_create_header_field(text);
if (header != NULL)
{
#ifdef VERBOSE
LOGDEBUG("appending header %s: %s", header->heading, header->data);
#endif
nt_add(block->headers, (void *) header);
}
}
return 0;
}
/*
* quarantine_message(AGENT_CTX *ATX, const char *message,
* const char *username)
*
* DESCRIPTION
* Quarantine a message using DSPAM's internal quarantine function
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* message Text message to quarantine
* username Destination user
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int
quarantine_message (AGENT_CTX *ATX, const char *message, const char *username)
{
char filename[MAX_FILENAME_LENGTH];
char *x, *msg;
int line = 1, i;
FILE *file;
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, username), "mbox");
_ds_prepare_path_for(filename);
file = fopen (filename, "a");
if (file == NULL)
{
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
return EFILE;
}
i = _ds_get_fcntl_lock(fileno(file));
if (i) {
LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
return EFILE;
}
/* Write our own "From " header if the MTA didn't give us one. This
* allows for the viewing of a mailbox from elm or some other local
* client that would otherwise believe the mailbox is corrupt.
*/
if (strncmp (message, "From ", 5))
{
char head[128];
time_t tm = time (NULL);
snprintf (head, sizeof (head), "From QUARANTINE %s", ctime (&tm));
fputs (head, file);
}
msg = strdup(message);
if (msg == NULL) {
LOG (LOG_CRIT, ERR_MEM_ALLOC);
return EUNKNOWN;
}
/* TODO: Is there a way to do this without a strdup/strsep ? */
x = strsep (&msg, "\n");
while (x)
{
/* Quote any lines beginning with 'From ' to keep mbox from breaking */
if (!strncmp (x, "From ", 5) && line != 1)
fputs (">", file);
fputs (x, file);
fputs ("\n", file);
line++;
x = strsep (&msg, "\n");
}
free (msg);
fputs ("\n\n", file);
_ds_free_fcntl_lock(fileno(file));
fclose (file);
return 0;
}
/*
* write_web_stats(AGENT_CTX *ATX, const char *username, const char *group,
* struct _ds_spam_totals *totals)
*
* DESCRIPTION
* Writes a .stats file in the user's data directory for use with web UI
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* username Destination user
* group Group membership
* totals Pointer to processing totals
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int
write_web_stats (
AGENT_CTX *ATX,
const char *username,
const char *group,
struct _ds_spam_totals *totals)
{
char filename[MAX_FILENAME_LENGTH];
FILE *file;
if (!totals)
return EINVAL;
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, username), "stats");
_ds_prepare_path_for (filename);
file = fopen (filename, "w");
if (file == NULL) {
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
return EFILE;
}
fprintf (file, "%ld,%ld,%ld,%ld,%ld,%ld\n",
MAX(0, (totals->spam_learned + totals->spam_classified) -
(totals->spam_misclassified + totals->spam_corpusfed)),
MAX(0, (totals->innocent_learned + totals->innocent_classified) -
(totals->innocent_misclassified + totals->innocent_corpusfed)),
totals->spam_misclassified, totals->innocent_misclassified,
totals->spam_corpusfed, totals->innocent_corpusfed);
if (group)
fprintf(file, "%s\n", group);
fclose (file);
return 0;
}
/*
* inoculate_user(AGENT_CTX *ATX, const char *username,
* struct _ds_spam_signature *SIG, const char *message)
*
* DESCRIPTION
* Provide a vaccination for the spam processed to the target user
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* username Target user
* SIG Signature (if providing signature-based inoculation)
* message Text Message (if providing message-based inoculation)
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int
inoculate_user (
AGENT_CTX *ATX,
const char *username,
struct _ds_spam_signature *SIG,
const char *message)
{
DSPAM_CTX *INOC;
int do_inoc = 1, result = 0;
int f_all = 0;
LOGDEBUG ("checking if user %s requires this inoculation", username);
if (user_classify(ATX, username, SIG, message) == DSR_ISSPAM) {
do_inoc = 0;
}
if (!do_inoc)
{
LOGDEBUG ("skipping user %s: doesn't require inoculation", username);
return EFAILURE;
}
else
{
LOGDEBUG ("inoculating user %s", username);
if (ATX->flags & DAF_NOISE)
f_all |= DSF_NOISE;
if (ATX->PTX != NULL &&
strcmp(_ds_pref_val(ATX->PTX, "processorBias"), ""))
{
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
f_all |= DSF_BIAS;
} else {
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
f_all |= DSF_BIAS;
}
INOC = dspam_create (username,
NULL,
_ds_read_attribute(agent_config, "Home"),
DSM_PROCESS,
f_all);
if (INOC)
{
set_libdspam_attributes(INOC);
if (attach_context(INOC, ATX->dbh)) {
LOG (LOG_WARNING, ERR_CORE_ATTACH);
dspam_destroy(INOC);
return EUNKNOWN;
}
INOC->classification = DSR_ISSPAM;
INOC->source = DSS_INOCULATION;
if (SIG)
{
INOC->flags |= DSF_SIGNATURE;
INOC->signature = SIG;
result = dspam_process (INOC, NULL);
}
else
{
result = dspam_process (INOC, message);
}
if (SIG)
INOC->signature = NULL;
dspam_destroy (INOC);
}
}
return result;
}
/*
* user_classify(AGENT_CTX *ATX, const char *username,
* struct _ds_spam_signature *SIG, const char *message)
*
* DESCRIPTION
* Determine the classification of a message for another user
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* username Target user
* SIG Signature (if performing signature-based classification)
* message Text Message (if performing message-based ciassification)
*
* RETURN VALUES
* returns DSR_ value, standard errors on failure
*/
int
user_classify (
AGENT_CTX *ATX,
const char *username,
struct _ds_spam_signature *SIG,
const char *message)
{
DSPAM_CTX *CLX;
int result = 0;
int f_all = 0;
if (SIG == NULL && message == NULL) {
LOG(LOG_WARNING, "user_classify(): SIG == NULL, message == NULL");
return EINVAL;
}
if (ATX->flags & DAF_NOISE)
f_all |= DSF_NOISE;
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "")) {
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
f_all |= DSF_BIAS;
} else {
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
f_all |= DSF_BIAS;
}
/* First see if the user needs to be inoculated */
CLX = dspam_create (username,
NULL,
_ds_read_attribute(agent_config, "Home"),
DSM_CLASSIFY,
f_all);
if (CLX)
{
set_libdspam_attributes(CLX);
if (attach_context(CLX, ATX->dbh)) {
LOG (LOG_WARNING, ERR_CORE_ATTACH);
dspam_destroy(CLX);
return EUNKNOWN;
}
if (SIG)
{
CLX->flags |= DSF_SIGNATURE;
CLX->signature = SIG;
result = dspam_process (CLX, NULL);
}
else
{
if (message == NULL) {
LOG(LOG_WARNING, "user_classify: SIG = %ld, message = NULL\n", (unsigned long) SIG);
if (SIG) CLX->signature = NULL;
dspam_destroy (CLX);
return EFAILURE;
}
result = dspam_process (CLX, message);
}
if (SIG)
CLX->signature = NULL;
if (result)
{
LOGDEBUG ("user_classify() returned error %d", result);
result = EFAILURE;
}
else
result = CLX->result;
dspam_destroy (CLX);
}
return result;
}
/*
* send_notice(AGENT_CTX *ATX, const char *filename, const char *mailer_args,
* const char *username)
*
* DESCRIPTION
* Sends a canned notice to the destination user
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* filename Filename of canned notice
* mailer_args Local agent arguments
* username Destination user
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int send_notice(
AGENT_CTX *ATX,
const char *filename,
const char *mailer_args,
const char *username)
{
FILE *f;
char msgfile[MAX_FILENAME_LENGTH];
buffer *b;
char buf[1024];
time_t now;
int ret;
time(&now);
if (_ds_read_attribute(agent_config, "TxtDirectory")) {
snprintf(msgfile, sizeof(msgfile), "%s/%s", _ds_read_attribute(agent_config, "TxtDirectory"), filename);
} else {
snprintf(msgfile, sizeof(msgfile), "%s/txt/%s", _ds_read_attribute(agent_config, "Home"), filename);
}
f = fopen(msgfile, "r");
if (!f) {
LOG(LOG_ERR, ERR_IO_FILE_OPEN, filename, strerror(errno));
return EFILE;
}
b = buffer_create(NULL);
if (!b) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
fclose(f);
return EUNKNOWN;
}
strftime(buf,sizeof(buf), "Date: %a, %d %b %Y %H:%M:%S %z\n",
localtime(&now));
buffer_cat(b, buf);
while(fgets(buf, sizeof(buf), f)!=NULL) {
char *s = buf;
char *w = strstr(buf, "$u");
while(w != NULL) {
w[0] = 0;
buffer_cat(b, s);
buffer_cat(b, username);
s = w+2;
w = strstr(s, "$u");
}
buffer_cat(b, s);
}
fclose(f);
ret = deliver_message(ATX, b->data, mailer_args, username,
stdout, DSR_ISINNOCENT);
buffer_destroy(b);
return ret;
}
/*
* process_users(AGENT_CTX *ATX, buffer *message)
*
* DESCRIPTION
* Primary processing loop: cycle through all destination users and process
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* message Buffer structure containing text message
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int process_users(AGENT_CTX *ATX, buffer *message) {
int i = 0, have_rcpts = 0, return_code = 0, retcode = 0;
struct nt_node *node_nt;
struct nt_node *node_rcpt = NULL;
struct nt_c c_nt, c_rcpt;
buffer *parse_message;
agent_result_t presult = NULL;
char *plus, *atsign;
char mailbox[256];
FILE *fout;
if (ATX->sockfd) {
fout = ATX->sockfd;
} else {
fout = stdout;
}
node_nt = c_nt_first (ATX->users, &c_nt);
if (ATX->recipients) {
node_rcpt = c_nt_first (ATX->recipients, &c_rcpt);
have_rcpts = ATX->recipients->items;
}
/* Keep going as long as we have destination users */
while (node_nt || node_rcpt)
{
struct stat s;
char filename[MAX_FILENAME_LENGTH];
int optin, optout;
char *username = NULL;
/* If ServerParameters specifies a --user, there will only be one
* instance on the stack, but possible multiple recipients. So we
* need to recycle.
*/
if (node_nt == NULL)
node_nt = ATX->users->first;
/* Set the "current recipient" to either the next item on the rcpt stack
* or the current user if not present.
*/
#ifdef EXT_LOOKUP
verified_user = 0;
if (_ds_match_attribute(agent_config, "ExtLookup", "on")) {
LOGDEBUG ("looking up user %s using %s driver.", node_nt->ptr, _ds_read_attribute(agent_config, "ExtLookupDriver"));
username = external_lookup(agent_config, node_nt->ptr, username);
if (username != NULL) {
LOGDEBUG ("external lookup verified user %s", node_nt->ptr);
verified_user = 1;
if (_ds_match_attribute(agent_config, "ExtLookupMode", "map") ||
_ds_match_attribute(agent_config, "ExtLookupMode", "strict")) {
LOGDEBUG ("mapping address %s to uid %s", node_nt->ptr, username);
node_nt->ptr = username;
}
} else if (_ds_match_attribute(agent_config, "ExtLookupMode", "map")) {
LOGDEBUG ("no match for user %s but mode is %s. continuing...", node_nt->ptr, _ds_read_attribute(agent_config, "ExtLookupMode"));
verified_user = 1;
}
} else {
verified_user = 1;
}
#endif
username = node_nt->ptr;
if (node_rcpt) {
ATX->recipient = node_rcpt->ptr;
node_rcpt = c_nt_next (ATX->recipients, &c_rcpt);
} else {
/* We started out using the recipients list and it's exhausted, so quit */
if (have_rcpts)
break;
ATX->recipient = node_nt->ptr;
}
/* If support for "+detail" is enabled, save full mailbox name for
delivery and strip detail for processing */
if (_ds_match_attribute(agent_config, "EnablePlusedDetail", "on")) {
char plused_char = '+';
if (_ds_read_attribute(agent_config, "PlusedCharacter"))
plused_char = _ds_read_attribute(agent_config, "PlusedCharacter")[0];
strlcpy(mailbox, username, sizeof(mailbox));
ATX->recipient = mailbox;
if (_ds_match_attribute(agent_config, "PlusedUserLowercase", "on"))
lc (username, username);
plus = index(username, plused_char);
if (plus) {
atsign = index(plus, '@');
if (atsign)
strcpy(plus, atsign);
else
*plus='\0';
}
}
presult = calloc(1, sizeof(struct agent_result));
parse_message = buffer_create(message->data);
if (parse_message == NULL) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
presult->exitcode = ERC_PROCESS;
strcpy(presult->text, ERR_MEM_ALLOC);
if (ATX->results) {
nt_add(ATX->results, presult);
if (ATX->results->nodetype == NT_CHAR)
free(presult);
} else
free(presult);
presult = NULL;
continue;
}
/* Determine whether to activate debug. If we're running in daemon mode,
* debug is either on or off (it's a global variable), so this only
* applies to running in client or local processing mode.
*/
#ifdef DEBUG
if (!DO_DEBUG &&
(_ds_match_attribute(agent_config, "Debug", "*") ||
_ds_match_attribute(agent_config, "Debug", node_nt->ptr)))
{
// No DebugOpt specified; turn it on for everything
if (!_ds_read_attribute(agent_config, "DebugOpt"))
{
DO_DEBUG = 1;
}
else {
if (_ds_match_attribute(agent_config, "DebugOpt", "process") &&
ATX->source == DSS_NONE &&
ATX->operating_mode == DSM_PROCESS)
{
DO_DEBUG = 1;
}
if (_ds_match_attribute(agent_config, "DebugOpt", "classify") &&
ATX->operating_mode == DSM_CLASSIFY)
{
DO_DEBUG = 1;
}
if (_ds_match_attribute(agent_config, "DebugOpt", "spam") &&
ATX->classification == DSR_ISSPAM &&
ATX->source == DSS_ERROR)
{
DO_DEBUG = 1;
}
if (_ds_match_attribute(agent_config, "DebugOpt", "fp") &&
ATX->classification == DSR_ISINNOCENT &&
ATX->source == DSS_ERROR)
{
DO_DEBUG = 1;
}
if (_ds_match_attribute(agent_config, "DebugOpt", "inoculation") &&
ATX->source == DSS_INOCULATION)
{
DO_DEBUG = 1;
}
if (_ds_match_attribute(agent_config, "DebugOpt", "corpus") &&
ATX->source == DSS_CORPUS)
{
DO_DEBUG = 1;
}
}
}
ATX->status[0] = 0;
if (DO_DEBUG) {
LOGDEBUG ("DSPAM Instance Startup");
LOGDEBUG ("input args: %s", ATX->debug_args);
LOGDEBUG ("pass-thru args: %s", ATX->mailer_args);
LOGDEBUG ("processing user %s", (const char *) node_nt->ptr);
LOGDEBUG ("uid = %d, euid = %d, gid = %d, egid = %d",
getuid(), geteuid(), getgid(), getegid());
/* Write message to dspam.messags */
{
FILE *f;
char m[MAX_FILENAME_LENGTH];
snprintf (m, sizeof (m), "%s/dspam.messages", LOGDIR);
f = fopen (m, "a");
if (f != NULL)
{
fprintf (f, "%s\n", parse_message->data);
fclose (f);
}
}
}
#endif
/*
* Determine if the user is opted in or out
*/
ATX->PTX = load_aggregated_prefs(ATX, username);
if (!strcmp(_ds_pref_val(ATX->PTX, "fallbackDomain"), "on")) {
if (username != NULL && strchr(username, '@')) {
char *domain = strchr(username, '@');
username = domain;
} else {
LOG(LOG_ERR, "process_users(): Can not fallback to domains for username '%s' without @domain part.", username);
}
}
ATX->train_pristine = 0;
if ((_ds_match_attribute(agent_config, "TrainPristine", "on") ||
!strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on")) &&
strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "off")) {
ATX->train_pristine = 1;
}
_ds_userdir_path(filename,
_ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, username), "dspam");
optin = stat(filename, &s);
#ifdef HOMEDIR
if (!optin && (!S_ISDIR(s.st_mode))) {
optin = -1;
LOG(LOG_WARNING, ERR_AGENT_OPTIN_DIR, filename);
}
#endif
_ds_userdir_path(filename,
_ds_read_attribute(agent_config, "Home"),
LOOKUP(ATX->PTX, username), "nodspam");
optout = stat(filename, &s);
/* If the message is too big to process, just deliver it */
if (_ds_read_attribute(agent_config, "MaxMessageSize")) {
if (parse_message->used >
atoi(_ds_read_attribute(agent_config, "MaxMessageSize")))
{
LOG (LOG_INFO, "message too big, delivering");
optout = 0;
}
}
/* Deliver the message if the user has opted not to be filtered */
optout = (optout) ? 0 : 1;
optin = (optin) ? 0 : 1;
if /* opted out implicitly */
(optout || !strcmp(_ds_pref_val(ATX->PTX, "optOut"), "on") ||
/* not opted in (in an opt-in system) */
(_ds_match_attribute(agent_config, "Opt", "in") &&
!optin && strcmp(_ds_pref_val(ATX->PTX, "optIn"), "on")))
{
if (ATX->flags & DAF_DELIVER_INNOCENT)
{
retcode =
deliver_message (ATX, parse_message->data,
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
node_nt->ptr, fout, DSR_ISINNOCENT);
if (retcode)
presult->exitcode = ERC_DELIVERY;
if (retcode == EINVAL)
presult->exitcode = ERC_PERMANENT_DELIVERY;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
ATX->sockfd_output = 1;
}
}
/* Call process_message(), then handle result appropriately */
else
{
char *result_string = NULL;
int result;
result = process_message (ATX, parse_message, username, &result_string);
presult->classification = result;
#ifdef CLAMAV
if (result_string && !strcmp(result_string, LANG_CLASS_VIRUS)) {
if (_ds_match_attribute(agent_config, "ClamAVResponse", "reject")) {
presult->classification = DSR_ISSPAM;
presult->exitcode = ERC_PERMANENT_DELIVERY;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
free(result_string);
result_string = NULL;
goto RSET;
}
else if (_ds_match_attribute(agent_config, "ClamAVResponse", "spam"))
{
presult->classification = DSR_ISSPAM;
presult->exitcode = ERC_SUCCESS;
result = DSR_ISSPAM;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
} else {
presult->classification = DSR_ISINNOCENT;
presult->exitcode = ERC_SUCCESS;
free(result_string);
result_string = NULL;
goto RSET;
}
}
#endif
free(result_string);
result_string = NULL;
/* Exit code 99 for spam (when using broken return codes) */
if (_ds_match_attribute(agent_config, "Broken", "returnCodes")) {
if (result == DSR_ISSPAM)
return_code = 99;
}
/*
* Classify Only
*/
if (ATX->operating_mode == DSM_CLASSIFY)
{
node_nt = c_nt_next (ATX->users, &c_nt);
_ds_pref_free(ATX->PTX);
free(ATX->PTX);
ATX->PTX = NULL;
buffer_destroy(parse_message);
free(presult);
presult = NULL;
i++;
continue;
}
/*
* Classify and Process
*/
/* Innocent */
if (result != DSR_ISSPAM)
{
int deliver = 1;
/* Processing Error */
if (result != DSR_ISINNOCENT) {
if (ATX->classification != DSR_NONE) {
deliver = 0;
LOG (LOG_WARNING,
"process_message returned error %d. dropping message.", result);
} else if (ATX->classification == DSR_NONE) {
LOG (LOG_WARNING,
"process_message returned error %d. delivering.", result);
}
}
/* Deliver */
if (deliver && ATX->flags & DAF_DELIVER_INNOCENT) {
LOGDEBUG ("delivering message");
retcode = deliver_message
(ATX, parse_message->data,
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
node_nt->ptr, fout, DSR_ISINNOCENT);
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
ATX->sockfd_output = 1;
if (retcode) {
presult->exitcode = ERC_DELIVERY;
if (retcode == EINVAL)
presult->exitcode = ERC_PERMANENT_DELIVERY;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
if (result == DSR_ISINNOCENT &&
_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
ATX->learned)
{
ATX->classification = result;
ATX->source = DSS_ERROR;
ATX->flags |= DAF_UNLEARN;
process_message (ATX, parse_message, username, NULL);
}
}
}
}
/* Spam */
else
{
/* Do not Deliver Spam */
if (! (ATX->flags & DAF_DELIVER_SPAM))
{
retcode = 0;
/* If a specific quarantine has been configured, use it */
if (ATX->source != DSS_CORPUS) {
if (ATX->spam_args[0] != 0 ||
(ATX->PTX != NULL &&
( !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag") ||
!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "deliver") )
)
)
{
if (ATX->classification == DSR_NONE) {
if (ATX->spam_args[0] != 0) {
retcode = deliver_message
(ATX, parse_message->data,
(ATX->flags & DAF_STDOUT) ? NULL : ATX->spam_args,
node_nt->ptr, fout, DSR_ISSPAM);
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
ATX->sockfd_output = 1;
} else {
retcode = deliver_message
(ATX, parse_message->data,
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
node_nt->ptr, fout, DSR_ISSPAM);
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
ATX->sockfd_output = 1;
}
if (retcode)
presult->exitcode = ERC_DELIVERY;
if (retcode == EINVAL)
presult->exitcode = ERC_PERMANENT_DELIVERY;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
}
}
else
{
/* Use standard quarantine procedure */
if (ATX->source == DSS_INOCULATION ||
ATX->classification == DSR_NONE)
{
if (ATX->flags & DAF_SUMMARY) {
retcode = 0;
} else {
if (ATX->managed_group[0] == 0)
retcode =
quarantine_message (ATX, parse_message->data, username);
else
retcode =
quarantine_message (ATX, parse_message->data,
ATX->managed_group);
}
}
}
if (retcode) {
presult->exitcode = ERC_DELIVERY;
if (retcode == EINVAL)
presult->exitcode = ERC_PERMANENT_DELIVERY;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
/* Unlearn the message on a local delivery failure */
if (_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
ATX->learned) {
ATX->classification = result;
ATX->source = DSS_ERROR;
ATX->flags |= DAF_UNLEARN;
process_message (ATX, parse_message, username, NULL);
}
}
}
}
/* Deliver Spam */
else
{
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
ATX->sockfd_output = 1;
retcode = deliver_message
(ATX, parse_message->data,
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
node_nt->ptr, fout, DSR_ISSPAM);
if (retcode) {
presult->exitcode = ERC_DELIVERY;
if (retcode == EINVAL)
presult->exitcode = ERC_PERMANENT_DELIVERY;
strlcpy(presult->text, ATX->status, sizeof(presult->text));
if (_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
ATX->learned) {
ATX->classification = result;
ATX->source = DSS_ERROR;
ATX->flags |= DAF_UNLEARN;
process_message (ATX, parse_message, username, NULL);
}
}
}
}
}
#ifdef CLAMAV
RSET:
#endif
_ds_pref_free(ATX->PTX);
free(ATX->PTX);
ATX->PTX = NULL;
node_nt = c_nt_next (ATX->users, &c_nt);
if (ATX->results) {
nt_add(ATX->results, presult);
if (ATX->results->nodetype == NT_CHAR)
free(presult);
} else
free(presult);
presult = NULL;
LOGDEBUG ("DSPAM Instance Shutdown. Exit Code: %d", return_code);
buffer_destroy(parse_message);
}
if (presult) free(presult);
return return_code;
}
// break
// load_agg
// continue
// return
/*
* find_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX)
*
* DESCRIPTION
* Find and parse DSPAM signature
*
* INPUT ARGUMENTS
* CTX DSPAM context containing message and parameters
* ATX Agent context defining processing behavior
*
* RETURN VALUES
* returns 1 (and sets CTX->signature) if found
*
*/
int find_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
struct nt_node *node_nt;
struct nt_c c, c2;
ds_message_part_t block = NULL;
char first_boundary[512];
int is_signed = 0, i = 0;
char *signature_begin = NULL, *signature_end, *erase_begin;
int signature_length, have_signature = 0;
struct nt_node *node_header;
first_boundary[0] = 0;
if (ATX->signature[0] != 0)
return 1;
/* Iterate through each message component in search of a signature
* and decode components as necessary
*/
node_nt = c_nt_first (CTX->message->components, &c);
while (node_nt != NULL)
{
block = (ds_message_part_t) node_nt->ptr;
if (block->media_type == MT_MULTIPART && block->media_subtype == MST_SIGNED)
is_signed = 1;
if (!strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers"))
is_signed = 2;
#ifdef VERBOSE
LOGDEBUG ("scanning component %d for a DSPAM signature", i);
#endif
if (block->media_type == MT_TEXT
|| block->media_type == MT_MESSAGE
|| block->media_type == MT_UNKNOWN
|| (!i && block->media_type == MT_MULTIPART))
{
char *body;
/* Verbose output of each message component */
#ifdef VERBOSE
if (DO_DEBUG) {
if (block->boundary != NULL)
{
LOGDEBUG (" : Boundary : %s", block->boundary);
}
if (block->terminating_boundary != NULL)
LOGDEBUG (" : Term Boundary: %s", block->terminating_boundary);
LOGDEBUG (" : Encoding : %d", block->encoding);
LOGDEBUG (" : Media Type : %d", block->media_type);
LOGDEBUG (" : Media Subtype: %d", block->media_subtype);
LOGDEBUG (" : Headers:");
node_header = c_nt_first (block->headers, &c2);
while (node_header != NULL)
{
ds_header_t header =
(ds_header_t) node_header->ptr;
LOGDEBUG (" %-32s %s", header->heading, header->data);
node_header = c_nt_next (block->headers, &c2);
}
}
#endif
body = block->body->data;
if (block->encoding == EN_BASE64
|| block->encoding == EN_QUOTED_PRINTABLE)
{
if (block->content_disposition != PCD_ATTACHMENT)
{
#ifdef VERBOSE
LOGDEBUG ("decoding message block from encoding type %d",
block->encoding);
#endif
body = _ds_decode_block (block);
if (is_signed)
{
LOGDEBUG
("message is signed. retaining original text for reassembly");
block->original_signed_body = block->body;
}
else
{
block->encoding = EN_8BIT;
node_header = c_nt_first (block->headers, &c2);
while (node_header != NULL)
{
ds_header_t header =
(ds_header_t) node_header->ptr;
if (!strcasecmp
(header->heading, "Content-Transfer-Encoding"))
{
free (header->data);
header->data = strdup ("8bit");
}
node_header = c_nt_next (block->headers, &c2);
}
buffer_destroy (block->body);
}
block->body = buffer_create (body);
free (body);
body = block->body->data;
}
}
if (!strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers")) {
if (block->headers != NULL && !have_signature)
{
struct nt_node *node_header;
ds_header_t head;
node_header = block->headers->first;
while(node_header != NULL) {
head = (ds_header_t) node_header->ptr;
if (head->heading &&
!strcasecmp(head->heading, "X-DSPAM-Signature")) {
if (!strncmp(head->data, SIGNATURE_BEGIN,
strlen(SIGNATURE_BEGIN)))
{
body = head->data;
}
else
{
strlcpy(ATX->signature, head->data, sizeof(ATX->signature));
have_signature = 1;
}
break;
}
node_header = node_header->next;
}
}
}
if (!ATX->train_pristine &&
/* Don't keep searching if we've already found the signature in the
* headers, and we're using signatureLocation=headers
*/
(!have_signature ||
strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers")))
{
/* Look for signature */
if (body != NULL)
{
int tight = 1;
signature_begin = strstr (body, SIGNATURE_BEGIN);
if (signature_begin == NULL) {
signature_begin = strstr (body, LOOSE_SIGNATURE_BEGIN);
tight = 0;
}
if (signature_begin)
{
erase_begin = signature_begin;
if (tight)
signature_begin += strlen(SIGNATURE_BEGIN);
else {
char *loose = strstr (signature_begin, SIGNATURE_DELIMITER);
if (!loose) {
LOGDEBUG("found loose signature begin, but no delimiter");
goto NEXT;
}
signature_begin = loose + strlen(SIGNATURE_DELIMITER);
}
signature_end = signature_begin;
/* Find the signature's end character */
while (signature_end != NULL
&& signature_end[0] != 0
&& (isalnum ((int) signature_end[0]) || signature_end[0] == 32 ||
signature_end[0] == ','))
{
signature_end++;
}
if (signature_end != NULL)
{
signature_length = signature_end - signature_begin;
if (signature_length < 128)
{
memcpy (ATX->signature, signature_begin, signature_length);
ATX->signature[signature_length] = 0;
while(isspace( (int) ATX->signature[0]))
{
memmove(ATX->signature, ATX->signature+1, strlen(ATX->signature));
}
if (strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"),
"headers")) {
if (!is_signed && ATX->classification == DSR_NONE) {
memmove(erase_begin, signature_end+1, strlen(signature_end+1)+1);
block->body->used = (long) strlen(body);
}
}
have_signature = 1;
LOGDEBUG ("found signature '%s'", ATX->signature);
}
}
}
}
} /* TrainPristine */
}
NEXT:
node_nt = c_nt_next (CTX->message->components, &c);
i++;
}
CTX->message->protect = is_signed;
return have_signature;
}
/*
* ctx_init(AGENT_CTX *ATX, const char *username)
*
* DESCRIPTION
* Initialize a DSPAM context from an agent context
*
* INPUT ARGUMENTS
* ATX Agent context defining processing behavior
* username Destination user
*
* RETURN VALUES
* pointer to newly allocated DSPAM context, NULL on failure
*
*/
DSPAM_CTX *ctx_init(AGENT_CTX *ATX, const char *username) {
/* We NEED a username. Without it we can't do much */
if (username == NULL) {
LOG (LOG_CRIT, ERR_AGENT_USER_UNDEFINED);
return NULL;
}
DSPAM_CTX *CTX;
char filename[MAX_FILENAME_LENGTH];
char ctx_group[128] = { 0 };
int f_all = 0, f_mode = DSM_PROCESS;
FILE *file;
ATX->inoc_users = nt_create (NT_CHAR);
if (ATX->inoc_users == NULL) {
LOG (LOG_CRIT, ERR_MEM_ALLOC);
return NULL;
}
ATX->classify_users = nt_create (NT_CHAR);
if (ATX->classify_users == NULL)
{
nt_destroy(ATX->inoc_users);
LOG (LOG_CRIT, ERR_MEM_ALLOC);
return NULL;
}
/* Set Group Membership */
if (ATX->operating_mode == DSM_CLASSIFY) {
LOGDEBUG ("Group support disabled in classify mode");
} else if (!strcmp(_ds_pref_val(ATX->PTX, "ignoreGroups"), "on")) {
LOGDEBUG ("Ignoring groups due preference ignoreGroups on");
} else if (ATX->operating_mode == DSM_PROCESS) {
snprintf (filename, sizeof (filename), "%s",
_ds_read_attribute(agent_config, "GroupConfig"));
file = fopen (filename, "r");
if (file != NULL)
{
int is_group_member_inoculation = 0;
int is_group_member_classification = 0;
int is_group_member_global = 0;
int is_group_member_shared = 0;
int is_group_member_merged = 0;
char *group;
char buffer[10240];
while (fgets (buffer, sizeof (buffer), file) != NULL)
{
int do_inocgroups = 0;
int do_classgroups = 0;
char *type, *list, *listentry;
chomp (buffer);
if (buffer[0] == 0 || buffer[0] == '#' || buffer[0] == ';')
continue;
list = strdup (buffer);
listentry = strdup (buffer);
group = strtok (buffer, ":");
if (group != NULL)
{
type = strtok (NULL, ":");
if (!type)
continue;
/* Check if user is member of inoculation group */
if (strcasecmp (type, "INOCULATION") == 0 &&
ATX->classification == DSR_ISSPAM &&
ATX->source != DSS_CORPUS)
{
if (is_group_member_shared == 1) {
LOGDEBUG ("skipping innoculation group %s: user %s is already in a shared group", group, username);
/* Process next entry in group file */
continue;
} else {
char *l = list, *u;
strsep (&l, ":");
strsep (&l, ":");
u = strsep (&l, ",");
while (u != NULL) {
if (strcasecmp(u,username) == 0) {
LOGDEBUG ("user %s is member of inoculation group %s", username, group);
is_group_member_inoculation = 1;
do_inocgroups = 1;
break;
}
u = strsep (&l, ",");
}
}
}
/* Check if user is member of classification group */
else if (strcasecmp (type, "CLASSIFICATION") == 0)
{
if (is_group_member_shared == 1) {
LOGDEBUG ("skipping classification or global group %s: user %s is already in a shared group", group, username);
/* Process next entry in group file */
continue;
} else {
char *l = list, *u;
strsep (&l, ":");
strsep (&l, ":");
u = strsep (&l, ",");
while (u != NULL) {
if (u[0] == '*' && strcmp(u,"*") != 0) {
if (is_group_member_classification == 1) {
LOGDEBUG ("skipping global group %s: user %s is already in a classification group", group, username);
break;
}
LOGDEBUG ("user %s is member of global group %s", username, group);
is_group_member_global = 1;
do_classgroups = 1;
break;
} else if (strcasecmp(u,username) == 0) {
if (is_group_member_global == 1) {
LOGDEBUG ("skipping classification group %s: user %s is already in a global group", group, username);
break;
}
LOGDEBUG ("user %s is member of classification group %s", username, group);
is_group_member_classification = 1;
do_classgroups = 1;
break;
}
u = strsep (&l, ",");
}
}
}
/* Process shared and shared,managed group */
else if (strncasecmp (type, "SHARED", 6) == 0)
{
if (is_group_member_shared == 1) {
LOGDEBUG ("skipping shared group %s: user %s is already in a shared group", group, username);
/* Process next entry in group file */
continue;
} else if (is_group_member_merged == 1) {
LOGDEBUG ("skipping shared group %s: user %s is already in a merged group", group, username);
/* Process next entry in group file */
continue;
} else if (is_group_member_inoculation == 1) {
LOGDEBUG ("skipping shared group %s: user %s is already in a inoculation group", group, username);
/* Process next entry in group file */
continue;
} else if (is_group_member_classification == 1) {
LOGDEBUG ("skipping shared group %s: user %s is already in a classification group", group, username);
/* Process next entry in group file */
continue;
} else if (is_group_member_global == 1) {
LOGDEBUG ("skipping shared group %s: user %s is already in a global group", group, username);
/* Process next entry in group file */
continue;
} else {
char *l = list, *u;
strsep (&l, ":");
strsep (&l, ":");
u = strsep (&l, ",");
while (u != NULL) {
if (strcasecmp(u,username) == 0 ||
strcmp(u,"*") == 0 ||
(strncmp(u,"*@",2) == 0 && strchr(username,'@') != NULL && strcasecmp(u+1,strchr(username,'@')) == 0))
{
LOGDEBUG ("assigning user %s to shared group %s", username, group);
strlcpy (ctx_group, group, sizeof (ctx_group));
if (strncasecmp (type + 6, ",MANAGED", 8) == 0) {
LOGDEBUG ("shared group is managed by %s", group);
strlcpy (ATX->managed_group, ctx_group, sizeof(ATX->managed_group));
}
is_group_member_shared = 1;
break;
}
u = strsep (&l, ",");
}
}
/* Process next entry in group file */
continue;
}
/* Process merged group */
else if (strcasecmp (type, "MERGED") == 0 && strcasecmp(group, username) != 0)
{
if (is_group_member_merged == 1) {
LOGDEBUG ("skipping merged group %s: user %s is already in merged group %s", group, username, ctx_group);
/* Process next entry in group file */
continue;
} else if (is_group_member_shared == 1) {
LOGDEBUG ("skipping merged group %s: user %s is already in a shared group", group, username);
/* Process next entry in group file */
continue;
} else if (ATX->flags & DAF_MERGED) {
LOGDEBUG ("BUG in DSPAM. Please report this bug:");
LOGDEBUG (" --> Skipping merged group %s: user %s is already in merged group %s", group, username, ctx_group);
/* Process next entry in group file */
continue;
} else {
char *l = list, *u;
strsep (&l, ":");
strsep (&l, ":");
u = strsep (&l, ",");
while (u != NULL) {
if (strcasecmp(u,username) == 0 || strcmp(u,"*") == 0 ||
(strncmp(u,"*@",2) == 0 && strchr(username,'@') != NULL && strcasecmp(u+1,strchr(username,'@')) == 0))
{
if (is_group_member_merged == 1) {
LOGDEBUG ("skipping entry %s for merged group %s. User is already in merged group.", u, group);
continue;
} else {
LOGDEBUG ("adding user to merged group %s", group);
ATX->flags |= DAF_MERGED;
strlcpy(ctx_group, group, sizeof(ctx_group));
is_group_member_merged = 1;
}
} else if ((strncmp(u,"-",1) == 0 && strcasecmp(u+1,username) == 0) ||
(strncmp(u,"-*@",3) == 0 && strchr(username,'@') != NULL && strcasecmp(u+2,strchr(username,'@')) == 0))
{
if (is_group_member_merged == 0) {
LOGDEBUG ("skipping entry %s for merged group %s. User is already not in merged group.", u, group);
continue;
} else {
LOGDEBUG ("removing user from merged group %s", group);
ATX->flags ^= DAF_MERGED;
ctx_group[0] = 0;
is_group_member_merged = 0;
}
} else {
LOGDEBUG ("unhandled entry %s in merged group %s", u, group);
}
u = strsep (&l, ",");
}
}
/* Process next entry in group file */
continue;
}
/*
* If we are reporting a spam, report it as a spam to all other
* users in the inoculation group
*/
if (do_inocgroups)
{
char *l = listentry, *u;
strsep (&l, ":");
strsep (&l, ":");
u = strsep (&l, ",");
while (u != NULL) {
if (strcasecmp(u,username) != 0) {
LOGDEBUG ("adding user %s as target for inoculation", u);
nt_add (ATX->inoc_users, u);
}
u = strsep (&l, ",");
}
}
/*
* When user is member of a global group or classification network
* then consult all other users in the group
*/
else if (do_classgroups)
{
char *l = listentry, *u;
strsep (&l, ":");
strsep (&l, ":");
u = strsep (&l, ",");
while (u != NULL) {
if (u[0] == '*' && strcmp(u,"*") != 0) {
/* global classification group */
if (is_group_member_classification == 1) {
LOGDEBUG ("skipping global group (%s) entry %s: user %s is already in a classification group", group, u, username);
continue;
} else if (strcasecmp(u+1,username) != 0 && is_group_member_global == 1) {
LOGDEBUG ("adding %s as classification peer for %s", u+1, username);
ATX->flags |= DAF_GLOBAL;
nt_add (ATX->classify_users, u+1);
} else {
LOGDEBUG ("skipping global group entry %s for user %s", u+1, username);
}
} else if (strcasecmp(u,username) != 0) {
/* classification network group */
if (is_group_member_global == 1) {
LOGDEBUG ("skipping classification group (%s) entry %s: user %s is already in a global group", group, u, username);
continue;
} else if (is_group_member_classification == 1) {
LOGDEBUG ("adding user %s to classification network group %", u, group);
nt_add (ATX->classify_users, u);
} else {
LOGDEBUG ("skipping classification group entry %s for user %s", u, username);
}
}
u = strsep (&l, ",");
}
}
}
free (list);
free (listentry);
}
fclose (file);
}
}
/* Crunch our agent context into a DSPAM context */
f_mode = ATX->operating_mode;
f_all = DSF_SIGNATURE;
if (ATX->flags & DAF_UNLEARN)
f_all |= DSF_UNLEARN;
/* If there is no preference, defer to commandline */
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "enableBNR"), "")) {
if (!strcmp(_ds_pref_val(ATX->PTX, "enableBNR"), "on"))
f_all |= DSF_NOISE;
} else {
if (ATX->flags & DAF_NOISE)
f_all |= DSF_NOISE;
}
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "")) {
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
f_all |= DSF_BIAS;
} else {
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
f_all |= DSF_BIAS;
}
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "enableWhitelist"), "")) {
if (!strcmp(_ds_pref_val(ATX->PTX, "enableWhitelist"), "on"))
f_all |= DSF_WHITELIST;
} else {
if (ATX->flags & DAF_WHITELIST)
f_all |= DSF_WHITELIST;
}
if (ATX->flags & DAF_MERGED)
f_all |= DSF_MERGED;
CTX = dspam_create (username,
ctx_group,
_ds_read_attribute(agent_config, "Home"),
f_mode,
f_all);
if (CTX == NULL)
return NULL;
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "statisticalSedation"), "")) {
CTX->training_buffer = atoi(_ds_pref_val(ATX->PTX, "statisticalSedation"));
LOGDEBUG("sedation level set to: %d", CTX->training_buffer);
} else if (ATX->training_buffer>=0) {
CTX->training_buffer = ATX->training_buffer;
LOGDEBUG("sedation level set to: %d", CTX->training_buffer);
}
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "whitelistThreshold"), ""))
CTX->wh_threshold = atoi(_ds_pref_val(ATX->PTX, "whitelistThreshold"));
if (ATX->classification != DSR_NONE) {
CTX->classification = ATX->classification;
CTX->source = ATX->source;
}
if (!( ATX->flags & DAF_FIXED_TR_MODE)
&& ATX->PTX != NULL
&& strcmp(_ds_pref_val(ATX->PTX, "trainingMode"), "")) {
if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TEFT"))
CTX->training_mode = DST_TEFT;
else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TOE"))
CTX->training_mode = DST_TOE;
else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TUM"))
CTX->training_mode = DST_TUM;
else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "NOTRAIN"))
CTX->training_mode = DST_NOTRAIN;
else
CTX->training_mode = ATX->training_mode;
} else {
CTX->training_mode = ATX->training_mode;
}
return CTX;
}
/*
* retrain_message(DSPAM_CTX *CTX, AGENT_CTX *ATX)
*
* DESCRIPTION
* Retrain a message and perform iterative training
*
* INPUT ARGUMENTS
* CTX DSPAM context containing the classification results
* ATX Agent context defining processing behavior
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int retrain_message(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
int do_train = 1, iter = 0, ck_result = 0, t_mode = CTX->source;
/* Train until test conditions are met, 5 iterations max */
if (!_ds_match_attribute(agent_config, "TestConditionalTraining", "on")) {
ck_result = dspam_process (CTX, NULL);
if (ck_result != 0)
return EFAILURE;
} else {
while (do_train && iter < 5)
{
DSPAM_CTX *CLX;
int match;
match = (CTX->classification == DSR_ISSPAM) ?
DSR_ISSPAM : DSR_ISINNOCENT;
iter++;
ck_result = dspam_process (CTX, NULL);
if (ck_result != 0)
return EFAILURE;
/* Only subtract innocent values once */
CTX->source = DSS_CORPUS;
LOGDEBUG ("reclassifying iteration %d result: %d", iter, ck_result);
if (t_mode == DSS_CORPUS)
do_train = 0;
/* Only attempt test-conditional training on a mature corpus */
if (CTX->totals.innocent_learned+CTX->totals.innocent_classified<1000 &&
CTX->classification == DSR_ISSPAM)
{
do_train = 0;
}
else
{
int f_all = DSF_SIGNATURE;
/* CLX = Classify Context */
if (ATX->flags & DAF_NOISE)
f_all |= DSF_NOISE;
if (ATX->PTX != NULL &&
strcmp(_ds_pref_val(ATX->PTX, "processorBias"), ""))
{
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
f_all |= DSF_BIAS;
} else {
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
f_all |= DSF_BIAS;
}
CLX = dspam_create (CTX->username,
CTX->group,
_ds_read_attribute(agent_config, "Home"),
DSM_CLASSIFY,
f_all);
if (!CLX)
break;
CLX->training_mode = CTX->training_mode;
set_libdspam_attributes(CLX);
if (attach_context(CLX, ATX->dbh)) {
dspam_destroy(CLX);
break;
}
CLX->signature = &ATX->SIG;
ck_result = dspam_process (CLX, NULL);
if (ck_result < 0) {
CLX->signature = NULL;
dspam_destroy(CLX);
return EFAILURE;
}
if (ck_result == 0 || CLX->result == match)
do_train = 0;
CLX->signature = NULL;
dspam_destroy (CLX);
}
}
CTX->source = DSS_ERROR;
}
return 0;
}
/*
* ensure_confident_result(DSPAM_CTX *CTX, AGENT_CTX *ATX, int result)
*
* DESCRIPTION
* Consult a global group or classification network if
* the user's filter instance isn't confident in its result
*
* INPUT ARGUMENTS
* CTX DSPAM context containing classification results
* ATX Agent context defining processing behavior
* result DSR_ processing result
*
* RETURN VALUES
* returns result networks believe the message should be
*/
/* ensure_confident_result: consult global group or
clasification network if the user isn't confident in their result */
int ensure_confident_result(DSPAM_CTX *CTX, AGENT_CTX *ATX, int result) {
int was_spam = 0;
/* Exit if no users available for global group or classification network */
if (ATX->classify_users && ATX->classify_users->items == 0)
return result;
/* global groups or classification network only operates on SPAM or INNOCENT */
if (strcmp(CTX->class, LANG_CLASS_WHITELISTED) ==0 ||
strcmp(CTX->class, LANG_CLASS_VIRUS) == 0 ||
strcmp(CTX->class, LANG_CLASS_BLOCKLISTED) == 0 ||
strcmp(CTX->class, LANG_CLASS_BLACKLISTED) == 0)
{
LOGDEBUG ("Not consulting %s group: message class is %s",
(ATX->flags & DAF_GLOBAL) ? "global" : "classification",
CTX->class);
return result;
}
/* Defer to global group */
if (ATX->flags & DAF_GLOBAL &&
((CTX->totals.innocent_learned + CTX->totals.innocent_corpusfed < 1000 ||
CTX->totals.spam_learned + CTX->totals.spam_corpusfed < 250) ||
(CTX->training_mode == DST_NOTRAIN))
)
{
if (result == DSR_ISSPAM) {
was_spam = 1;
CTX->result = DSR_ISINNOCENT;
result = DSR_ISINNOCENT;
}
CTX->confidence = 0.60f;
}
if (result != DSR_ISSPAM &&
CTX->operating_mode == DSM_PROCESS &&
CTX->classification == DSR_NONE &&
CTX->confidence < 0.65)
{
LOGDEBUG ("consulting %s group member list", (ATX->flags & DAF_GLOBAL) ? "global" : "classification");
struct nt_node *node_int;
struct nt_c c_i;
node_int = c_nt_first (ATX->classify_users, &c_i);
while (node_int != NULL && result != DSR_ISSPAM) {
LOGDEBUG ("checking result for user %s", (const char *) node_int->ptr);
result = user_classify (ATX, (const char *) node_int->ptr, CTX->signature, NULL);
if (result == DSR_ISSPAM) {
LOGDEBUG ("CLASSIFY CATCH: %s", (const char *) node_int->ptr);
CTX->result = result;
}
node_int = c_nt_next (ATX->classify_users, &c_i);
}
/* If the global user thinks it's spam, and the user thought it was
* innocent, retrain the user as a false negative.
*/
if (result == DSR_ISSPAM && !was_spam) {
LOGDEBUG ("re-adding as %s", LANG_CLASS_SPAM);
DSPAM_CTX *CTC = malloc(sizeof(DSPAM_CTX));
if (CTC == NULL) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return EUNKNOWN;
}
memcpy(CTC, CTX, sizeof(DSPAM_CTX));
CTC->operating_mode = DSM_PROCESS;
CTC->classification = DSR_ISSPAM;
CTC->source = DSS_ERROR;
CTC->flags |= DSF_SIGNATURE;
dspam_process (CTC, NULL);
memcpy(&CTX->totals, &CTC->totals, sizeof(struct _ds_spam_totals));
free(CTC);
CTC = NULL;
CTX->totals.spam_misclassified--;
strncpy(CTX->class, LANG_CLASS_SPAM, sizeof(CTX->class));
/* should we be resetting CTX->probability and CTX->confidence here as well? */
CTX->result = result;
/* If the global user thinks it's innocent, and the user thought it was
* spam, retrain the user as a false positive
*/
} else if (result == DSR_ISINNOCENT && was_spam) {
LOGDEBUG ("re-adding as %s", LANG_CLASS_INNOCENT);
DSPAM_CTX *CTC = malloc(sizeof(DSPAM_CTX));
if (CTC == NULL) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return EUNKNOWN;
}
memcpy(CTC, CTX, sizeof(DSPAM_CTX));
CTC->operating_mode = DSM_PROCESS;
CTC->classification = DSR_ISINNOCENT;
CTC->source = DSS_ERROR;
CTC->flags |= DSF_SIGNATURE;
dspam_process (CTC, NULL);
memcpy(&CTX->totals, &CTC->totals, sizeof(struct _ds_spam_totals));
free(CTC);
CTC = NULL;
CTX->totals.innocent_misclassified--;
strncpy(CTX->class, LANG_CLASS_INNOCENT, sizeof(CTX->class));
/* should we be resetting CTX->probability and CTX->confidence here as well? */
CTX->result = result;
}
}
return result;
}
/*
* log_prepare(char *buffer, char *value)
*
* DESCRIPTION
* Prepares a value for logging by copying it to the buffer and removing
* all potentially dangerous characters.
*
* INPUT ARGUMENTS
* buffer A 256-byte buffer to store the result into
* value Value to be logged or NULL
*
* RETURN VALUES
* None
*/
static void log_prepare(char *buffer, char *value) {
char *p;
if (!value)
value = "";
strncpy(buffer, value, 255);
buffer[255] = 0;
for (p=buffer; *p; p++)
if (*p >= 0 && *p < 32)
*p = ' ';
}
/*
* log_events(DSPAM_CTX *CTX, AGENT_CTX *ATX)
*
* DESCRIPTION
* Log events to system and user logs
*
* INPUT ARGUMENTS
* CTX DSPAM context
* ATX Agent context defining processing behavior
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int log_events(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
char filename[MAX_FILENAME_LENGTH];
char *subject = NULL, *from = NULL;
struct nt_node *node_nt;
struct nt_c c_nt;
FILE *file;
char class;
char x[1024], subject_buf[256], from_buf[256];
char *messageid = NULL;
if (CTX->message)
messageid = _ds_find_header(CTX->message, "Message-Id");
if (ATX->status[0] == 0 && CTX->source == DSS_ERROR &&
(!(ATX->flags & DAF_UNLEARN)))
{
STATUS("Retrained");
}
if (ATX->status[0] == 0 && CTX->classification == DSR_NONE
&& CTX->result == DSR_ISSPAM
&& ATX->status[0] == 0)
{
if (_ds_pref_val(ATX->PTX, "spamAction")[0] == 0 ||
!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "quarantine"))
{
STATUS("Quarantined");
} else if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag")) {
STATUS("Tagged");
} else if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "deliver")) {
STATUS("Delivered");
}
}
if (ATX->status[0] == 0 &&
CTX->classification == DSR_NONE &&
CTX->result == DSR_ISINNOCENT)
{
STATUS("Delivered");
}
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"), LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group : CTX->username), "log");
if (CTX->message)
{
node_nt = c_nt_first (CTX->message->components, &c_nt);
if (node_nt != NULL)
{
ds_message_part_t block;
block = node_nt->ptr;
if (block->headers != NULL)
{
ds_header_t head;
struct nt_node *node_header;
node_header = block->headers->first;
while(node_header != NULL) {
head = (ds_header_t) node_header->ptr;
if (head) {
if (!strcasecmp(head->heading, "Subject")) {
subject = head->data;
if (from != NULL) break;
} else if (!strcasecmp(head->heading, "From")) {
from = head->data;
if (subject != NULL) break;
}
}
node_header = node_header->next;
}
}
}
}
if (!strcmp(CTX->class, LANG_CLASS_WHITELISTED))
class = 'W';
else if (!strcmp(CTX->class, LANG_CLASS_VIRUS))
class = 'V';
else if (!strcmp(CTX->class, LANG_CLASS_BLACKLISTED))
class = 'A';
else if (!strcmp(CTX->class, LANG_CLASS_BLOCKLISTED))
class = 'O';
else if (CTX->result == DSR_ISSPAM)
class = 'S';
else if (CTX->result == DSR_ISINNOCENT)
class = 'I';
else
class = 'U';
if (CTX->source == DSS_ERROR) {
if (CTX->classification == DSR_ISSPAM)
class = 'M';
else if (CTX->classification == DSR_ISINNOCENT)
class = 'F';
} else if (CTX->source == DSS_INOCULATION)
class = 'N';
else if (CTX->source == DSS_CORPUS)
class = 'C';
if (ATX->flags & DAF_UNLEARN) {
char stat[256];
snprintf(stat, sizeof(stat), "Delivery Failed (%s)",
(ATX->status[0]) ? ATX->status : "No error provided");
STATUS("%s", stat);
class = 'E';
}
log_prepare(from_buf, from);
log_prepare(subject_buf, subject);
/* Write USER.log */
if (_ds_match_attribute(agent_config, "UserLog", "on")) {
snprintf(x, sizeof(x), "%ld\t%c\t%s\t%s\t%s\t%s\t%s\n",
(long) time(NULL),
class,
from_buf,
ATX->signature,
subject_buf,
ATX->status,
(messageid) ? messageid : "");
_ds_prepare_path_for(filename);
file = fopen(filename, "a");
if (file != NULL) {
int i = _ds_get_fcntl_lock(fileno(file));
if (!i) {
fputs(x, file);
fputs("\n", file);
_ds_free_fcntl_lock(fileno(file));
} else {
LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
}
fclose(file);
}
}
/* Write system.log */
if (_ds_match_attribute(agent_config, "SystemLog", "on")) {
snprintf(filename, sizeof(filename), "%s/system.log",
_ds_read_attribute(agent_config, "Home"));
file = fopen(filename, "a");
if (file != NULL) {
int i = _ds_get_fcntl_lock(fileno(file));
if (!i) {
snprintf(x, sizeof(x), "%ld\t%c\t%s\t%s\t%s\t%f\t%s\t%s\t%s\n",
(long) time(NULL),
class,
from_buf,
ATX->signature,
subject_buf,
_ds_gettime()-ATX->timestart,
(CTX->username) ? CTX->username: "",
(ATX->status) ? ATX->status : "",
(messageid) ? messageid : "");
fputs(x, file);
_ds_free_fcntl_lock(fileno(file));
} else {
LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
}
fclose(file);
}
}
return 0;
}
/*
* add_xdspam_headers(DSPAM_CTX *CTX, AGENT_CTX *ATX)
*
* DESCRIPTION
* Add X-DSPAM headers to the message being processed
*
* INPUT ARGUMENTS
* CTX DSPAM context containing message and results
* ATX Agent context defining processing behavior
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int add_xdspam_headers(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
struct nt_node *node_nt;
struct nt_c c_nt;
node_nt = c_nt_first (CTX->message->components, &c_nt);
if (node_nt != NULL)
{
ds_message_part_t block = node_nt->ptr;
struct nt_node *node_ft;
struct nt_c c_ft;
if (block != NULL && block->headers != NULL)
{
ds_header_t head;
char data[10240];
char scratch[128];
snprintf(data, sizeof(data), "%s: %s",
(CTX->source == DSS_ERROR) ? "X-DSPAM-Reclassified" : "X-DSPAM-Result",
CTX->class);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
#endif
nt_add (block->headers, (void *) head);
}
else {
LOG (LOG_CRIT, ERR_MEM_ALLOC);
}
if (CTX->source == DSS_NONE) {
char buf[27];
time_t t = time(NULL);
ctime_r(&t, buf);
chomp(buf);
snprintf(data, sizeof(data), "X-DSPAM-Processed: %s", buf);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG("appending header %s: %s", head->heading, head->data);
#endif
nt_add(block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
}
if (CTX->source != DSS_ERROR) {
snprintf(data, sizeof(data), "X-DSPAM-Confidence: %01.4f",
CTX->confidence);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG("appending header %s: %s", head->heading, head->data);
#endif
nt_add(block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
snprintf(data, sizeof(data), "X-DSPAM-Recipient: %s",
ATX->recipient);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG("appending header %s: %s", head->heading, head->data);
#endif
nt_add(block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
if (_ds_match_attribute(agent_config, "ImprobabilityDrive", "on"))
{
float probability = CTX->confidence;
char *as;
if (probability > 0.999999)
probability = 0.999999;
if (CTX->result == DSR_ISINNOCENT) {
as = "spam";
} else {
as = "ham";
}
snprintf(data, sizeof(data), "X-DSPAM-Improbability: 1 in %.0f "
"chance of being %s",
1.0+(100*(probability / (1-probability))), as);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG("appending header %s: %s", head->heading, head->data);
#endif
nt_add(block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
}
snprintf(data, sizeof(data), "X-DSPAM-Probability: %01.4f",
CTX->probability);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
#endif
nt_add (block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
if (CTX->training_mode != DST_NOTRAIN && ATX->signature[0] != 0) {
snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
head = _ds_create_header_field(data);
if (head != NULL)
{
if (strlen(ATX->signature)<5)
{
LOGDEBUG("WARNING: Signature not generated, or invalid");
}
#ifdef VERBOSE
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
#endif
nt_add (block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
}
if (CTX->result == DSR_ISSPAM && (ATX->managed_group[0] || (_ds_pref_val(ATX->PTX, "localStore")[0])))
{
snprintf(data, sizeof(data), "X-DSPAM-User: %s", CTX->username);
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
#endif
nt_add (block->headers, (void *) head);
}
else
LOG (LOG_CRIT, ERR_MEM_ALLOC);
}
if (!strcmp(_ds_pref_val(ATX->PTX, "showFactors"), "on")) {
if (CTX->factors != NULL) {
snprintf(data, sizeof(data), "X-DSPAM-Factors: %d",
CTX->factors->items);
node_ft = c_nt_first(CTX->factors, &c_ft);
while(node_ft != NULL) {
struct dspam_factor *f = (struct dspam_factor *) node_ft->ptr;
if (f) {
char *s, *t;
strlcat(data, ",\n\t", sizeof(data));
s = f->token_name;
t = scratch;
while (*s && t < scratch + sizeof(scratch) - 16)
if (*s >= ' ' && *s < 0x7f && *s != '%')
*t++ = *s++;
else
t += sprintf(t, "%%%02x", (unsigned char) *s++);
snprintf(t, 15, ", %2.5f", f->value);
strlcat(data, scratch, sizeof(data));
}
node_ft = c_nt_next(CTX->factors, &c_ft);
}
head = _ds_create_header_field(data);
if (head != NULL)
{
#ifdef VERBOSE
LOGDEBUG("appending header %s: %s", head->heading, head->data);
#endif
nt_add(block->headers, (void *) head);
}
}
}
} /* CTX->source != DSS_ERROR */
}
}
return 0;
}
/*
* embed_msgtag(DSPAM_CTX *CTX, AGENT_CTX *ATX)
*
* DESCRIPTION
* Embed a message tag
*
* INPUT ARGUMENTS
* CTX DSPAM context containing the message
* ATX Agent context defining processing behavior
*
* RETURN VALUES
* returns 0 on success, standard errors on failure
*/
int embed_msgtag(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
struct nt_node *node_nt;
struct nt_c c_nt;
char toplevel_boundary[128] = { 0 };
ds_message_part_t block;
int i = 0;
FILE *f;
char buff[1024], msgfile[MAX_FILENAME_LENGTH];
buffer *b;
ATX = ATX; /* Keep compiler happy */
if (CTX->result != DSR_ISSPAM && CTX->result != DSR_ISINNOCENT)
return EINVAL;
node_nt = c_nt_first (CTX->message->components, &c_nt);
if (node_nt == NULL || node_nt->ptr == NULL)
return EFAILURE;
block = node_nt->ptr;
/* Signed messages cannot be tagged */
if (block->media_subtype == MST_SIGNED)
return EINVAL;
/* Load the message tag */
if (_ds_read_attribute(agent_config, "TxtDirectory")) {
snprintf(msgfile, sizeof(msgfile), "%s/msgtag.%s",
_ds_read_attribute(agent_config, "TxtDirectory"),
(CTX->result == DSR_ISSPAM) ? "spam" : "nonspam");
} else {
snprintf(msgfile, sizeof(msgfile), "%s/txt/msgtag.%s",
_ds_read_attribute(agent_config, "Home"),
(CTX->result == DSR_ISSPAM) ? "spam" : "nonspam");
}
f = fopen(msgfile, "r");
if (!f) {
LOG(LOG_ERR, ERR_IO_FILE_OPEN, msgfile, strerror(errno));
return EFILE;
}
b = buffer_create(NULL);
if (!b) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
fclose(f);
return EUNKNOWN;
}
while(fgets(buff, sizeof(buff), f)!=NULL) {
buffer_cat(b, buff);
}
fclose(f);
if (block->media_type == MT_MULTIPART && block->terminating_boundary != NULL)
{
strlcpy(toplevel_boundary, block->terminating_boundary,
sizeof(toplevel_boundary));
}
while (node_nt != NULL)
{
char *body_close = NULL, *dup = NULL;
block = node_nt->ptr;
/* Append signature to blocks when... */
if (block != NULL
/* Either a text section, or this is a non-multipart message AND...*/
&& (block->media_type == MT_TEXT
|| (block->boundary == NULL && i == 0
&& block->media_type != MT_MULTIPART))
&& (toplevel_boundary[0] == 0 || (block->body && block->body->used)))
{
if (block->content_disposition == PCD_ATTACHMENT)
{
node_nt = c_nt_next (CTX->message->components, &c_nt);
i++;
continue;
}
/* Some email clients reformat HTML parts, and require that we include
* the signature before the HTML close tags (because they're stupid)
*/
if (body_close == NULL &&
block->body != NULL &&
block->body->data != NULL &&
block->media_subtype == MST_HTML)
{
body_close = strcasestr(block->body->data, "