1
/* $Id: dspam.c,v 1.219 2006/01/31 16:08:35 jonz Exp $ */
5
COPYRIGHT (C) 2002-2006 DEEP LOGIC INC.
7
This program is free software; you can redistribute it and/or
8
modify it under the terms of the GNU General Public License
9
as published by the Free Software Foundation; version 2
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program; if not, write to the Free Software
19
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
* dspam.c - primary dspam processing agent
27
* The agent provides a commandline interface to the libdspam core engine
28
* and also provides advanced functions such as a daemonized LMTP server,
29
* extended groups, and other agent features outlined in the documentation.
31
* This codebase is the full client/processing engine. See dspamc.c for
32
* the lightweight client-only codebase.
36
#include <auto-config.h>
49
#include <sys/types.h>
53
#include <sys/socket.h>
58
#define WIDEXITED(x) 1
59
#define WEXITSTATUS(x) (x)
63
#include <sys/param.h>
67
#include "read_config.h"
75
#ifdef TIME_WITH_SYS_TIME
76
# include <sys/time.h>
79
# ifdef HAVE_SYS_TIME_H
80
# include <sys/time.h>
87
#include "agent_shared.h"
95
#include "config_api.h"
97
#define USE_LMTP (_ds_read_attribute(agent_config, "DeliveryProto") && !strcmp(_ds_read_attribute(agent_config, "DeliveryProto"), "LMTP"))
98
#define USE_SMTP (_ds_read_attribute(agent_config, "DeliveryProto") && !strcmp(_ds_read_attribute(agent_config, "DeliveryProto"), "SMTP"))
99
#define LOOKUP(A, B) ((_ds_pref_val(A, "localStore")[0]) ? _ds_pref_val(A, "localStore") : B)
102
main (int argc, char *argv[])
104
AGENT_CTX ATX; /* agent configuration */
105
buffer *message = NULL; /* input message */
106
int agent_init = 0; /* agent is initialized */
107
int driver_init = 0; /* storage driver is initialized */
108
int exitcode = EXIT_SUCCESS;
109
struct nt_node *node_nt;
112
srand ((long) time << (long) getpid ());
113
umask (006); /* rw-rw---- */
114
setbuf (stdout, NULL); /* unbuffered output */
120
pthread_mutex_init(&__syslog_lock, NULL);
123
/* Read dspam.conf into global config structure (ds_config_t) */
125
agent_config = read_config(NULL);
127
LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
128
exitcode = EXIT_FAILURE;
132
if (!_ds_read_attribute(agent_config, "Home")) {
133
LOG(LOG_ERR, ERR_AGENT_DSPAM_HOME);
134
exitcode = EXIT_FAILURE;
138
/* Set up an agent context to define the behavior of the processor */
140
if (initialize_atx(&ATX)) {
141
LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
142
exitcode = EXIT_FAILURE;
148
if (process_arguments(&ATX, argc, argv)) {
149
LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
150
exitcode = EXIT_FAILURE;
154
/* Switch into daemon mode if --daemon was specified on the commandline */
157
#ifdef TRUSTED_USER_SECURITY
158
if (ATX.operating_mode == DSM_DAEMON && ATX.trusted)
160
if (ATX.operating_mode == DSM_DAEMON)
166
nt_destroy(ATX.users);
167
nt_destroy(ATX.recipients);
171
_ds_destroy_config(agent_config);
173
pthread_mutex_destroy(&__syslog_lock);
178
if (apply_defaults(&ATX)) {
179
LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
180
exitcode = EXIT_FAILURE;
184
if (check_configuration(&ATX)) {
185
LOG(LOG_ERR, ERR_AGENT_MISCONFIGURED);
186
exitcode = EXIT_FAILURE;
190
/* Read the message in and apply ParseTo services */
192
message = read_stdin(&ATX);
193
if (message == NULL) {
194
exitcode = EXIT_FAILURE;
198
if (ATX.users->items == 0)
200
LOG(LOG_ERR, ERR_AGENT_USER_UNDEFINED);
201
fprintf (stderr, "%s\n", SYNTAX);
202
exitcode = EXIT_FAILURE;
206
/* Perform client-based processing of message if --client was specified */
209
if (ATX.client_mode &&
210
_ds_read_attribute(agent_config, "ClientIdent") &&
211
(_ds_read_attribute(agent_config, "ClientHost") ||
212
_ds_read_attribute(agent_config, "ServerDomainSocketPath")))
214
exitcode = client_process(&ATX, message);
216
LOG(LOG_ERR, ERR_CLIENT_EXIT, exitcode);
221
/* Primary (non-client) processing procedure */
223
libdspam_init(_ds_read_attribute(agent_config, "StorageDriver"));
225
if (dspam_init_driver (NULL))
227
LOG (LOG_WARNING, ERR_DRV_INIT);
228
exitcode = EXIT_FAILURE;
234
ATX.results = nt_create(NT_PTR);
235
if (ATX.results == NULL) {
236
LOG(LOG_CRIT, ERR_MEM_ALLOC);
241
exitcode = process_users(&ATX, message);
243
LOGDEBUG("process_users() failed on error %d", exitcode);
246
node_nt = c_nt_first(ATX.results, &c_nt);
248
agent_result_t result = (agent_result_t) node_nt->ptr;
249
if (result->exitcode)
251
node_nt = c_nt_next(ATX.results, &c_nt);
254
nt_destroy(ATX.results);
263
buffer_destroy(message);
266
nt_destroy(ATX.users);
267
nt_destroy(ATX.recipients);
270
if (!_ds_read_attribute(agent_config, "ClientHost")) {
272
dspam_shutdown_driver(NULL);
277
_ds_destroy_config(agent_config);
280
pthread_mutex_destroy(&__syslog_lock);
286
* process_message(AGENT_CTX *ATX, buffer *message, const char *username)
289
* Core message processing / interface to libdspam
290
* This function should be called once for each destination user
293
* ATX Agent context defining processing behavior
294
* message Buffer structure containing the message
295
* username Destination user
298
* The processing result is returned:
300
* DSR_ISINNOCENT Message is innocent
301
* DSR_ISSPAM Message is spam
302
* (other) Error code (see libdspam.h)
309
const char *username,
310
char **result_string)
312
DSPAM_CTX *CTX = NULL; /* (lib)dspam context */
313
ds_message_t components;
315
int have_signature = 0;
316
int have_decision = 0;
318
int internally_canned = 0;
320
ATX->timestart = _ds_gettime(); /* set tick count to get run time */
322
/* Create a dspam context based on the agent context */
324
CTX = ctx_init(ATX, username);
326
LOG (LOG_WARNING, ERR_CORE_INIT);
331
/* Configure libdspam's storage properties, then attach storage */
333
set_libdspam_attributes(CTX);
334
if (ATX->sockfd && ATX->dbh == NULL)
335
ATX->dbh = _ds_connect(CTX);
337
/* Re-Establish database connection (if failed) */
339
if (attach_context(CTX, ATX->dbh)) {
341
ATX->dbh = _ds_connect(CTX);
343
if (attach_context(CTX, ATX->dbh)) {
344
LOG(LOG_ERR, ERR_CORE_ATTACH);
349
LOG(LOG_ERR, ERR_CORE_ATTACH);
355
if (message->data == NULL) {
356
LOGDEBUG("empty message provided");
360
/* Parse and decode the message into our message structure (ds_message_t) */
362
components = _ds_actualize_message (message->data);
363
if (components == NULL) {
364
LOG (LOG_ERR, ERR_AGENT_PARSER_FAILED);
369
CTX->message = components;
372
/* Check for viruses */
374
if (_ds_read_attribute(agent_config, "ClamAVPort") &&
375
_ds_read_attribute(agent_config, "ClamAVHost") &&
376
CTX->source != DSS_ERROR &&
377
strcmp(_ds_pref_val(ATX->PTX, "optOutClamAV"), "on"))
379
if (has_virus(message)) {
380
CTX->result = DSR_ISSPAM;
381
CTX->probability = 1.0;
382
CTX->confidence = 1.0;
383
STATUS("A virus was detected in the message contents");
385
strcpy(CTX->class, LANG_CLASS_VIRUS);
386
internally_canned = 1;
391
/* Check for a domain blocklist (user-based setting) */
393
if (is_blocklisted(CTX, ATX)) {
394
CTX->result = DSR_ISSPAM;
396
CTX->probability = 1.0;
397
CTX->confidence = 1.0;
398
strcpy(CTX->class, LANG_CLASS_BLOCKLISTED);
399
internally_canned = 1;
402
/* Check for an RBL blacklist (system-based setting) */
404
if (CTX->classification == DSR_NONE &&
405
_ds_read_attribute(agent_config, "Lookup"))
407
int bad = is_blacklisted(CTX, ATX);
409
if (_ds_match_attribute(agent_config, "RBLInoculate", "on")) {
410
LOGDEBUG("source address is blacklisted. learning as spam.");
411
CTX->classification = DSR_ISSPAM;
412
CTX->source = DSS_INOCULATION;
414
CTX->result = DSR_ISSPAM;
416
CTX->probability = 1.0;
417
CTX->confidence = 1.0;
418
strcpy(CTX->class, LANG_CLASS_BLACKLISTED);
419
internally_canned = 1;
424
/* Process a signature if one was provided */
426
have_signature = find_signature(CTX, ATX);
427
if (ATX->source == DSS_CORPUS || ATX->source == DSS_NONE)
428
have_signature = 0; /* ignore sigs from corpusfed and inbound email */
432
char *original_username = CTX->username;
435
if (_ds_get_signature (CTX, &ATX->SIG, ATX->signature))
437
LOG(LOG_WARNING, ERR_AGENT_SIG_RET_FAILED, ATX->signature);
442
/* uid-based signatures will change the active username, so reload
443
preferences if it has changed */
445
CTX->signature = &ATX->SIG;
446
if (CTX->username != original_username) {
448
_ds_pref_free(ATX->PTX);
451
ATX->PTX = load_aggregated_prefs(ATX, CTX->username);
455
} else if (CTX->operating_mode == DSM_CLASSIFY ||
456
CTX->classification != DSR_NONE)
458
CTX->flags = CTX->flags ^ DSF_SIGNATURE;
459
CTX->signature = NULL;
462
if (have_signature && CTX->classification != DSR_NONE) {
465
* Reclassify (or retrain) message by signature
468
retrain_message(CTX, ATX);
470
CTX->signature = NULL;
471
if (!_ds_match_attribute(agent_config, "TrainPristine", "on") &&
472
strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on")) {
473
if (CTX->classification != DSR_NONE && CTX->source == DSS_ERROR) {
474
LOG(LOG_WARNING, ERR_AGENT_NO_VALID_SIG);
481
* Call libdspam to process the environment we've configured
484
if (!internally_canned)
485
result = dspam_process (CTX, message->data);
488
result = CTX->result;
490
if (result == DSR_ISINNOCENT && !strcmp(CTX->class, LANG_CLASS_WHITELISTED)) {
491
STATUS("Auto-Whitelisted");
495
* Send any relevant notifications to the user (first spam, etc)
496
* Only if the process was successful
499
if (result == DSR_ISINNOCENT || result == DSR_ISSPAM)
501
do_notifications(CTX, ATX);
504
if (strcmp(CTX->class, LANG_CLASS_WHITELISTED))
505
result = ensure_confident_result(CTX, ATX, result);
509
/* Inoculate other users (signature) */
511
if (have_signature &&
512
CTX->classification == DSR_ISSPAM &&
513
CTX->source != DSS_CORPUS &&
514
ATX->inoc_users->items > 0)
516
struct nt_node *node_int;
519
node_int = c_nt_first (ATX->inoc_users, &c_i);
520
while (node_int != NULL)
522
inoculate_user (ATX, (const char *) node_int->ptr, &ATX->SIG, NULL);
523
node_int = c_nt_next (ATX->inoc_users, &c_i);
527
/* Inoculate other users (message) */
529
if (!have_signature &&
530
CTX->classification == DSR_ISSPAM &&
531
CTX->source != DSS_CORPUS &&
532
ATX->inoc_users->items > 0)
534
struct nt_node *node_int;
536
node_int = c_nt_first (ATX->inoc_users, &c_i);
537
while (node_int != NULL)
539
inoculate_user (ATX, (const char *) node_int->ptr, NULL, message->data);
540
node_int = c_nt_next (ATX->inoc_users, &c_i);
542
inoculate_user (ATX, CTX->username, NULL, message->data);
544
CTX->result = DSR_ISSPAM;
549
/* Generate a signature id for the message and store */
551
if (internally_canned) {
552
if (CTX->signature) {
553
free(CTX->signature->data);
554
free(CTX->signature);
556
CTX->signature = calloc(1, sizeof(struct _ds_spam_signature));
557
if (CTX->signature) {
558
CTX->signature->length = 8;
559
CTX->signature->data = calloc(1, 8);
563
if (internally_canned || (CTX->operating_mode == DSM_PROCESS &&
564
CTX->classification == DSR_NONE &&
565
CTX->signature != NULL))
571
_ds_create_signature_id (CTX, ATX->signature, sizeof (ATX->signature));
572
if (_ds_verify_signature (CTX, ATX->signature))
575
LOGDEBUG ("saving signature as %s", ATX->signature);
577
if (CTX->classification == DSR_NONE && CTX->training_mode != DST_NOTRAIN)
579
if (!_ds_match_attribute(agent_config, "TrainPristine", "on") &&
580
strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on")) {
582
int x = _ds_set_signature (CTX, CTX->signature, ATX->signature);
584
LOG(LOG_WARNING, "_ds_set_signature() failed with error %d", x);
590
/* Write .stats file for web interface */
592
if (CTX->training_mode != DST_NOTRAIN && !_ds_match_attribute(agent_config, "SupressWebStats", "on")) {
595
(CTX->group == NULL || CTX->flags & DSF_MERGED) ?
596
CTX->username : CTX->group,
597
(CTX->group != NULL && CTX->flags & DSF_MERGED) ?
602
LOGDEBUG ("libdspam returned probability of %f", CTX->probability);
603
LOGDEBUG ("message result: %s", (result != DSR_ISSPAM) ? "NOT SPAM" : "SPAM");
605
/* System and User logging */
607
if (CTX->operating_mode != DSM_CLASSIFY &&
608
(_ds_match_attribute(agent_config, "SystemLog", "on") ||
609
_ds_match_attribute(agent_config, "UserLog", "on")))
611
log_events(CTX, ATX);
614
/* Fragment Store - Store 1k fragments of each message for web users who
615
* want to be able to see them from history. This requires some type of
616
* find recipe for purging
619
if (ATX->PTX != NULL && !strcmp(_ds_pref_val(ATX->PTX, "storeFragments"),
622
char dirname[MAX_FILENAME_LENGTH];
623
char corpusfile[MAX_FILENAME_LENGTH];
627
_ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
628
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group :
629
CTX->username), "frag");
630
snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s.frag",
631
dirname, ATX->signature);
633
LOGDEBUG("writing to frag file %s", corpusfile);
635
_ds_prepare_path_for(corpusfile);
636
file = fopen(corpusfile, "w");
638
char *body = strstr(message->data, "\n\n");
640
body = message->data;
641
strlcpy(output, body, sizeof(output));
648
/* Corpus Maker - Build a corpus in DSPAM_HOME/data/USERPATH/USER.corpus */
650
if (ATX->PTX != NULL && !strcmp(_ds_pref_val(ATX->PTX, "makeCorpus"), "on")) {
651
if (ATX->source != DSS_ERROR) {
652
char dirname[MAX_FILENAME_LENGTH];
653
char corpusfile[MAX_FILENAME_LENGTH];
656
_ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
657
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
658
: CTX->username), "corpus");
659
snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
660
dirname, (result == DSR_ISSPAM) ? "spam" : "nonspam",
663
LOGDEBUG("writing to corpus file %s", corpusfile);
665
_ds_prepare_path_for(corpusfile);
666
file = fopen(corpusfile, "w");
668
fputs(message->data, file);
672
char dirname[MAX_FILENAME_LENGTH];
673
char corpusfile[MAX_FILENAME_LENGTH];
674
char corpusdest[MAX_FILENAME_LENGTH];
676
_ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
677
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
678
: CTX->username), "corpus");
679
snprintf(corpusdest, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
680
dirname, (result == DSR_ISSPAM) ? "spam" : "nonspam",
682
snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
683
dirname, (result == DSR_ISSPAM) ? "nonspam" : "spam",
685
LOGDEBUG("moving corpusfile %s -> %s", corpusfile, corpusdest);
686
_ds_prepare_path_for(corpusdest);
687
rename(corpusfile, corpusdest);
691
/* False positives and spam misses should return here */
693
if (CTX->message == NULL)
696
/* Add headers, tag, and deliver if necessary */
699
add_xdspam_headers(CTX, ATX);
702
if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag") &&
703
result == DSR_ISSPAM)
705
tag_message(ATX, CTX->message);
708
if (strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers") &&
709
!_ds_match_attribute(agent_config, "TrainPristine", "on") &&
710
strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on") &&
711
(CTX->classification == DSR_NONE || internally_canned))
713
i = embed_signature(CTX, ATX);
720
/* Reassemble message from components */
722
copyback = _ds_assemble_message (CTX->message);
723
buffer_clear (message);
724
buffer_cat (message, copyback);
727
/* Track source address and report to syslog, RABL */
729
if ( _ds_read_attribute(agent_config, "TrackSources") &&
730
CTX->operating_mode == DSM_PROCESS &&
731
CTX->source != DSS_CORPUS &&
732
CTX->source != DSS_ERROR)
737
/* Print --classify output */
739
if (CTX->operating_mode == DSM_CLASSIFY || ATX->flags & DAF_SUMMARY)
744
switch (CTX->result) {
746
strcpy(data, "Spam");
749
strcpy(data, "Innocent");
755
ATX->sockfd_output = 1;
761
fprintf(fout, "X-DSPAM-Result: %s; result=\"%s\"; class=\"%s\"; "
762
"probability=%01.4f; confidence=%02.2f; signature=%s\n",
768
(ATX->signature[0]) ? ATX->signature : "N/A");
771
ATX->learned = CTX->learned;
773
*result_string = strdup(CTX->class);
778
ATX->signature[0] = 0;
779
nt_destroy (ATX->inoc_users);
780
nt_destroy (ATX->classify_users);
782
if (CTX->signature == &ATX->SIG) {
783
CTX->signature = NULL;
791
* deliver_message(AGENT_CTX *ATX, const char *message,
792
* const char *mailer_args, const char *username, FILE *stream,
796
* Deliver message to the appropriate destination. This could be one of:
797
* - Trusted/Untrusted Delivery Agent
798
* - Delivery Host (SMTP/LMTP)
800
* - File stream (--stdout)
803
* ATX Agent context defining processing behavior
804
* message Message to be delivered
805
* mailer_args Arguments to pass to local agents via pipe()
806
* username Destination username
807
* stream File stream (if any) for stdout delivery
808
* result Message classification result (DSR_)
811
* returns 0 on success
812
* EINVAL on permanent failure
813
* EFAILURE on temporary failure
814
* EFILE local agent failure
821
const char *mailer_args,
822
const char *username,
827
char *margs, *mmargs, *arg;
833
/* If QuarantineMailbox defined and delivering a spam, get
834
* name of recipient, truncate possible "+detail", and
835
* add the QuarantineMailbox name (that must include the "+")
838
if ((_ds_read_attribute(agent_config, "QuarantineMailbox")) &&
839
(result == DSR_ISSPAM)) {
840
strlcpy(args, ATX->recipient, sizeof(args));
842
if (arg != NULL) *arg='\0';
843
strlcat(args,_ds_read_attribute(agent_config, "QuarantineMailbox"),
848
/* If (using LMTP or SMTP) and (not delivering to stdout) and
849
* (we shouldn't be delivering this to a quarantine agent)
850
* then call deliver_socket to deliver to DeliveryHost
854
(USE_LMTP || USE_SMTP) && ! (ATX->flags & DAF_STDOUT) &&
855
(!(result == DSR_ISSPAM &&
856
_ds_read_attribute(agent_config, "QuarantineAgent") &&
857
ATX->PTX && !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "quarantine")))
860
return deliver_socket(ATX, message, (USE_LMTP) ? DDP_LMTP : DDP_SMTP);
867
/* If we're delivering to stdout, we need to provide a classification for
868
* use by client/daemon setups where the client needs to know the result
869
* in order to support broken returnCodes.s
872
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
873
fprintf(stream, "X-Daemon-Classification: %s\n",
874
(result == DSR_ISSPAM) ? "SPAM" : "INNOCENT");
876
if (mailer_args == NULL) {
877
fputs (message, stream);
881
/* Prepare local mailer args and interpolate all special symbols */
884
margs = strdup (mailer_args);
886
arg = strsep (&margs, " ");
892
/* Destination user */
894
if (!strcmp (arg, "$u") || !strcmp (arg, "\\$u") ||
895
!strcmp (arg, "%u") || !strcmp(arg, "\\%u"))
897
strlcpy(a, username, sizeof(a));
900
/* Recipient (from RCPT TO)*/
902
else if (!strcmp (arg, "%r") || !strcmp (arg, "\\%r"))
905
strlcpy(a, ATX->recipient, sizeof(a));
907
strlcpy(a, username, sizeof(a));
910
/* Sender (from MAIL FROM) */
912
else if (!strcmp (arg, "%s") || !strcmp (arg, "\\%s"))
913
strlcpy(a, ATX->mailfrom, sizeof(a));
916
strlcpy(a, arg, sizeof(a));
918
/* Escape special characters */
920
if (strcmp(a, "\"\"")) {
921
for(i=0;i<strlen(a);i++) {
922
if (!(isalnum((unsigned char) a[i]) || a[i] == '+' || a[i] == '_' ||
923
a[i] == '-' || a[i] == '.' || a[i] == '/' || a[i] == '@')) {
924
strlcpy(b, a+i, sizeof(b));
927
strlcat(a, b, sizeof(a));
934
strlcat (args, a, sizeof(args));
936
arg = strsep(&margs, " ");
939
strlcat (args, " ", sizeof (args));
944
LOGDEBUG ("Opening pipe to LDA: %s", args);
945
file = popen (args, "w");
948
LOG(LOG_ERR, ERR_LDA_OPEN, args, strerror (errno));
952
/* Manage local delivery agent failures */
954
fputs (message, file);
957
LOG(LOG_WARNING, ERR_LDA_STATUS, args, strerror (errno));
959
} else if (WIFEXITED (rc)) {
961
lda_exit_code = WEXITSTATUS (rc);
962
if (lda_exit_code == 0) {
963
LOGDEBUG ("LDA returned success");
965
LOG(LOG_ERR, ERR_LDA_EXIT, lda_exit_code, args);
966
if (_ds_match_attribute(agent_config, "LMTPLDAErrorsPermanent", "on"))
969
return lda_exit_code;
973
else if (WIFSIGNALED (rc))
977
LOG(LOG_ERR, ERR_LDA_SIGNAL, sig, args);
982
LOG(LOG_ERR, ERR_LDA_CLOSE, rc);
991
* tag_message(AGENT_CTX *ATX, ds_message_t message)
994
* Tags a message's subject line as spam using spamSubject
997
* ATX Agent context defining processing behavior
998
* message Message structure (ds_message_t) to tag
1001
* returns 0 on success
1002
* EINVAL on permanent failure
1003
* EFAILURE on temporary failure
1004
* EFILE local agent failure
1007
int tag_message(AGENT_CTX *ATX, ds_message_t message)
1009
ds_message_part_t block = message->components->first->ptr;
1010
struct nt_node *node_header = block->headers->first;
1012
char spam_subject[16];
1014
strcpy(spam_subject, "[SPAM]");
1015
if (_ds_pref_val(ATX->PTX, "spamSubject")[0] != '\n' &&
1016
_ds_pref_val(ATX->PTX, "spamSubject")[0] != 0)
1018
strlcpy(spam_subject, _ds_pref_val(ATX->PTX, "spamSubject"),
1019
sizeof(spam_subject));
1022
/* Only scan the first (primary) header of the message. */
1024
while (node_header != NULL)
1028
head = (ds_header_t) node_header->ptr;
1029
if (head->heading && !strcasecmp(head->heading, "Subject"))
1032
/* CURRENT HEADER: Is this header already tagged? */
1034
if (strncmp(head->data, spam_subject, strlen(spam_subject)))
1036
/* Not tagged, so tag it */
1037
long subject_length = strlen(head->data)+strlen(spam_subject)+2;
1038
char *subject = malloc(subject_length);
1039
if (subject != NULL) {
1041
subject_length, "%s %s",
1045
head->data = subject;
1049
/* ORIGINAL HEADER: Is this header already tagged? */
1051
if (head->original_data != NULL &&
1052
strncmp(head->original_data, spam_subject, strlen(spam_subject)))
1054
/* Not tagged => tag it. */
1055
long subject_length = strlen(head->original_data)+strlen(spam_subject)+2;
1056
char *subject = malloc(subject_length);
1057
if (subject != NULL) {
1059
subject_length, "%s %s",
1061
head->original_data);
1062
free(head->original_data);
1063
head->original_data = subject;
1069
node_header = node_header->next;
1072
/* There doesn't seem to be a subject field, so make one */
1078
snprintf(text, sizeof(text), "Subject: %s", spam_subject);
1079
header = _ds_create_header_field(text);
1083
LOGDEBUG("appending header %s: %s", header->heading, header->data);
1085
nt_add(block->headers, (void *) header);
1093
* quarantine_message(AGENT_CTX *ATX, const char *message,
1094
* const char *username)
1097
* Quarantine a message using DSPAM's internal quarantine function
1100
* ATX Agent context defining processing behavior
1101
* message Text message to quarantine
1102
* username Destination user
1105
* returns 0 on success, standard errors on failure
1109
quarantine_message (AGENT_CTX *ATX, const char *message, const char *username)
1111
char filename[MAX_FILENAME_LENGTH];
1112
char *x, *msg, *omsg;
1116
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
1117
LOOKUP(ATX->PTX, username), "mbox");
1118
_ds_prepare_path_for(filename);
1119
file = fopen (filename, "a");
1122
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
1126
i = _ds_get_fcntl_lock(fileno(file));
1128
LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
1132
/* Write our own "From " header if the MTA didn't give us one. This
1133
* allows for the viewing of a mailbox from elm or some other local
1134
* client that would otherwise believe the mailbox is corrupt.
1137
if (strncmp (message, "From ", 5))
1140
time_t tm = time (NULL);
1142
snprintf (head, sizeof (head), "From QUARANTINE %s", ctime (&tm));
1146
msg = strdup(message);
1150
LOG (LOG_CRIT, ERR_MEM_ALLOC);
1154
/* TODO: Is there a way to do this without a strdup/strsep ? */
1156
x = strsep (&msg, "\n");
1159
/* Quote any lines beginning with 'From ' to keep mbox from breaking */
1161
if (!strncmp (x, "From ", 5) && line != 1)
1166
x = strsep (&msg, "\n");
1168
fputs ("\n\n", file);
1170
_ds_free_fcntl_lock(fileno(file));
1178
* write_web_stats(AGENT_CTX *ATX, const char *username, const char *group,
1179
* struct _ds_spam_totals *totals)
1182
* Writes a .stats file in the user's data directory for use with web UI
1185
* ATX Agent context defining processing behavior
1186
* username Destination user
1187
* group Group membership
1188
* totals Pointer to processing totals
1191
* returns 0 on success, standard errors on failure
1197
const char *username,
1199
struct _ds_spam_totals *totals)
1201
char filename[MAX_FILENAME_LENGTH];
1207
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
1208
LOOKUP(ATX->PTX, username), "stats");
1209
_ds_prepare_path_for (filename);
1210
file = fopen (filename, "w");
1212
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
1216
fprintf (file, "%ld,%ld,%ld,%ld,%ld,%ld\n",
1217
MAX(0, (totals->spam_learned + totals->spam_classified) -
1218
(totals->spam_misclassified + totals->spam_corpusfed)),
1219
MAX(0, (totals->innocent_learned + totals->innocent_classified) -
1220
(totals->innocent_misclassified + totals->innocent_corpusfed)),
1221
totals->spam_misclassified, totals->innocent_misclassified,
1222
totals->spam_corpusfed, totals->innocent_corpusfed);
1225
fprintf(file, "%s\n", group);
1232
* inoculate_user(AGENT_CTX *ATX, const char *username,
1233
* struct _ds_spam_signature *SIG, const char *message)
1236
* Provide a vaccination for the spam processed to the target user
1239
* ATX Agent context defining processing behavior
1240
* username Target user
1241
* SIG Signature (if providing signature-based inoculation)
1242
* message Text Message (if providing message-based inoculation)
1245
* returns 0 on success, standard errors on failure
1251
const char *username,
1252
struct _ds_spam_signature *SIG,
1253
const char *message)
1256
int do_inoc = 1, result = 0;
1259
LOGDEBUG ("checking if user %s requires this inoculation", username);
1260
if (user_classify(ATX, username, SIG, message) == DSR_ISSPAM) {
1266
LOGDEBUG ("skipping user %s: doesn't require inoculation", username);
1271
LOGDEBUG ("inoculating user %s", username);
1273
if (ATX->flags & DAF_CHAINED)
1274
f_all |= DSF_CHAINED;
1276
if (ATX->flags & DAF_NOISE)
1279
if (ATX->flags & DAF_SBPH)
1282
if (ATX->PTX != NULL &&
1283
strcmp(_ds_pref_val(ATX->PTX, "processorBias"), ""))
1285
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
1288
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
1292
INOC = dspam_create (username,
1294
_ds_read_attribute(agent_config, "Home"),
1299
set_libdspam_attributes(INOC);
1300
if (attach_context(INOC, ATX->dbh)) {
1301
LOG (LOG_WARNING, ERR_CORE_ATTACH);
1302
dspam_destroy(INOC);
1306
INOC->classification = DSR_ISSPAM;
1307
INOC->source = DSS_INOCULATION;
1310
INOC->flags |= DSF_SIGNATURE;
1311
INOC->signature = SIG;
1312
result = dspam_process (INOC, NULL);
1316
result = dspam_process (INOC, message);
1320
INOC->signature = NULL;
1321
dspam_destroy (INOC);
1329
* user_classify(AGENT_CTX *ATX, const char *username,
1330
* struct _ds_spam_signature *SIG, const char *message)
1333
* Determine the classification of a message for another user
1336
* ATX Agent context defining processing behavior
1337
* username Target user
1338
* SIG Signature (if performing signature-based classification)
1339
* message Text Message (if performing message-based ciassification)
1342
* returns DSR_ value, standard errors on failure
1348
const char *username,
1349
struct _ds_spam_signature *SIG,
1350
const char *message)
1356
if (SIG == NULL && message == NULL) {
1357
LOG(LOG_WARNING, "user_classify(): SIG == NULL, message == NULL");
1361
if (ATX->flags & DAF_CHAINED)
1362
f_all |= DSF_CHAINED;
1364
if (ATX->flags & DAF_NOISE)
1367
if (ATX->flags & DAF_SBPH)
1370
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "")) {
1371
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
1374
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
1378
/* First see if the user needs to be inoculated */
1379
CLX = dspam_create (username,
1381
_ds_read_attribute(agent_config, "Home"),
1386
set_libdspam_attributes(CLX);
1387
if (attach_context(CLX, ATX->dbh)) {
1388
LOG (LOG_WARNING, ERR_CORE_ATTACH);
1395
CLX->flags |= DSF_SIGNATURE;
1396
CLX->signature = SIG;
1397
result = dspam_process (CLX, NULL);
1401
if (message == NULL) {
1402
LOG(LOG_WARNING, "user_classify: SIG = %ld, message = NULL\n", (unsigned long) SIG);
1405
result = dspam_process (CLX, message);
1409
CLX->signature = NULL;
1413
LOGDEBUG ("user_classify() returned error %d", result);
1417
result = CLX->result;
1419
dspam_destroy (CLX);
1426
* send_notice(AGENT_CTX *ATX, const char *filename, const char *mailer_args,
1427
* const char *username)
1430
* Sends a canned notice to the destination user
1433
* ATX Agent context defining processing behavior
1434
* filename Filename of canned notice
1435
* mailer_args Local agent arguments
1436
* username Destination user
1439
* returns 0 on success, standard errors on failure
1444
const char *filename,
1445
const char *mailer_args,
1446
const char *username)
1449
char msgfile[MAX_FILENAME_LENGTH];
1457
snprintf(msgfile, sizeof(msgfile), "%s/txt/%s",
1458
_ds_read_attribute(agent_config, "Home"), filename);
1459
f = fopen(msgfile, "r");
1461
LOG(LOG_ERR, ERR_IO_FILE_OPEN, filename, strerror(errno));
1465
b = buffer_create(NULL);
1467
LOG(LOG_CRIT, ERR_MEM_ALLOC);
1471
strftime(buf,sizeof(buf), "Date: %a, %d %b %Y %H:%M:%S %z\n",
1475
while(fgets(buf, sizeof(buf), f)!=NULL) {
1477
char *w = strstr(buf, "$u");
1481
buffer_cat(b, username);
1483
w = strstr(s, "$u");
1488
ret = deliver_message(ATX, b->data, mailer_args, username,
1489
stdout, DSR_ISINNOCENT);
1497
* process_users(AGENT_CTX *ATX, buffer *message)
1500
* Primary processing loop: cycle through all destination users and process
1503
* ATX Agent context defining processing behavior
1504
* message Buffer structure containing text message
1507
* returns 0 on success, standard errors on failure
1510
int process_users(AGENT_CTX *ATX, buffer *message) {
1511
int i = 0, have_rcpts = 0, return_code = 0, retcode = 0;
1512
struct nt_node *node_nt;
1513
struct nt_node *node_rcpt = NULL;
1514
struct nt_c c_nt, c_rcpt;
1515
buffer *parse_message;
1516
agent_result_t presult;
1517
char *plus, *atsign;
1527
node_nt = c_nt_first (ATX->users, &c_nt);
1528
if (ATX->recipients) {
1529
node_rcpt = c_nt_first (ATX->recipients, &c_rcpt);
1530
have_rcpts = ATX->recipients->items;
1533
/* Keep going as long as we have destination users */
1535
while (node_nt || node_rcpt)
1538
char filename[MAX_FILENAME_LENGTH];
1539
int result, optin, optout;
1542
/* If ServerParameters specifies a --user, there will only be one
1543
* instance on the stack, but possible multiple recipients. So we
1547
if (node_nt == NULL)
1548
node_nt = ATX->users->first;
1550
/* Set the "current recipient" to either the next item on the rcpt stack
1551
* or the current user if not present.
1554
username = node_nt->ptr;
1555
presult = calloc(1, sizeof(struct agent_result));
1557
ATX->recipient = node_rcpt->ptr;
1558
node_rcpt = c_nt_next (ATX->recipients, &c_rcpt);
1561
/* We started out using the recipients list and it's exhausted, so quit */
1565
ATX->recipient = node_nt->ptr;
1568
/* If support for "+detail" is enabled, save full mailbox name for
1569
delivery and strip detail for processing */
1571
if (_ds_match_attribute(agent_config, "EnablePlusedDetail", "on")) {
1572
strlcpy(mailbox, username, sizeof(mailbox));
1573
ATX->recipient = mailbox;
1574
plus = index(username, '+');
1576
atsign = index(plus, '@');
1578
strcpy(plus, atsign);
1584
parse_message = buffer_create(message->data);
1585
if (parse_message == NULL) {
1586
LOG(LOG_CRIT, ERR_MEM_ALLOC);
1587
presult->exitcode = ERC_PROCESS;
1588
strcpy(presult->text, ERR_MEM_ALLOC);
1591
nt_add(ATX->results, presult);
1596
/* Determine whether to activate debug. If we're running in daemon mode,
1597
* debug is either on or off (it's a global variable), so this only
1598
* applies to running in client or local processing mode.
1603
(_ds_match_attribute(agent_config, "Debug", "*") ||
1604
_ds_match_attribute(agent_config, "Debug", node_nt->ptr)))
1606
// No DebugOpt specified; turn it on for everything
1607
if (!_ds_read_attribute(agent_config, "DebugOpt"))
1612
if (_ds_match_attribute(agent_config, "DebugOpt", "process") &&
1613
ATX->source == DSS_NONE &&
1614
ATX->operating_mode == DSM_PROCESS)
1619
if (_ds_match_attribute(agent_config, "DebugOpt", "classify") &&
1620
ATX->operating_mode == DSM_CLASSIFY)
1625
if (_ds_match_attribute(agent_config, "DebugOpt", "spam") &&
1626
ATX->classification == DSR_ISSPAM &&
1627
ATX->source == DSS_ERROR)
1632
if (_ds_match_attribute(agent_config, "DebugOpt", "fp") &&
1633
ATX->classification == DSR_ISINNOCENT &&
1634
ATX->source == DSS_ERROR)
1639
if (_ds_match_attribute(agent_config, "DebugOpt", "inoculation") &&
1640
ATX->source == DSS_INOCULATION)
1645
if (_ds_match_attribute(agent_config, "DebugOpt", "corpus") &&
1646
ATX->source == DSS_CORPUS)
1656
LOGDEBUG ("DSPAM Instance Startup");
1657
LOGDEBUG ("input args: %s", ATX->debug_args);
1658
LOGDEBUG ("pass-thru args: %s", ATX->mailer_args);
1659
LOGDEBUG ("processing user %s", (const char *) node_nt->ptr);
1660
LOGDEBUG ("uid = %d, euid = %d, gid = %d, egid = %d",
1661
getuid(), geteuid(), getgid(), getegid());
1663
/* Write message to dspam.messags */
1666
char m[MAX_FILENAME_LENGTH];
1667
snprintf (m, sizeof (m), "%s/dspam.messages", LOGDIR);
1671
fprintf (f, "%s\n", parse_message->data);
1679
* Determine if the user is opted in or out
1682
ATX->PTX = load_aggregated_prefs(ATX, username);
1683
if (!strcmp(_ds_pref_val(ATX->PTX, "fallbackDomain"), "on")) {
1684
char *domain = strchr(username, '@');
1688
_ds_userdir_path(filename,
1689
_ds_read_attribute(agent_config, "Home"),
1690
LOOKUP(ATX->PTX, username), "dspam");
1691
optin = stat(filename, &s);
1694
if (!optin && (!S_ISDIR(s.st_mode))) {
1696
LOG(LOG_WARNING, ERR_AGENT_OPTIN_DIR, filename);
1700
_ds_userdir_path(filename,
1701
_ds_read_attribute(agent_config, "Home"),
1702
LOOKUP(ATX->PTX, username), "nodspam");
1703
optout = stat(filename, &s);
1705
/* If the message is too big to process, just deliver it */
1707
if (_ds_read_attribute(agent_config, "MaxMessageSize")) {
1708
if (parse_message->used >
1709
atoi(_ds_read_attribute(agent_config, "MaxMessageSize")))
1711
LOG (LOG_INFO, "message too big, delivering");
1716
/* Deliver the message if the user has opted not to be filtered */
1718
optout = (optout) ? 0 : 1;
1719
optin = (optin) ? 0 : 1;
1721
if /* opted out implicitly */
1722
(optout || !strcmp(_ds_pref_val(ATX->PTX, "optOut"), "on") ||
1724
/* not opted in (in an opt-in system) */
1725
(_ds_match_attribute(agent_config, "Opt", "in") &&
1726
!optin && strcmp(_ds_pref_val(ATX->PTX, "optIn"), "on")))
1728
if (ATX->flags & DAF_DELIVER_INNOCENT)
1731
deliver_message (ATX, parse_message->data,
1732
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
1733
node_nt->ptr, fout, DSR_ISINNOCENT);
1735
presult->exitcode = ERC_DELIVERY;
1736
if (retcode == EINVAL)
1737
presult->exitcode = ERC_PERMANENT_DELIVERY;
1738
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1740
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1741
ATX->sockfd_output = 1;
1745
/* Call process_message(), then handle result appropriately */
1749
char *result_string = NULL;
1750
result = process_message (ATX, parse_message, username, &result_string);
1751
presult->classification = result;
1754
if (result_string && !strcmp(result_string, LANG_CLASS_VIRUS)) {
1755
if (_ds_match_attribute(agent_config, "ClamAVResponse", "reject")) {
1756
presult->classification = DSR_ISSPAM;
1757
presult->exitcode = ERC_PERMANENT_DELIVERY;
1758
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1761
else if (_ds_match_attribute(agent_config, "ClamAVResponse", "spam"))
1763
presult->classification = DSR_ISSPAM;
1764
presult->exitcode = ERC_SUCCESS;
1765
result = DSR_ISSPAM;
1766
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1768
presult->classification = DSR_ISINNOCENT;
1769
presult->exitcode = ERC_SUCCESS;
1774
free(result_string);
1776
/* Exit code 99 for spam (when using broken return codes) */
1778
if (_ds_match_attribute(agent_config, "Broken", "returnCodes")) {
1779
if (result == DSR_ISSPAM)
1787
if (ATX->operating_mode == DSM_CLASSIFY)
1789
node_nt = c_nt_next (ATX->users, &c_nt);
1790
_ds_pref_free(ATX->PTX);
1793
buffer_destroy(parse_message);
1799
* Classify and Process
1804
if (result != DSR_ISSPAM)
1808
/* Processing Error */
1810
if (result != DSR_ISINNOCENT &&
1811
ATX->classification != DSR_NONE &&
1812
ATX->classification != DSR_NONE)
1816
"process_message returned error %d. dropping message.", result);
1819
if (result != DSR_ISINNOCENT && ATX->classification == DSR_NONE)
1823
"process_message returned error %d. delivering.", result);
1828
if (deliver && ATX->flags & DAF_DELIVER_INNOCENT) {
1829
LOGDEBUG ("delivering message");
1830
retcode = deliver_message
1831
(ATX, parse_message->data,
1832
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
1833
node_nt->ptr, fout, DSR_ISINNOCENT);
1835
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1836
ATX->sockfd_output = 1;
1838
presult->exitcode = ERC_DELIVERY;
1839
if (retcode == EINVAL)
1840
presult->exitcode = ERC_PERMANENT_DELIVERY;
1841
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1843
if (result == DSR_ISINNOCENT &&
1844
_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
1847
ATX->classification = result;
1848
ATX->source = DSS_ERROR;
1849
ATX->flags |= DAF_UNLEARN;
1850
process_message (ATX, parse_message, username, NULL);
1861
/* Do not Deliver Spam */
1863
if (! (ATX->flags & DAF_DELIVER_SPAM))
1867
/* If a specific quarantine has been configured, use it */
1869
if (ATX->source != DSS_CORPUS) {
1870
if (ATX->spam_args[0] != 0 ||
1871
(ATX->PTX != NULL &&
1872
( !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag") ||
1873
!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "deliver") )
1877
if (ATX->classification == DSR_NONE) {
1878
if (ATX->spam_args[0] != 0) {
1879
retcode = deliver_message
1880
(ATX, parse_message->data,
1881
(ATX->flags & DAF_STDOUT) ? NULL : ATX->spam_args,
1882
node_nt->ptr, fout, DSR_ISSPAM);
1883
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1884
ATX->sockfd_output = 1;
1886
retcode = deliver_message
1887
(ATX, parse_message->data,
1888
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
1889
node_nt->ptr, fout, DSR_ISSPAM);
1890
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1891
ATX->sockfd_output = 1;
1895
presult->exitcode = ERC_DELIVERY;
1896
if (retcode == EINVAL)
1897
presult->exitcode = ERC_PERMANENT_DELIVERY;
1898
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1903
/* Use standard quarantine procedure */
1904
if (ATX->source == DSS_INOCULATION ||
1905
ATX->classification == DSR_NONE)
1907
if (ATX->managed_group[0] == 0)
1909
quarantine_message (ATX, parse_message->data, username);
1912
quarantine_message (ATX, parse_message->data,
1913
ATX->managed_group);
1918
presult->exitcode = ERC_DELIVERY;
1919
if (retcode == EINVAL)
1920
presult->exitcode = ERC_PERMANENT_DELIVERY;
1921
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1924
/* Unlearn the message on a local delivery failure */
1925
if (_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
1927
ATX->classification = result;
1928
ATX->source = DSS_ERROR;
1929
ATX->flags |= DAF_UNLEARN;
1930
process_message (ATX, parse_message, username, NULL);
1940
if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1941
ATX->sockfd_output = 1;
1942
retcode = deliver_message
1943
(ATX, parse_message->data,
1944
(ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
1945
node_nt->ptr, fout, DSR_ISSPAM);
1948
presult->exitcode = ERC_DELIVERY;
1949
if (retcode == EINVAL)
1950
presult->exitcode = ERC_PERMANENT_DELIVERY;
1951
strlcpy(presult->text, ATX->status, sizeof(presult->text));
1954
if (_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
1956
ATX->classification = result;
1957
ATX->source = DSS_ERROR;
1958
ATX->flags |= DAF_UNLEARN;
1959
process_message (ATX, parse_message, username, NULL);
1969
_ds_pref_free(ATX->PTX);
1972
node_nt = c_nt_next (ATX->users, &c_nt);
1975
nt_add(ATX->results, presult);
1978
LOGDEBUG ("DSPAM Instance Shutdown. Exit Code: %d", return_code);
1979
buffer_destroy(parse_message);
1990
* find_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX)
1993
* Find and parse DSPAM signature
1996
* CTX DSPAM context containing message and parameters
1997
* ATX Agent context defining processing behavior
2000
* returns 1 (and sets CTX->signature) if found
2004
int find_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
2005
struct nt_node *node_nt, *prev_node = NULL;
2007
ds_message_part_t block = NULL;
2008
char first_boundary[512];
2009
int is_signed = 0, i = 0;
2010
char *signature_begin = NULL, *signature_end, *erase_begin;
2011
int signature_length, have_signature = 0;
2012
struct nt_node *node_header;
2014
first_boundary[0] = 0;
2016
if (ATX->signature[0] != 0)
2019
/* Iterate through each message component in search of a signature
2020
* and decode components as necessary
2023
node_nt = c_nt_first (CTX->message->components, &c);
2024
while (node_nt != NULL)
2026
block = (ds_message_part_t) node_nt->ptr;
2028
if (block->media_type == MT_MULTIPART && block->media_subtype == MST_SIGNED)
2031
if (!strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers"))
2035
LOGDEBUG ("scanning component %d for a DSPAM signature", i);
2038
if (block->media_type == MT_TEXT
2039
|| block->media_type == MT_MESSAGE
2040
|| block->media_type == MT_UNKNOWN
2041
|| (!i && block->media_type == MT_MULTIPART))
2045
/* Verbose output of each message component */
2049
if (block->boundary != NULL)
2051
LOGDEBUG (" : Boundary : %s", block->boundary);
2053
if (block->terminating_boundary != NULL)
2054
LOGDEBUG (" : Term Boundary: %s", block->terminating_boundary);
2055
LOGDEBUG (" : Encoding : %d", block->encoding);
2056
LOGDEBUG (" : Media Type : %d", block->media_type);
2057
LOGDEBUG (" : Media Subtype: %d", block->media_subtype);
2058
LOGDEBUG (" : Headers:");
2059
node_header = c_nt_first (block->headers, &c2);
2060
while (node_header != NULL)
2062
ds_header_t header =
2063
(ds_header_t) node_header->ptr;
2064
LOGDEBUG (" %-32s %s", header->heading, header->data);
2065
node_header = c_nt_next (block->headers, &c2);
2070
body = block->body->data;
2071
if (block->encoding == EN_BASE64
2072
|| block->encoding == EN_QUOTED_PRINTABLE)
2074
if (block->content_disposition != PCD_ATTACHMENT)
2077
LOGDEBUG ("decoding message block from encoding type %d",
2081
body = _ds_decode_block (block);
2086
("message is signed. retaining original text for reassembly");
2087
block->original_signed_body = block->body;
2091
block->encoding = EN_8BIT;
2093
node_header = c_nt_first (block->headers, &c2);
2094
while (node_header != NULL)
2096
ds_header_t header =
2097
(ds_header_t) node_header->ptr;
2099
(header->heading, "Content-Transfer-Encoding"))
2101
free (header->data);
2102
header->data = strdup ("8bit");
2104
node_header = c_nt_next (block->headers, &c2);
2107
buffer_destroy (block->body);
2109
block->body = buffer_create (body);
2112
body = block->body->data;
2116
if (!strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers")) {
2117
if (block->headers != NULL && !have_signature)
2119
struct nt_node *node_header;
2122
node_header = block->headers->first;
2123
while(node_header != NULL) {
2124
head = (ds_header_t) node_header->ptr;
2125
if (head->heading &&
2126
!strcmp(head->heading, "X-DSPAM-Signature")) {
2127
if (!strncmp(head->data, SIGNATURE_BEGIN,
2128
strlen(SIGNATURE_BEGIN)))
2134
strlcpy(ATX->signature, head->data, sizeof(ATX->signature));
2139
node_header = node_header->next;
2144
if (!_ds_match_attribute(agent_config, "TrainPristine", "on") &&
2145
strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on") &&
2147
/* Don't keep searching if we've already found the signature in the
2148
* headers, and we're using signatureLocation=headers
2151
strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers")))
2153
/* Look for signature */
2157
signature_begin = strstr (body, SIGNATURE_BEGIN);
2158
if (signature_begin == NULL) {
2159
signature_begin = strstr (body, LOOSE_SIGNATURE_BEGIN);
2163
if (signature_begin)
2165
erase_begin = signature_begin;
2167
signature_begin += strlen(SIGNATURE_BEGIN);
2169
char *loose = strstr (signature_begin, SIGNATURE_DELIMITER);
2171
LOGDEBUG("found loose signature begin, but no delimiter");
2174
signature_begin = loose + strlen(SIGNATURE_DELIMITER);
2177
signature_end = signature_begin;
2179
/* Find the signature's end character */
2180
while (signature_end != NULL
2181
&& signature_end[0] != 0
2182
&& (isalnum ((int) signature_end[0]) || signature_end[0] == 32 ||
2183
signature_end[0] == ','))
2188
if (signature_end != NULL)
2190
signature_length = signature_end - signature_begin;
2192
if (signature_length < 128)
2194
memcpy (ATX->signature, signature_begin, signature_length);
2195
ATX->signature[signature_length] = 0;
2197
while(isspace( (int) ATX->signature[0]))
2199
memmove(ATX->signature, ATX->signature+1, strlen(ATX->signature));
2202
if (strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"),
2205
if (!is_signed && ATX->classification == DSR_NONE) {
2206
memmove(erase_begin, signature_end+1, strlen(signature_end+1)+1);
2207
block->body->used = (long) strlen(body);
2211
LOGDEBUG ("found signature '%s'", ATX->signature);
2216
} /* TrainPristine */
2219
prev_node = node_nt;
2220
node_nt = c_nt_next (CTX->message->components, &c);
2224
CTX->message->protect = is_signed;
2226
return have_signature;
2230
* ctx_init(AGENT_CTX *ATX, const char *username)
2233
* Initialize a DSPAM context from an agent context
2236
* ATX Agent context defining processing behavior
2237
* username Destination user
2240
* pointer to newly allocated DSPAM context, NULL on failure
2244
DSPAM_CTX *ctx_init(AGENT_CTX *ATX, const char *username) {
2246
char filename[MAX_FILENAME_LENGTH];
2247
char ctx_group[128] = { 0 };
2248
int f_all = 0, f_mode = DSM_PROCESS;
2251
ATX->inoc_users = nt_create (NT_CHAR);
2252
if (ATX->inoc_users == NULL) {
2253
LOG (LOG_CRIT, ERR_MEM_ALLOC);
2257
ATX->classify_users = nt_create (NT_CHAR);
2258
if (ATX->classify_users == NULL)
2260
nt_destroy(ATX->inoc_users);
2261
LOG (LOG_CRIT, ERR_MEM_ALLOC);
2265
/* Set Group Membership */
2267
if (strcmp(_ds_pref_val(ATX->PTX, "ignoreGroups"), "on")) {
2268
snprintf (filename, sizeof (filename), "%s/group",
2269
_ds_read_attribute(agent_config, "Home"));
2270
file = fopen (filename, "r");
2277
while (fgets (buffer, sizeof (buffer), file) != NULL)
2279
int do_inocgroups = 0;
2283
if (buffer[0] == 0 || buffer[0] == '#' || buffer[0] == ';')
2286
list = strdup (buffer);
2287
group = strtok (buffer, ":");
2291
type = strtok (NULL, ":");
2292
user = strtok (NULL, ",");
2297
if (!strcasecmp (type, "INOCULATION") &&
2298
ATX->classification == DSR_ISSPAM &&
2299
ATX->source != DSS_CORPUS)
2304
while (user != NULL)
2306
if (!strcmp (user, username) || user[0] == '*' ||
2307
(!strncmp(user, "*@", 2) && !strcmp(user+2, strchr(username,'@'))))
2310
/* If we're reporting a spam, report it as a spam to all other
2311
* users in the inoculation group */
2315
u = strsep (&l, ":");
2316
u = strsep (&l, ":");
2317
u = strsep (&l, ",");
2320
if (strcmp (u, username))
2322
LOGDEBUG ("adding user %s to inoculation group %s", u,
2325
nt_add (ATX->inoc_users, u+1);
2327
nt_add (ATX->inoc_users, u);
2329
u = strsep (&l, ",");
2332
else if (!strncasecmp (type, "SHARED", 6))
2334
strlcpy (ctx_group, group, sizeof (ctx_group));
2335
LOGDEBUG ("assigning user %s to group %s", username, group);
2337
if (!strncasecmp (type + 6, ",MANAGED", 8))
2338
strlcpy (ATX->managed_group,
2340
sizeof(ATX->managed_group));
2343
else if (!strcasecmp (type, "CLASSIFICATION"))
2346
u = strsep (&l, ":");
2347
u = strsep (&l, ":");
2348
u = strsep (&l, ",");
2351
if (strcmp (u, username))
2353
LOGDEBUG ("adding user %s to classification group %s", u,
2357
ATX->flags |= DAF_GLOBAL;
2358
nt_add (ATX->classify_users, u+1);
2360
nt_add (ATX->classify_users, u);
2362
u = strsep (&l, ",");
2365
else if (!strcasecmp (type, "MERGED") && strcmp(group, username))
2368
u = strsep (&l, ":");
2369
u = strsep (&l, ":");
2370
u = strsep (&l, ",");
2373
if (!strcmp (u, username) || u[0] == '*')
2375
LOGDEBUG ("adding user to merged group %s", group);
2377
ATX->flags |= DAF_MERGED;
2379
strlcpy(ctx_group, group, sizeof(ctx_group));
2380
} else if (u[0] == '-' && !strcmp(u+1, username)) {
2381
LOGDEBUG ("removing user from merged group %s", group);
2383
ATX->flags ^= DAF_MERGED;
2386
u = strsep (&l, ",");
2391
user = strtok (NULL, ",");
2401
/* Crunch our agent context into a DSPAM context */
2403
f_mode = ATX->operating_mode;
2404
f_all = DSF_SIGNATURE;
2406
if (ATX->flags & DAF_UNLEARN)
2407
f_all |= DSF_UNLEARN;
2409
if (ATX->flags & DAF_CHAINED)
2410
f_all |= DSF_CHAINED;
2412
if (ATX->flags & DAF_SBPH)
2415
/* If there is no preference, defer to commandline */
2416
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "enableBNR"), "")) {
2417
if (!strcmp(_ds_pref_val(ATX->PTX, "enableBNR"), "on"))
2420
if (ATX->flags & DAF_NOISE)
2424
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "")) {
2425
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
2428
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
2432
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "enableWhitelist"), ""))
2434
if (!strcmp(_ds_pref_val(ATX->PTX, "enableWhitelist"), "on"))
2435
f_all |= DSF_WHITELIST;
2437
if (ATX->flags & DAF_WHITELIST)
2438
f_all |= DSF_WHITELIST;
2441
if (ATX->flags & DAF_MERGED)
2442
f_all |= DSF_MERGED;
2444
CTX = dspam_create (username,
2446
_ds_read_attribute(agent_config, "Home"),
2453
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "statisticalSedation"), ""))
2454
CTX->training_buffer = atoi(_ds_pref_val(ATX->PTX, "statisticalSedation"));
2455
else if (ATX->training_buffer>=0)
2456
CTX->training_buffer = ATX->training_buffer;
2457
LOGDEBUG("sedation level set to: %d", CTX->training_buffer);
2459
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "whitelistThreshold"), ""))
2460
CTX->wh_threshold = atoi(_ds_pref_val(ATX->PTX, "whitelistThreshold"));
2462
if (ATX->classification != DSR_NONE) {
2463
CTX->classification = ATX->classification;
2464
CTX->source = ATX->source;
2467
if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "trainingMode"), "")) {
2468
if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TEFT"))
2469
CTX->training_mode = DST_TEFT;
2470
else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TOE"))
2471
CTX->training_mode = DST_TOE;
2472
else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TUM"))
2473
CTX->training_mode = DST_TUM;
2474
else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "NOTRAIN"))
2475
CTX->training_mode = DST_NOTRAIN;
2477
CTX->training_mode = ATX->training_mode;
2479
CTX->training_mode = ATX->training_mode;
2486
* retrain_message(DSPAM_CTX *CTX, AGENT_CTX *ATX)
2489
* Retrain a message and perform iterative training
2492
* CTX DSPAM context containing the classification results
2493
* ATX Agent context defining processing behavior
2496
* returns 0 on success, standard errors on failure
2499
int retrain_message(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
2501
int do_train = 1, iter = 0, ck_result = 0, t_mode = CTX->source;
2503
/* Train until test conditions are met, 5 iterations max */
2505
if (!_ds_match_attribute(agent_config, "TestConditionalTraining", "on")) {
2506
result = dspam_process (CTX, NULL);
2508
while (do_train && iter < 5)
2513
match = (CTX->classification == DSR_ISSPAM) ?
2514
DSR_ISSPAM : DSR_ISINNOCENT;
2517
result = dspam_process (CTX, NULL);
2519
/* Only subtract innocent values once */
2520
CTX->source = DSS_CORPUS;
2522
LOGDEBUG ("reclassifying iteration %d result: %d", iter, result);
2524
if (t_mode == DSS_CORPUS)
2527
/* Only attempt test-conditional training on a mature corpus */
2529
if (CTX->totals.innocent_learned+CTX->totals.innocent_classified<1000 &&
2530
CTX->classification == DSR_ISSPAM)
2536
int f_all = DSF_SIGNATURE;
2538
/* CLX = Classify Context */
2539
if (ATX->flags & DAF_CHAINED)
2540
f_all |= DSF_CHAINED;
2542
if (ATX->flags & DAF_NOISE)
2545
if (ATX->flags & DAF_SBPH)
2548
if (ATX->PTX != NULL &&
2549
strcmp(_ds_pref_val(ATX->PTX, "processorBias"), ""))
2551
if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
2554
if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
2558
CLX = dspam_create (CTX->username,
2560
_ds_read_attribute(agent_config, "Home"),
2569
CLX->training_mode = CTX->training_mode;
2571
set_libdspam_attributes(CLX);
2572
if (attach_context(CLX, ATX->dbh)) {
2578
CLX->signature = &ATX->SIG;
2579
ck_result = dspam_process (CLX, NULL);
2580
if (ck_result || CLX->result == match)
2582
CLX->signature = NULL;
2583
dspam_destroy (CLX);
2587
CTX->source = DSS_ERROR;
2594
* ensure_confident_result(DSPAM_CTX *CTX, AGENT_CTX, ATX, int result)
2597
* Consult a global group or classification network if
2598
* the user's filter instance isn't confident in its result
2601
* CTX DSPAM context containing classification results
2602
* ATX Agent context defining processing behavior
2603
* result DSR_ processing result
2606
* returns result networks believe the message should be
2609
/* ensure_confident_result: consult global group or
2610
clasification network if the user isn't confident in their result */
2612
int ensure_confident_result(DSPAM_CTX *CTX, AGENT_CTX *ATX, int result) {
2615
/* Defer to global group */
2616
if (ATX->flags & DAF_GLOBAL &&
2617
((CTX->totals.innocent_learned + CTX->totals.innocent_corpusfed < 1000 ||
2618
CTX->totals.spam_learned + CTX->totals.spam_corpusfed < 250) ||
2619
(CTX->training_mode == DST_NOTRAIN))
2622
if (result == DSR_ISSPAM) {
2624
CTX->result = DSR_ISINNOCENT;
2625
result = DSR_ISINNOCENT;
2627
CTX->confidence = 0.60f;
2630
if (result != DSR_ISSPAM &&
2631
CTX->operating_mode == DSM_PROCESS &&
2632
CTX->classification == DSR_NONE &&
2633
CTX->confidence < 0.65)
2635
struct nt_node *node_int;
2638
node_int = c_nt_first (ATX->classify_users, &c_i);
2639
while (node_int != NULL && result != DSR_ISSPAM)
2641
LOGDEBUG ("checking result for user %s", (const char *) node_int->ptr);
2642
result = user_classify (ATX, (const char *) node_int->ptr,
2643
CTX->signature, NULL);
2644
if (result == DSR_ISSPAM)
2646
LOGDEBUG ("CLASSIFY CATCH: %s", (const char *) node_int->ptr);
2647
CTX->result = result;
2650
node_int = c_nt_next (ATX->classify_users, &c_i);
2653
/* Re-add as spam */
2655
if (result == DSR_ISSPAM && !was_spam)
2657
DSPAM_CTX *CTC = malloc(sizeof(DSPAM_CTX));
2660
LOG(LOG_CRIT, ERR_MEM_ALLOC);
2664
memcpy(CTC, CTX, sizeof(DSPAM_CTX));
2666
CTC->operating_mode = DSM_PROCESS;
2667
CTC->classification = DSR_ISSPAM;
2668
CTC->source = DSS_ERROR;
2669
CTC->flags |= DSF_SIGNATURE;
2670
dspam_process (CTC, NULL);
2671
memcpy(&CTX->totals, &CTC->totals, sizeof(struct _ds_spam_totals));
2673
CTX->totals.spam_misclassified--;
2674
CTX->result = result;
2677
/* If the global user thinks it's innocent, and the user thought it was
2678
* spam, retrain the user as a false positive
2681
if (result == DSR_ISINNOCENT && was_spam) {
2682
DSPAM_CTX *CTC = malloc(sizeof(DSPAM_CTX));
2684
LOG(LOG_CRIT, ERR_MEM_ALLOC);
2688
memcpy(CTC, CTX, sizeof(DSPAM_CTX));
2690
CTC->operating_mode = DSM_PROCESS;
2691
CTC->classification = DSR_ISINNOCENT;
2692
CTC->source = DSS_ERROR;
2693
CTC->flags |= DSF_SIGNATURE;
2694
dspam_process (CTC, NULL);
2695
memcpy(&CTX->totals, &CTC->totals, sizeof(struct _ds_spam_totals));
2697
CTX->totals.innocent_misclassified--;
2698
CTX->result = result;
2706
* log_events(DSPAM_CTX *CTX, AGENT_CTX *ATX)
2709
* Log events to system and user logs
2713
* ATX Agent context defining processing behavior
2716
* returns 0 on success, standard errors on failure
2719
int log_events(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
2720
char filename[MAX_FILENAME_LENGTH];
2721
char *subject = NULL, *from = NULL;
2722
struct nt_node *node_nt;
2727
char *messageid = NULL;
2731
messageid = _ds_find_header(CTX->message, "Message-Id", DDF_ICASE);
2733
if (ATX->status[0] == 0 && CTX->source == DSS_ERROR &&
2734
(!(ATX->flags & DAF_UNLEARN)))
2736
STATUS("Retrained");
2739
if (ATX->status[0] == 0 && CTX->classification == DSR_NONE
2740
&& CTX->result == DSR_ISSPAM
2741
&& ATX->status[0] == 0)
2743
if (_ds_pref_val(ATX->PTX, "spamAction")[0] == 0 ||
2744
!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "quarantine"))
2746
STATUS("Quarantined");
2747
} else if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag")) {
2749
} else if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "deliver")) {
2750
STATUS("Delivered");
2754
if (ATX->status[0] == 0 &&
2755
CTX->classification == DSR_NONE &&
2756
CTX->result == DSR_ISINNOCENT)
2758
STATUS("Delivered");
2761
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"), LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group : CTX->username), "log");
2763
node_nt = c_nt_first (CTX->message->components, &c_nt);
2764
if (node_nt != NULL)
2766
ds_message_part_t block;
2768
block = node_nt->ptr;
2769
if (block->headers != NULL)
2772
struct nt_node *node_header;
2774
node_header = block->headers->first;
2775
while(node_header != NULL) {
2776
head = (ds_header_t) node_header->ptr;
2778
if (!strcasecmp(head->heading, "Subject"))
2779
subject = head->data;
2780
else if (!strcasecmp(head->heading, "From"))
2784
node_header = node_header->next;
2789
if (CTX->result == DSR_ISSPAM)
2791
else if (!strcmp(CTX->class, LANG_CLASS_WHITELISTED))
2796
if (CTX->source == DSS_ERROR) {
2797
if (CTX->classification == DSR_ISSPAM)
2799
else if (CTX->classification == DSR_ISINNOCENT)
2803
if (CTX->source == DSS_INOCULATION)
2805
else if (CTX->source == DSS_CORPUS)
2808
if (ATX->flags & DAF_UNLEARN) {
2810
snprintf(stat, sizeof(stat), "Delivery Failed (%s)",
2811
(ATX->status[0]) ? ATX->status : "No error provided");
2816
/* Write USER.log */
2818
if (_ds_match_attribute(agent_config, "UserLog", "on")) {
2821
snprintf(fromline, sizeof(fromline), "%s", (from == NULL) ? "<None Specified>" : from);
2822
while(subject && (c=strchr(subject, '\t')))
2824
while((c=strchr(fromline, '\t')))
2827
snprintf(x, sizeof(x), "%ld\t%c\t%s\t%s\t%s\t%s\t%s\n",
2832
(subject == NULL) ? "<None Specified>" : subject,
2834
(messageid) ? messageid : "");
2835
for(y=0;y<strlen(x);y++)
2839
_ds_prepare_path_for(filename);
2840
file = fopen(filename, "a");
2842
int i = _ds_get_fcntl_lock(fileno(file));
2846
_ds_free_fcntl_lock(fileno(file));
2848
LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
2854
/* Write system.log */
2856
if (_ds_match_attribute(agent_config, "SystemLog", "on")) {
2857
snprintf(filename, sizeof(filename), "%s/system.log",
2858
_ds_read_attribute(agent_config, "Home"));
2859
file = fopen(filename, "a");
2861
int i = _ds_get_fcntl_lock(fileno(file));
2865
snprintf(s, sizeof(s), "%ld\t%c\t%s\t%s\t%s\t%f\t%s\t%s\t%s\n",
2868
(from == NULL) ? "<None Specified>" : from,
2870
(subject == NULL) ? "<None Specified>" : subject,
2871
_ds_gettime()-ATX->timestart,
2872
(CTX->username) ? CTX->username: "",
2873
(ATX->status) ? ATX->status : "",
2874
(messageid) ? messageid : "");
2876
_ds_free_fcntl_lock(fileno(file));
2878
LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
2887
* add_xdspam_headers(DSPAM_CTX *CTX, AGENT_CTX *ATX)
2890
* Add X-DSPAM headers to the message being processed
2893
* CTX DSPAM context containing message and results
2894
* ATX Agent context defining processing behavior
2897
* returns 0 on success, standard errors on failure
2901
int add_xdspam_headers(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
2902
struct nt_node *node_nt;
2905
node_nt = c_nt_first (CTX->message->components, &c_nt);
2906
if (node_nt != NULL)
2908
ds_message_part_t block = node_nt->ptr;
2909
struct nt_node *node_ft;
2911
if (block != NULL && block->headers != NULL)
2917
snprintf(data, sizeof(data), "%s: %s",
2918
(CTX->source == DSS_ERROR) ? "X-DSPAM-Reclassified" : "X-DSPAM-Result",
2921
head = _ds_create_header_field(data);
2925
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
2927
nt_add (block->headers, (void *) head);
2930
LOG (LOG_CRIT, ERR_MEM_ALLOC);
2933
if (CTX->source == DSS_NONE) {
2935
time_t t = time(NULL);
2938
snprintf(data, sizeof(data), "X-DSPAM-Processed: %s", buf);
2939
head = _ds_create_header_field(data);
2943
LOGDEBUG("appending header %s: %s", head->heading, head->data);
2945
nt_add(block->headers, (void *) head);
2948
LOG (LOG_CRIT, ERR_MEM_ALLOC);
2951
if (CTX->source != DSS_ERROR) {
2952
snprintf(data, sizeof(data), "X-DSPAM-Confidence: %01.4f",
2954
head = _ds_create_header_field(data);
2958
LOGDEBUG("appending header %s: %s", head->heading, head->data);
2960
nt_add(block->headers, (void *) head);
2963
LOG (LOG_CRIT, ERR_MEM_ALLOC);
2965
if (_ds_match_attribute(agent_config, "ImprobabilityDrive", "on"))
2967
float probability = CTX->confidence;
2969
if (probability > 0.999999)
2970
probability = 0.999999;
2971
if (CTX->result == DSR_ISINNOCENT) {
2976
snprintf(data, sizeof(data), "X-DSPAM-Improbability: 1 in %.0f "
2977
"chance of being %s",
2978
1.0+(100*(probability / (1-probability))), as);
2979
head = _ds_create_header_field(data);
2983
LOGDEBUG("appending header %s: %s", head->heading, head->data);
2985
nt_add(block->headers, (void *) head);
2988
LOG (LOG_CRIT, ERR_MEM_ALLOC);
2992
snprintf(data, sizeof(data), "X-DSPAM-Probability: %01.4f",
2995
head = _ds_create_header_field(data);
2999
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3001
nt_add (block->headers, (void *) head);
3004
LOG (LOG_CRIT, ERR_MEM_ALLOC);
3006
if (CTX->training_mode != DST_NOTRAIN && ATX->signature[0] != 0) {
3007
snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
3009
head = _ds_create_header_field(data);
3012
if (strlen(ATX->signature)<5)
3014
LOGDEBUG("WARNING: Signature not generated, or invalid");
3017
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3019
nt_add (block->headers, (void *) head);
3022
LOG (LOG_CRIT, ERR_MEM_ALLOC);
3025
if (CTX->result == DSR_ISSPAM && (ATX->managed_group[0] || (_ds_pref_val(ATX->PTX, "localStore")[0])))
3027
snprintf(data, sizeof(data), "X-DSPAM-User: %s", CTX->username);
3028
head = _ds_create_header_field(data);
3032
LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3034
nt_add (block->headers, (void *) head);
3037
LOG (LOG_CRIT, ERR_MEM_ALLOC);
3040
if (!strcmp(_ds_pref_val(ATX->PTX, "showFactors"), "on")) {
3042
if (CTX->factors != NULL) {
3043
snprintf(data, sizeof(data), "X-DSPAM-Factors: %d",
3044
CTX->factors->items);
3045
node_ft = c_nt_first(CTX->factors, &c_ft);
3046
while(node_ft != NULL) {
3047
struct dspam_factor *f = (struct dspam_factor *) node_ft->ptr;
3049
strlcat(data, ",\n\t", sizeof(data));
3050
snprintf(scratch, sizeof(scratch), "%s, %2.5f",
3051
f->token_name, f->value);
3052
strlcat(data, scratch, sizeof(data));
3054
node_ft = c_nt_next(CTX->factors, &c_ft);
3056
head = _ds_create_header_field(data);
3060
LOGDEBUG("appending header %s: %s", head->heading, head->data);
3062
nt_add(block->headers, (void *) head);
3067
} /* CTX->source != DSS_ERROR */
3074
* embed_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3077
* Embed the DSPAM signature in all relevant parts of the message
3080
* CTX DSPAM context containing the message
3081
* ATX Agent context defining processing behavior
3084
* returns 0 on success, standard errors on failure
3087
int embed_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3088
struct nt_node *node_nt;
3090
char toplevel_boundary[128] = { 0 };
3091
ds_message_part_t block;
3094
if (CTX->training_mode == DST_NOTRAIN || ! ATX->signature[0])
3097
node_nt = c_nt_first (CTX->message->components, &c_nt);
3099
if (node_nt == NULL || node_nt->ptr == NULL)
3102
block = node_nt->ptr;
3104
/* Signed messages are handled differently */
3106
if (block->media_subtype == MST_SIGNED)
3107
return embed_signed(CTX, ATX);
3109
if (block->media_type == MT_MULTIPART && block->terminating_boundary != NULL)
3111
strlcpy(toplevel_boundary, block->terminating_boundary,
3112
sizeof(toplevel_boundary));
3115
while (node_nt != NULL)
3117
char *body_close = NULL, *dup = NULL;
3119
block = node_nt->ptr;
3121
/* Append signature to blocks when... */
3125
/* Either a text section, or this is a non-multipart message AND...*/
3126
&& (block->media_type == MT_TEXT
3127
|| (block->boundary == NULL && i == 0
3128
&& block->media_type != MT_MULTIPART))
3129
&& (toplevel_boundary[0] == 0 || (block->body && block->body->used)))
3131
if (block->content_disposition == PCD_ATTACHMENT)
3133
node_nt = c_nt_next (CTX->message->components, &c_nt);
3138
/* Some email clients reformat HTML parts, and require that we include
3139
* the signature before the HTML close tags (because they're stupid)
3142
if (body_close == NULL &&
3143
block->body != NULL &&
3144
block->body->data != NULL &&
3145
block->media_subtype == MST_HTML)
3148
body_close = strcasestr(block->body->data, "</body");
3150
body_close = strcasestr(block->body->data, "</html");
3153
/* Save and truncate everything after and including the close tag */
3156
dup = strdup (body_close);
3157
block->body->used -= (long) strlen (dup);
3161
buffer_cat (block->body, "\n");
3162
buffer_cat (block->body, SIGNATURE_BEGIN);
3163
buffer_cat (block->body, ATX->signature);
3164
buffer_cat (block->body, SIGNATURE_END);
3165
buffer_cat (block->body, "\n\n");
3169
buffer_cat (block->body, dup);
3170
buffer_cat (block->body, "\n\n");
3175
node_nt = c_nt_next (CTX->message->components, &c_nt);
3182
* embed_signed(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3185
* Embed the DSPAM signature within a signed message
3188
* CTX DSPAM context containing message
3189
* ATX Agent context defining processing behavior
3192
* returns 0 on success, standard errors on failure
3195
int embed_signed(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3196
struct nt_node *node_nt, *node_block, *parent;
3198
ds_message_part_t block, newblock;
3200
char scratch[256], data[256];
3202
node_block = c_nt_first (CTX->message->components, &c_nt);
3203
if (node_block == NULL || node_block->ptr == NULL)
3206
block = node_block->ptr;
3208
/* Construct a new block to contain the signed message */
3210
newblock = (ds_message_part_t) malloc(sizeof(struct _ds_message_part));
3211
if (newblock == NULL)
3214
newblock->headers = nt_create(NT_PTR);
3215
if (newblock->headers == NULL)
3218
newblock->boundary = NULL;
3219
newblock->terminating_boundary= block->terminating_boundary;
3220
newblock->encoding = block->encoding;
3221
newblock->original_encoding = block->original_encoding;
3222
newblock->media_type = block->media_type;
3223
newblock->media_subtype = block->media_subtype;
3224
newblock->body = buffer_create (NULL);
3225
newblock->original_signed_body= NULL;
3227
/* Move the relevant headers from the main part to the new block */
3230
node_nt = c_nt_first(block->headers, &c_nt);
3231
while(node_nt != NULL) {
3232
field = node_nt->ptr;
3234
if (!strcasecmp(field->heading, "Content-Type") ||
3235
!strcasecmp(field->heading, "Content-Disposition"))
3237
struct nt_node *old = node_nt;
3238
node_nt = c_nt_next(block->headers, &c_nt);
3240
parent->next = node_nt;
3242
block->headers->first = node_nt;
3243
nt_add(newblock->headers, field);
3245
block->headers->items--;
3250
node_nt = c_nt_next(block->headers, &c_nt);
3253
/* Create a new top-level boundary */
3254
snprintf(scratch, sizeof(scratch), "DSPAM_MULTIPART_EX-%ld", (long)getpid());
3255
block->terminating_boundary = strdup(scratch);
3257
/* Create a new content-type field */
3258
block->media_type = MT_MULTIPART;
3259
block->media_subtype = MST_MIXED;
3260
snprintf(data, sizeof(data), "Content-Type: multipart/mixed; boundary=%s", scratch);
3261
field = _ds_create_header_field(data);
3263
nt_add(block->headers, field);
3265
/* Insert the new block right below the top headers and blank body */
3266
node_nt = nt_node_create(newblock);
3267
if (node_nt == NULL)
3269
node_nt->next = node_block->next;
3270
node_block->next = node_nt;
3271
CTX->message->components->items++;
3273
/* Strip the old terminating boundary */
3276
node_nt = c_nt_first (CTX->message->components, &c_nt);
3279
if (!node_nt->next && parent) {
3280
parent->next = node_nt->next;
3281
CTX->message->components->items--;
3282
CTX->message->components->insert = NULL;
3283
_ds_destroy_block(node_nt->ptr);
3288
node_nt = node_nt->next;
3291
/* Create a new message part containing only the boundary delimiter */
3293
newblock = (ds_message_part_t)
3294
malloc(sizeof(struct _ds_message_part));
3295
if (newblock == NULL)
3298
newblock->headers = nt_create(NT_PTR);
3299
if (newblock->headers == NULL)
3302
newblock->boundary = NULL;
3303
newblock->terminating_boundary= strdup(scratch);
3304
newblock->encoding = EN_7BIT;
3305
newblock->original_encoding = EN_7BIT;
3306
newblock->media_type = MT_TEXT;
3307
newblock->media_subtype = MST_PLAIN;
3308
newblock->body = buffer_create (NULL);
3309
newblock->original_signed_body= NULL;
3310
nt_add (CTX->message->components, newblock);
3312
/* Create a new message part containing the signature */
3314
newblock = (ds_message_part_t) malloc(sizeof(struct _ds_message_part));
3315
if (newblock == NULL)
3318
newblock->headers = nt_create(NT_PTR);
3319
if (newblock->headers == NULL)
3322
snprintf(data, sizeof(data), "%s--\n\n", scratch);
3323
newblock->boundary = NULL;
3324
newblock->terminating_boundary= strdup(data);
3325
newblock->encoding = EN_7BIT;
3326
newblock->original_encoding = EN_7BIT;
3327
newblock->media_type = MT_TEXT;
3328
newblock->media_subtype = MST_PLAIN;
3329
snprintf (scratch, sizeof (scratch),
3330
"%s%s%s\n", SIGNATURE_BEGIN, ATX->signature, SIGNATURE_END);
3331
newblock->body = buffer_create (scratch);
3332
newblock->original_signed_body= NULL;
3334
field = _ds_create_header_field ("Content-Type: text/plain");
3335
nt_add (newblock->headers, field);
3336
snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
3337
nt_add (newblock->headers, _ds_create_header_field(data));
3338
nt_add (CTX->message->components, newblock);
3344
if (newblock->headers)
3345
nt_destroy(newblock->headers);
3349
LOG (LOG_CRIT, ERR_MEM_ALLOC);
3354
* tracksources(DSPAM_CTX *CTX)
3357
* Track the source address of a message, report to syslog and/or RABL
3360
* CTX DSPAM context containing filter results and message
3363
* returns 0 on success, standard errors on failure
3366
int tracksource(DSPAM_CTX *CTX) {
3369
if (!dspam_getsource (CTX, ip, sizeof (ip)))
3371
if (CTX->totals.innocent_learned + CTX->totals.innocent_classified > 2500) {
3372
if (CTX->result == DSR_ISSPAM &&
3373
_ds_match_attribute(agent_config, "TrackSources", "spam")) {
3375
char dropfile[MAX_FILENAME_LENGTH];
3376
LOG (LOG_INFO, "spam detected from %s", ip);
3377
if (_ds_read_attribute(agent_config, "RABLQueue")) {
3378
snprintf(dropfile, sizeof(dropfile), "%s/%s",
3379
_ds_read_attribute(agent_config, "RABLQueue"), ip);
3380
file = fopen(dropfile, "w");
3385
if (CTX->result != DSR_ISSPAM &&
3386
_ds_match_attribute(agent_config, "TrackSources", "nonspam"))
3388
LOG (LOG_INFO, "innocent message from %s", ip);
3398
* has_virus(buffer *message)
3401
* Call ClamAV to determine if the message has a virus
3404
* message pointer to buffer containing message for scanning
3407
* returns 1 if virus, 0 otherwise
3410
int has_virus(buffer *message) {
3411
struct sockaddr_in addr;
3415
int port = atoi(_ds_read_attribute(agent_config, "ClamAVPort"));
3417
char *host = _ds_read_attribute(agent_config, "ClamAVHost");
3421
sockfd = socket(AF_INET, SOCK_STREAM, 0);
3422
memset(&addr, 0, sizeof(struct sockaddr_in));
3423
addr.sin_family = AF_INET;
3424
addr.sin_addr.s_addr = inet_addr(host);
3425
addr.sin_port = htons(port);
3426
addr_len = sizeof(struct sockaddr_in);
3427
LOGDEBUG("Connecting to %s:%d for virus check", host, port);
3428
if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
3429
LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3433
setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
3435
sock = fdopen(sockfd, "r+");
3436
fprintf(sock, "STREAM\r\n");
3438
if ((fgets(buf, sizeof(buf), sock))!=NULL && !strncmp(buf, "PORT", 4)) {
3439
int s_port = atoi(buf+5);
3440
if (feed_clam(s_port, message)==0) {
3441
if ((fgets(buf, sizeof(buf), sock))!=NULL) {
3442
if (!strstr(buf, ": OK"))
3454
* feed_clam(int port, buffer *message)
3457
* Feed a stream to ClamAV for virus detection
3460
* sockfd port number of stream
3461
* message pointer to buffer containing message for scanning
3464
* returns 0 on success
3467
int feed_clam(int port, buffer *message) {
3468
struct sockaddr_in addr;
3469
int sockfd, r, addr_len;
3472
long size = strlen(message->data);
3473
char *host = _ds_read_attribute(agent_config, "ClamAVHost");
3475
sockfd = socket(AF_INET, SOCK_STREAM, 0);
3476
memset(&addr, 0, sizeof(struct sockaddr_in));
3477
addr.sin_family = AF_INET;
3478
addr.sin_addr.s_addr = inet_addr(host);
3479
addr.sin_port = htons(port);
3480
addr_len = sizeof(struct sockaddr_in);
3481
LOGDEBUG("Connecting to %s:%d for virus stream transmission", host, port);
3482
if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
3483
LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3487
setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
3490
r = send(sockfd, message->data+sent, size-sent, 0);
3504
* is_blacklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3507
* Determine if the source address of the message is blacklisted
3510
* CTX DSPAM context containing the message
3511
* ATX Agent context defining processing behavior
3514
* returns 1 if blacklisted, 0 otherwise
3517
int is_blacklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3519
/* No cygwin support for IP Blacklisting */
3524
struct attribute *attrib;
3525
struct addrinfo *res = NULL;
3526
struct sockaddr_in saddr;
3533
if (!dspam_getsource (CTX, ip, sizeof (ip))) {
3535
ptr = strtok(ip, ".");
3536
while(ptr != NULL && i>=0 && i<4) {
3538
ptr = strtok(NULL, ".");
3539
if (ptr == NULL && i<4)
3544
snprintf(host, sizeof(host), "%s.%s.%s.%s.",
3545
octet[0], octet[1], octet[2], octet[3]);
3547
attrib = _ds_find_attribute(agent_config, "Lookup");
3548
while(attrib != NULL) {
3550
snprintf(lookup, sizeof(lookup), "%s%s", host, attrib->value);
3551
error = getaddrinfo(lookup, NULL, NULL, &res);
3555
memcpy(&saddr, res->ai_addr, sizeof(struct sockaddr));
3556
inet_ntoa_r(saddr.sin_addr, buff, sizeof(buff));
3557
if (!strcmp(buff, "127.0.0.2")) {
3558
STATUS("Blacklisted (%s)", attrib->value);
3564
attrib = attrib->next;
3573
* is_blocklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3576
* Determine if the source address of the message is blocklisted on
3577
* the destination user's blocklist
3580
* CTX DSPAM context containing the message
3581
* ATX Agent context defining processing behavior
3584
* returns 1 if blacklisted, 0 otherwise
3587
int is_blocklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3588
char filename[MAX_FILENAME_LENGTH];
3590
int blocklisted = 0;
3591
_ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
3592
LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
3593
: CTX->username), "blocklist");
3594
file = fopen(filename, "r");
3596
char *heading = _ds_find_header(CTX->message, "From", 0);
3599
char *dup = strdup(heading);
3600
char *domain = strchr(dup, '@');
3603
for(i=0;domain[i] && domain[i]!='\r' && domain[i]!='\n'
3604
&& domain[i]!='>' && !isspace((int) domain[i]);i++) { }
3606
while((fgets(buf, sizeof(buf), file))!=NULL) {
3608
if (!strcasecmp(buf, domain+1)) {
3621
* daemon_start(AGENT_CTX *ATX)
3624
* Launch into daemon mode and start listener
3627
* ATX Agent context defining processing behavior
3630
* returns 0 on successful termination
3634
int daemon_start(AGENT_CTX *ATX) {
3641
pthread_mutex_init(&__lock, NULL);
3642
libdspam_init(_ds_read_attribute(agent_config, "StorageDriver"));
3644
LOG(LOG_INFO, INFO_DAEMON_START);
3646
while(__daemon_run) {
3647
pidfile = _ds_read_attribute(agent_config, "ServerPID");
3649
DTX.CTX = dspam_create (NULL, NULL,
3650
_ds_read_attribute(agent_config, "Home"),
3654
LOG(LOG_ERR, ERR_CORE_INIT);
3658
set_libdspam_attributes(DTX.CTX);
3659
DTX.flags = DRF_STATEFUL;
3665
if (dspam_init_driver (&DTX))
3667
LOG (LOG_WARNING, ERR_DRV_INIT);
3673
file = fopen(pidfile, "w");
3675
LOG(LOG_ERR, ERR_IO_FILE_WRITE, pidfile, strerror(errno));
3677
fprintf(file, "%ld\n", (long) getpid());
3682
LOGDEBUG("spawning daemon listener");
3684
if (daemon_listen(&DTX)) {
3685
LOG(LOG_CRIT, ERR_DAEMON_FAIL);
3689
LOG(LOG_WARNING, "received signal. waiting for processing threads to exit.");
3690
while(__num_threads) {
3694
select(0, NULL, NULL, NULL, &tv);
3697
LOG(LOG_WARNING, "daemon is down.");
3703
dspam_shutdown_driver(&DTX);
3704
dspam_destroy(DTX.CTX);
3708
LOG(LOG_WARNING, "reloading configuration");
3711
_ds_destroy_config(agent_config);
3713
agent_config = read_config(NULL);
3714
if (!agent_config) {
3715
LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
3724
LOG(LOG_WARNING, INFO_DAEMON_EXIT);
3725
pthread_mutex_destroy(&__lock);
3726
libdspam_shutdown();
3733
* load_aggregated_prefs(AGENT_CTX *ATX, const char *username)
3736
* Load and aggregate system+user preferences
3739
* ATX Agent context defining processing behavior
3740
* username Target user
3743
* pointer to aggregated preference structure, NULL on failure
3746
agent_pref_t load_aggregated_prefs(AGENT_CTX *ATX, const char *username) {
3747
agent_pref_t PTX = NULL;
3748
agent_pref_t STX = NULL;
3749
agent_pref_t UTX = NULL;
3751
LOGDEBUG("loading preferences for user %s", username);
3752
UTX = _ds_pref_load(agent_config, username,
3753
_ds_read_attribute(agent_config, "Home"), ATX->dbh);
3755
if (!UTX && _ds_match_attribute(agent_config, "FallbackDomains", "on")) {
3756
char *domain = strchr(username, '@');
3758
UTX = _ds_pref_load(agent_config,
3760
_ds_read_attribute(agent_config, "Home"), ATX->dbh);
3761
if (UTX && !strcmp(_ds_pref_val(UTX, "fallbackDomain"), "on")) {
3762
LOGDEBUG("empty prefs found. falling back to %s", domain);
3772
UTX = _ds_pref_load(agent_config, NULL,
3773
_ds_read_attribute(agent_config, "Home"), ATX->dbh);
3776
STX = _ds_pref_load(agent_config, NULL,
3777
_ds_read_attribute(agent_config, "Home"), ATX->dbh);
3779
if (!STX || STX[0] == 0) {
3783
LOGDEBUG("default preferences empty. reverting to dspam.conf preferences.");
3784
STX = pref_config();
3786
LOGDEBUG("loaded default preferences externally");
3789
PTX = _ds_pref_aggregate(STX, UTX);
3798
for(j=0;PTX[j];j++) {
3799
LOGDEBUG("aggregated preference '%s' => '%s'",
3800
PTX[j]->attribute, PTX[j]->value);
3809
* do_notifications(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3812
* Evaluate and send notifications as necessary
3816
* ATX Agent context defining processing behavior
3819
* returns 0 on success, standard errors in failure
3822
int do_notifications(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3823
char filename[MAX_FILENAME_LENGTH];
3826
/* First run notification */
3828
if (_ds_match_attribute(agent_config, "Notifications", "on")) {
3829
_ds_userdir_path(filename,
3830
_ds_read_attribute(agent_config, "Home"),
3831
LOOKUP(ATX->PTX, CTX->username), "firstrun");
3832
file = fopen(filename, "r");
3834
LOGDEBUG("sending firstrun.txt to %s (%s): %s",
3835
CTX->username, filename, strerror(errno));
3836
send_notice(ATX, "firstrun.txt", ATX->mailer_args, CTX->username);
3837
file = fopen(filename, "w");
3839
fprintf(file, "%ld\n", (long) time(NULL));
3848
/* First spam notification */
3850
if (CTX->result == DSR_ISSPAM &&
3851
_ds_match_attribute(agent_config, "Notifications", "on"))
3853
_ds_userdir_path(filename,
3854
_ds_read_attribute(agent_config, "Home"),
3855
LOOKUP(ATX->PTX, CTX->username), "firstspam");
3856
file = fopen(filename, "r");
3858
LOGDEBUG("sending firstspam.txt to %s (%s): %s",
3859
CTX->username, filename, strerror(errno));
3860
send_notice(ATX, "firstspam.txt", ATX->mailer_args, CTX->username);
3861
file = fopen(filename, "w");
3863
fprintf(file, "%ld\n", (long) time(NULL));
3871
/* Quarantine size notification */
3873
if (_ds_match_attribute(agent_config, "Notifications", "on")) {
3875
char qfile[MAX_FILENAME_LENGTH];
3877
_ds_userdir_path(qfile, _ds_read_attribute(agent_config, "Home"),
3878
LOOKUP(ATX->PTX, CTX->username), "mbox");
3880
if (!stat(qfile, &s) && s.st_size > 1024*1024*2) {
3881
_ds_userdir_path(qfile, _ds_read_attribute(agent_config, "Home"),
3882
LOOKUP(ATX->PTX, CTX->username), "mboxwarn");
3883
if (stat(qfile, &s)) {
3886
f = fopen(qfile, "w");
3888
fprintf(f, "%ld", (long) time(NULL));
3891
send_notice(ATX, "quarantinefull.txt", ATX->mailer_args, CTX->username);