1 /* $Id: dspam.c,v 1.412 2011/11/10 00:26:00 tomhendr Exp $ */
2
3 /*
4 DSPAM
5 COPYRIGHT (C) 2002-2012 DSPAM PROJECT
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as
9 published by the Free Software Foundation, either version 3 of the
10 License, or (at your option) any later version.
11
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 Affero General Public License for more details.
16
17 You should have received a copy of the GNU Affero General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 */
21
22 /*
23 * dspam.c - primary dspam processing agent
24 *
25 * DESCRIPTION
26 * The agent provides a commandline interface to the libdspam core engine
27 * and also provides advanced functions such as a daemonized LMTP server,
28 * extended groups, and other agent features outlined in the documentation.
29 *
30 * This codebase is the full client/processing engine. See dspamc.c for
31 * the lightweight client-only codebase.
32 */
33
34 #ifdef HAVE_CONFIG_H
35 #include <auto-config.h>
36 #endif
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <strings.h>
42 #include <ctype.h>
43 #include <errno.h>
44 #ifdef HAVE_UNISTD_H
45 #include <unistd.h>
46 #include <pwd.h>
47 #endif
48 #include <sys/types.h>
49 #include <signal.h>
50 #include <sys/stat.h>
51 #include <netdb.h>
52 #include <sys/socket.h>
53 #include <sysexits.h>
54
55 #ifdef _WIN32
56 #include <io.h>
57 #include <process.h>
58 #define WIDEXITED(x) 1
59 #define WEXITSTATUS(x) (x)
60 #include <windows.h>
61 #else
62 #include <sys/wait.h>
63 #include <sys/param.h>
64 #endif
65 #include "config.h"
66 #include "util.h"
67 #include "read_config.h"
68
69 #ifdef DAEMON
70 #include <pthread.h>
71 #include "daemon.h"
72 #include "client.h"
73 #endif
74
75 #ifdef TIME_WITH_SYS_TIME
76 # include <sys/time.h>
77 # include <time.h>
78 #else
79 # ifdef HAVE_SYS_TIME_H
80 # include <sys/time.h>
81 # else
82 # include <time.h>
83 # endif
84 #endif
85
86 #ifdef EXT_LOOKUP
87 #include "external_lookup.h"
88 int verified_user = 0;
89 #endif
90
91 #include "dspam.h"
92 #include "agent_shared.h"
93 #include "pref.h"
94 #include "libdspam.h"
95 #include "language.h"
96 #include "buffer.h"
97 #include "base64.h"
98 #include "heap.h"
99 #include "pref.h"
100 #include "config_api.h"
101
102 #define USE_LMTP (_ds_read_attribute(agent_config, "DeliveryProto") && !strcmp(_ds_read_attribute(agent_config, "DeliveryProto"), "LMTP"))
103 #define USE_SMTP (_ds_read_attribute(agent_config, "DeliveryProto") && !strcmp(_ds_read_attribute(agent_config, "DeliveryProto"), "SMTP"))
104 #define LOOKUP(A, B) ((_ds_pref_val(A, "localStore")[0]) ? _ds_pref_val(A, "localStore") : B)
105
106
107 int
main(int argc,char * argv[])108 main (int argc, char *argv[])
109 {
110 AGENT_CTX ATX; /* agent configuration */
111 buffer *message = NULL; /* input message */
112 int agent_init = 0; /* agent is initialized */
113 int driver_init = 0; /* storage driver is initialized */
114 int pwent_cache_init = 0; /* cache for username and uid is initialized */
115 int exitcode = EXIT_SUCCESS;
116 struct nt_node *node_nt;
117 struct nt_c c_nt;
118
119 srand ((long) time(NULL) ^ (long) getpid ());
120 umask (006); /* rw-rw---- */
121 setbuf (stdout, NULL); /* unbuffered output */
122 #ifdef DEBUG
123 DO_DEBUG = 0;
124 #endif
125
126 #ifdef DAEMON
127 pthread_mutex_init(&__syslog_lock, NULL);
128 #endif
129
130 /* Cache my username and uid for trusted user security */
131
132 if (!init_pwent_cache()) {
133 LOG(LOG_ERR, ERR_AGENT_RUNTIME_USER);
134 exitcode = EXIT_FAILURE;
135 goto BAIL;
136 } else
137 pwent_cache_init = 1;
138
139 /* Read dspam.conf into global config structure (ds_config_t) */
140
141 agent_config = read_config(NULL);
142 if (!agent_config) {
143 LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
144 exitcode = EXIT_FAILURE;
145 goto BAIL;
146 }
147
148 if (!_ds_read_attribute(agent_config, "Home")) {
149 LOG(LOG_ERR, ERR_AGENT_DSPAM_HOME);
150 exitcode = EXIT_FAILURE;
151 goto BAIL;
152 }
153
154 /* Set up an agent context to define the behavior of the processor */
155
156 if (initialize_atx(&ATX)) {
157 LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
158 exitcode = EXIT_FAILURE;
159 goto BAIL;
160 } else {
161 agent_init = 1;
162 }
163
164 if (process_arguments(&ATX, argc, argv)) {
165 LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
166 exitcode = EXIT_FAILURE;
167 goto BAIL;
168 }
169
170 /* Switch into daemon mode if --daemon was specified on the commandline */
171
172 #ifdef DAEMON
173 #ifdef TRUSTED_USER_SECURITY
174 if (ATX.operating_mode == DSM_DAEMON && ATX.trusted)
175 #else
176 if (ATX.operating_mode == DSM_DAEMON)
177 #endif
178 {
179 exitcode = daemon_start(&ATX);
180
181 if (agent_init) {
182 nt_destroy(ATX.users);
183 nt_destroy(ATX.recipients);
184 }
185
186 if (agent_config)
187 _ds_destroy_config(agent_config);
188
189 pthread_mutex_destroy(&__syslog_lock);
190 if (pwent_cache_init)
191 free(__pw_name);
192 exit(exitcode);
193 }
194 #endif
195
196 if (apply_defaults(&ATX)) {
197 LOG(LOG_ERR, ERR_AGENT_INIT_ATX);
198 exitcode = EXIT_FAILURE;
199 goto BAIL;
200 }
201
202 if (check_configuration(&ATX)) {
203 LOG(LOG_ERR, ERR_AGENT_MISCONFIGURED);
204 exitcode = EXIT_FAILURE;
205 goto BAIL;
206 }
207
208 /* Read the message in and apply ParseTo services */
209
210 message = read_stdin(&ATX);
211 if (message == NULL) {
212 exitcode = EXIT_FAILURE;
213 goto BAIL;
214 }
215
216 if (ATX.users->items == 0)
217 {
218 LOG(LOG_ERR, ERR_AGENT_USER_UNDEFINED);
219 fprintf (stderr, "%s\n", SYNTAX);
220 exitcode = EXIT_FAILURE;
221 goto BAIL;
222 }
223
224 /* Perform client-based processing of message if --client was specified */
225
226 #ifdef DAEMON
227 if (ATX.client_mode &&
228 _ds_read_attribute(agent_config, "ClientIdent") &&
229 (_ds_read_attribute(agent_config, "ClientHost") ||
230 _ds_read_attribute(agent_config, "ServerDomainSocketPath")))
231 {
232 exitcode = client_process(&ATX, message);
233 if (exitcode<0) {
234 LOG(LOG_ERR, ERR_CLIENT_EXIT, exitcode);
235 }
236 } else {
237 #endif
238
239 /* Primary (non-client) processing procedure */
240
241 if (libdspam_init(_ds_read_attribute(agent_config, "StorageDriver"))) {
242 LOG(LOG_CRIT, ERR_DRV_INIT);
243 exitcode = EXIT_FAILURE;
244 goto BAIL;
245 }
246
247 if (dspam_init_driver (NULL))
248 {
249 LOG (LOG_WARNING, ERR_DRV_INIT);
250 exitcode = EXIT_FAILURE;
251 goto BAIL;
252 } else {
253 driver_init = 1;
254 }
255
256 ATX.results = nt_create(NT_PTR);
257 if (ATX.results == NULL) {
258 LOG(LOG_CRIT, ERR_MEM_ALLOC);
259 exitcode = EUNKNOWN;
260 goto BAIL;
261 }
262
263 exitcode = process_users(&ATX, message);
264 if (exitcode) {
265 LOGDEBUG("process_users() failed on error %d", exitcode);
266 } else {
267 exitcode = 0;
268 node_nt = c_nt_first(ATX.results, &c_nt);
269 while(node_nt) {
270 agent_result_t result = (agent_result_t) node_nt->ptr;
271 if (result->exitcode)
272 exitcode--;
273 node_nt = c_nt_next(ATX.results, &c_nt);
274 }
275 }
276 nt_destroy(ATX.results);
277 node_nt = NULL;
278
279 #ifdef DAEMON
280 }
281 #endif
282
283 BAIL:
284
285 if (message)
286 buffer_destroy(message);
287
288 if (agent_init) {
289 nt_destroy(ATX.users);
290 nt_destroy(ATX.recipients);
291 }
292
293 #ifdef DAEMON
294 if (agent_init) {
295 if (!ATX.client_mode) {
296 #endif
297 if (driver_init)
298 dspam_shutdown_driver(NULL);
299 libdspam_shutdown();
300 #ifdef DAEMON
301 }
302 }
303 #endif
304
305 if (agent_config)
306 _ds_destroy_config(agent_config);
307
308 if (pwent_cache_init)
309 free(__pw_name);
310 #ifdef DAEMON
311 pthread_mutex_destroy(&__syslog_lock);
312 // pthread_exit(0);
313 #endif
314 exit(exitcode);
315 }
316
317 /*
318 * process_message(AGENT_CTX *ATX, buffer *message, const char *username)
319 *
320 * DESCRIPTION
321 * Core message processing / interface to libdspam
322 * This function should be called once for each destination user
323 *
324 * INPUT ARGUMENTS
325 * ATX Agent context defining processing behavior
326 * message Buffer structure containing the message
327 * username Destination user
328 *
329 * RETURN VALUES
330 * The processing result is returned:
331 *
332 * DSR_ISINNOCENT Message is innocent
333 * DSR_ISSPAM Message is spam
334 * (other) Error code (see libdspam.h)
335 */
336
337 int
process_message(AGENT_CTX * ATX,buffer * message,const char * username,char ** result_string)338 process_message (
339 AGENT_CTX *ATX,
340 buffer * message,
341 const char *username,
342 char **result_string)
343 {
344 DSPAM_CTX *CTX = NULL; /* (lib)dspam context */
345 ds_message_t components;
346 char *copyback;
347 int have_signature = 0;
348 int result, i;
349 int internally_canned = 0;
350
351 ATX->timestart = _ds_gettime(); /* set tick count to get run time */
352
353 if (message->data == NULL) {
354 LOGDEBUG("empty message provided");
355 return EINVAL;
356 }
357
358 /* Create a dspam context based on the agent context */
359
360 CTX = ctx_init(ATX, username);
361 if (CTX == NULL) {
362 LOG (LOG_WARNING, ERR_CORE_INIT);
363 result = EUNKNOWN;
364 goto RETURN;
365 }
366
367 /* Configure libdspam's storage properties, then attach storage */
368
369 set_libdspam_attributes(CTX);
370 if (ATX->sockfd && ATX->dbh == NULL)
371 ATX->dbh = _ds_connect(CTX);
372
373 /* Re-Establish database connection (if failed) */
374
375 if (attach_context(CTX, ATX->dbh)) {
376 if (ATX->sockfd) {
377 ATX->dbh = _ds_connect(CTX);
378 LOG(LOG_ERR, ERR_CORE_REATTACH);
379
380 if (attach_context(CTX, ATX->dbh)) {
381 LOG(LOG_ERR, ERR_CORE_ATTACH);
382 result = EUNKNOWN;
383 goto RETURN;
384 }
385 } else {
386 LOG(LOG_ERR, ERR_CORE_ATTACH);
387 result = EUNKNOWN;
388 goto RETURN;
389 }
390 }
391
392 /* Parse and decode the message into our message structure (ds_message_t) */
393
394 components = _ds_actualize_message (message->data);
395 if (components == NULL) {
396 LOG (LOG_ERR, ERR_AGENT_PARSER_FAILED);
397 result = EUNKNOWN;
398 goto RETURN;
399 }
400
401 CTX->message = components;
402
403 #ifdef CLAMAV
404 /* Check for viruses */
405
406 if (_ds_read_attribute(agent_config, "ClamAVPort") &&
407 _ds_read_attribute(agent_config, "ClamAVHost") &&
408 CTX->source != DSS_ERROR &&
409 strcmp(_ds_pref_val(ATX->PTX, "optOutClamAV"), "on"))
410 {
411 if (has_virus(message)) {
412 char ip[32];
413 CTX->result = DSR_ISSPAM;
414 CTX->probability = 1.0;
415 CTX->confidence = 1.0;
416 STATUS("A virus was detected in the message contents");
417 result = DSR_ISSPAM;
418 strcpy(CTX->class, LANG_CLASS_VIRUS);
419 internally_canned = 1;
420 if(!_ds_match_attribute(agent_config, "TrackSources", "virus")) {
421 if (!dspam_getsource (CTX, ip, sizeof (ip)))
422 {
423 LOG(LOG_WARNING, "virus warning: infected message from %s", ip);
424 }
425 }
426 }
427 }
428 #endif
429
430 /* Check for a domain blocklist (user-based setting) */
431
432 if (is_blocklisted(CTX, ATX)) {
433 CTX->result = DSR_ISSPAM;
434 CTX->probability = 1.0;
435 CTX->confidence = 1.0;
436 strcpy(CTX->class, LANG_CLASS_BLOCKLISTED);
437 internally_canned = 1;
438 }
439
440 /* Check for an RBL blacklist (system-based setting) */
441
442 if (CTX->classification == DSR_NONE &&
443 _ds_read_attribute(agent_config, "Lookup"))
444 {
445 if(strcasecmp(_ds_pref_val(ATX->PTX, "ignoreRBLLookups"), "on")) {
446 int bad = is_blacklisted(CTX, ATX);
447 if (bad) {
448 if ((_ds_match_attribute(agent_config, "RBLInoculate", "on") ||
449 !strcasecmp(_ds_pref_val(ATX->PTX, "RBLInoculate"), "on")) &&
450 strcasecmp(_ds_pref_val(ATX->PTX, "RBLInoculate"), "off")) {
451 LOGDEBUG("source address is blacklisted. learning as spam.");
452 CTX->classification = DSR_ISSPAM;
453 CTX->source = DSS_INOCULATION;
454 } else {
455 CTX->result = DSR_ISSPAM;
456 CTX->probability = 1.0;
457 CTX->confidence = 1.0;
458 strcpy(CTX->class, LANG_CLASS_BLACKLISTED);
459 internally_canned = 1;
460 }
461 }
462 }
463 }
464
465 /* Process a signature if one was provided */
466
467 have_signature = find_signature(CTX, ATX);
468 if (ATX->source == DSS_CORPUS || ATX->source == DSS_NONE)
469 have_signature = 0; /* ignore sigs from corpusfed and inbound email */
470
471 char *original_username = strdup(CTX->username);
472
473 if (have_signature)
474 {
475
476 if (_ds_get_signature (CTX, &ATX->SIG, ATX->signature))
477 {
478 LOG(LOG_WARNING, ERR_AGENT_SIG_RET_FAILED, ATX->signature);
479 have_signature = 0;
480 }
481 else {
482
483 /* uid-based signatures will change the active username, so reload
484 preferences if it has changed */
485
486 CTX->signature = &ATX->SIG;
487 if (!strcasecmp(CTX->username, original_username)) {
488 if (ATX->PTX)
489 _ds_pref_free(ATX->PTX);
490 free(ATX->PTX);
491 ATX->PTX = NULL;
492
493 ATX->PTX = load_aggregated_prefs(ATX, CTX->username);
494
495 ATX->train_pristine = 0;
496 if ((_ds_match_attribute(agent_config, "TrainPristine", "on") ||
497 !strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on")) &&
498 strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "off")) {
499 ATX->train_pristine = 1;
500 }
501
502 /* Change also the mail recipient */
503 ATX->recipient = CTX->username;
504
505 }
506 }
507 } else if (CTX->operating_mode == DSM_CLASSIFY ||
508 CTX->classification != DSR_NONE)
509 {
510 CTX->flags = CTX->flags ^ DSF_SIGNATURE;
511 CTX->signature = NULL;
512 }
513
514 if (have_signature && CTX->classification != DSR_NONE) {
515
516 /*
517 * Reclassify (or retrain) message by signature
518 */
519
520 if (retrain_message(CTX, ATX) != 0) {
521 have_signature = 0;
522 result = EFAILURE;
523 goto RETURN;
524 }
525 } else {
526 CTX->signature = NULL;
527 if (! ATX->train_pristine) {
528 if (CTX->classification != DSR_NONE && CTX->source == DSS_ERROR) {
529 LOG(LOG_WARNING, ERR_AGENT_NO_VALID_SIG);
530 result = EFAILURE;
531 goto RETURN;
532 }
533 }
534
535 /*
536 * Call libdspam to process the environment we've configured
537 */
538
539 if (!internally_canned) {
540 result = dspam_process (CTX, message->data);
541 if (result != 0) {
542 result = EFAILURE;
543 goto RETURN;
544 }
545 }
546 }
547
548 result = CTX->result;
549
550 if (result == DSR_ISINNOCENT && !strcmp(CTX->class, LANG_CLASS_WHITELISTED)) {
551 STATUS("Auto-Whitelisted");
552 }
553
554 /*
555 * Send any relevant notifications to the user (first spam, etc)
556 * Only if the process was successful
557 */
558
559 if (result == DSR_ISINNOCENT || result == DSR_ISSPAM)
560 {
561 do_notifications(CTX, ATX);
562 }
563
564 /* Consult global group or classification network */
565 result = ensure_confident_result(CTX, ATX, result);
566 if (result < 0)
567 goto RETURN;
568
569 /* Inoculate other users (signature) */
570
571 if (have_signature &&
572 CTX->classification == DSR_ISSPAM &&
573 CTX->source != DSS_CORPUS &&
574 ATX->inoc_users->items > 0)
575 {
576 struct nt_node *node_int;
577 struct nt_c c_i;
578
579 node_int = c_nt_first (ATX->inoc_users, &c_i);
580 while (node_int != NULL)
581 {
582 inoculate_user (ATX, (const char *) node_int->ptr, &ATX->SIG, NULL);
583 node_int = c_nt_next (ATX->inoc_users, &c_i);
584 }
585 }
586
587 /* Inoculate other users (message) */
588
589 if (!have_signature &&
590 CTX->classification == DSR_ISSPAM &&
591 CTX->source != DSS_CORPUS &&
592 ATX->inoc_users->items > 0)
593 {
594 struct nt_node *node_int;
595 struct nt_c c_i;
596 node_int = c_nt_first (ATX->inoc_users, &c_i);
597 while (node_int != NULL)
598 {
599 inoculate_user (ATX, (const char *) node_int->ptr, NULL, message->data);
600 node_int = c_nt_next (ATX->inoc_users, &c_i);
601 }
602 inoculate_user (ATX, CTX->username, NULL, message->data);
603 result = DSR_ISSPAM;
604 CTX->result = DSR_ISSPAM;
605
606 goto RETURN;
607 }
608
609 /* Generate a signature id for the message and store */
610
611 if (internally_canned) {
612 if (CTX->signature) {
613 free(CTX->signature->data);
614 free(CTX->signature);
615 CTX->signature = NULL;
616 }
617 CTX->signature = calloc(1, sizeof(struct _ds_spam_signature));
618 if (CTX->signature) {
619 CTX->signature->length = 8;
620 CTX->signature->data = calloc(1, (CTX->signature->length));
621 }
622 }
623
624 if (internally_canned || (CTX->operating_mode == DSM_PROCESS &&
625 CTX->classification == DSR_NONE &&
626 CTX->signature != NULL))
627 {
628 int valid = 0;
629
630 while (!valid)
631 {
632 _ds_create_signature_id (CTX, ATX->signature, sizeof (ATX->signature));
633 if (_ds_verify_signature (CTX, ATX->signature))
634 valid = 1;
635 }
636 LOGDEBUG ("saving signature as %s", ATX->signature);
637
638 if (CTX->classification == DSR_NONE && CTX->training_mode != DST_NOTRAIN)
639 {
640 if (!ATX->train_pristine) {
641 int x = _ds_set_signature (CTX, CTX->signature, ATX->signature);
642 if (x) {
643 LOG(LOG_WARNING, "_ds_set_signature() failed with error %d", x);
644 }
645 }
646 }
647 }
648
649 /* Restore original username if necessary */
650
651 if (CTX->group != NULL && strcasecmp(CTX->username, original_username) != 0)
652 {
653 LOGDEBUG ("restoring original username %s after group processing as %s", original_username, CTX->username);
654 CTX->username = original_username;
655 }
656
657 /* Write .stats file for web interface */
658
659 if (CTX->training_mode != DST_NOTRAIN && _ds_match_attribute(agent_config, "WebStats", "on")) {
660 write_web_stats (
661 ATX,
662 (CTX->group == NULL || CTX->flags & DSF_MERGED) ?
663 CTX->username : CTX->group,
664 (CTX->group != NULL && CTX->flags & DSF_MERGED) ?
665 CTX->group: NULL,
666 &CTX->totals);
667 }
668
669 LOGDEBUG ("libdspam returned probability of %f", CTX->probability);
670 LOGDEBUG ("message result: %s", (result != DSR_ISSPAM) ? "NOT SPAM" : "SPAM");
671
672 /* System and User logging */
673
674 if (CTX->operating_mode != DSM_CLASSIFY &&
675 (_ds_match_attribute(agent_config, "SystemLog", "on") ||
676 _ds_match_attribute(agent_config, "UserLog", "on")))
677 {
678 log_events(CTX, ATX);
679 }
680
681 /* Fragment Store - Store 1k fragments of each message for web users who
682 * want to be able to see them from history. This requires some type of
683 * find recipe for purging
684 */
685
686 if (ATX->PTX != NULL
687 && !strcmp(_ds_pref_val(ATX->PTX, "storeFragments"), "on")
688 && CTX->source != DSS_ERROR)
689 {
690 char dirname[MAX_FILENAME_LENGTH];
691 char corpusfile[MAX_FILENAME_LENGTH];
692 char output[1024];
693 FILE *file;
694
695 _ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
696 LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group :
697 CTX->username), "frag");
698 snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s.frag",
699 dirname, ATX->signature);
700
701 LOGDEBUG("writing to frag file %s", corpusfile);
702
703 _ds_prepare_path_for(corpusfile);
704 file = fopen(corpusfile, "w");
705 if (file != NULL) {
706 char *body = strstr(message->data, "\n\n");
707 if (!body)
708 body = message->data;
709 strlcpy(output, body, sizeof(output));
710 fputs(output, file);
711 fputs("\n", file);
712 fclose(file);
713 }
714 }
715
716 /* Corpus Maker - Build a corpus in DSPAM_HOME/data/USERPATH/USER.corpus */
717
718 if (ATX->PTX != NULL && !strcmp(_ds_pref_val(ATX->PTX, "makeCorpus"), "on")) {
719 if (ATX->source != DSS_ERROR) {
720 char dirname[MAX_FILENAME_LENGTH];
721 char corpusfile[MAX_FILENAME_LENGTH];
722 FILE *file;
723
724 _ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
725 LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
726 : CTX->username), "corpus");
727 snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
728 dirname, (result == DSR_ISSPAM) ? "spam" : "nonspam",
729 ATX->signature);
730
731 LOGDEBUG("writing to corpus file %s", corpusfile);
732
733 _ds_prepare_path_for(corpusfile);
734 file = fopen(corpusfile, "w");
735 if (file != NULL) {
736 fputs(message->data, file);
737 fclose(file);
738 }
739 } else {
740 char dirname[MAX_FILENAME_LENGTH];
741 char corpusfile[MAX_FILENAME_LENGTH];
742 char corpusdest[MAX_FILENAME_LENGTH];
743
744 _ds_userdir_path(dirname, _ds_read_attribute(agent_config, "Home"),
745 LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
746 : CTX->username), "corpus");
747 snprintf(corpusdest, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
748 dirname, (result == DSR_ISSPAM) ? "spam" : "nonspam",
749 ATX->signature);
750 snprintf(corpusfile, MAX_FILENAME_LENGTH, "%s/%s/%s.msg",
751 dirname, (result == DSR_ISSPAM) ? "nonspam" : "spam",
752 ATX->signature);
753 LOGDEBUG("moving corpusfile %s -> %s", corpusfile, corpusdest);
754 _ds_prepare_path_for(corpusdest);
755 rename(corpusfile, corpusdest);
756 }
757 }
758
759 /* False positives and spam misses should return here */
760
761 if (CTX->message == NULL)
762 goto RETURN;
763
764 /* Add headers, tag, and deliver if necessary */
765
766 {
767 add_xdspam_headers(CTX, ATX);
768 }
769
770 if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag") &&
771 result == DSR_ISSPAM)
772 {
773 tag_message(ATX, CTX->message);
774 }
775
776
777 if (
778 (!strcmp(_ds_pref_val(ATX->PTX, "tagSpam"), "on")
779 && CTX->result == DSR_ISSPAM)
780 ||
781 (!strcmp(_ds_pref_val(ATX->PTX, "tagNonspam"), "on")
782 && CTX->result == DSR_ISINNOCENT)
783 )
784 {
785 i = embed_msgtag(CTX, ATX);
786 if (i<0) {
787 result = i;
788 goto RETURN;
789 }
790 }
791
792 if (strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers") &&
793 !ATX->train_pristine &&
794 (CTX->classification == DSR_NONE || internally_canned))
795 {
796 i = embed_signature(CTX, ATX);
797 if (i<0) {
798 result = i;
799 goto RETURN;
800 }
801 }
802
803 /* Reassemble message from components */
804
805 copyback = _ds_assemble_message (CTX->message, (USE_LMTP || USE_SMTP) ? "\r\n" : "\n");
806 buffer_clear (message);
807 buffer_cat (message, copyback);
808 free (copyback);
809
810 /* Track source address and report to syslog, RABL */
811
812 if ( _ds_read_attribute(agent_config, "TrackSources") &&
813 CTX->operating_mode == DSM_PROCESS &&
814 CTX->source != DSS_CORPUS &&
815 CTX->source != DSS_ERROR)
816 {
817 tracksource(CTX);
818 }
819
820 /* Print --classify output */
821
822 if (CTX->operating_mode == DSM_CLASSIFY || ATX->flags & DAF_SUMMARY)
823 {
824 char data[128];
825 FILE *fout;
826
827 switch (CTX->result) {
828 case DSR_ISSPAM:
829 strcpy(data, "Spam");
830 break;
831 default:
832 strcpy(data, "Innocent");
833 break;
834 }
835
836 if (ATX->sockfd) {
837 fout = ATX->sockfd;
838 ATX->sockfd_output = 1;
839 }
840 else {
841 fout = stdout;
842 }
843
844 fprintf(fout, "X-DSPAM-Result: %s; result=\"%s\"; class=\"%s\"; "
845 "probability=%01.4f; confidence=%02.2f; signature=%s\n",
846 CTX->username,
847 data,
848 CTX->class,
849 CTX->probability,
850 CTX->confidence,
851 (ATX->signature[0]) ? ATX->signature : "N/A");
852 }
853
854 ATX->learned = CTX->learned;
855 if (result_string)
856 *result_string = strdup(CTX->class);
857
858 RETURN:
859 if (have_signature) {
860 if (ATX->SIG.data != NULL) {
861 free(ATX->SIG.data);
862 ATX->SIG.data = NULL;
863 }
864 }
865 ATX->signature[0] = 0;
866 nt_destroy (ATX->inoc_users);
867 nt_destroy (ATX->classify_users);
868 if (CTX) {
869 if (CTX->signature == &ATX->SIG) {
870 CTX->signature = NULL;
871 } else if (CTX->signature != NULL) {
872 if (CTX->signature->data != NULL) {
873 free (CTX->signature->data);
874 }
875 free (CTX->signature);
876 CTX->signature = NULL;
877 }
878 dspam_destroy (CTX);
879 }
880 return result;
881 }
882
883 /*
884 * deliver_message(AGENT_CTX *ATX, const char *message,
885 * const char *mailer_args, const char *username, FILE *stream,
886 * int result)
887 *
888 * DESCRIPTION
889 * Deliver message to the appropriate destination. This could be one of:
890 * - Trusted/Untrusted Delivery Agent
891 * - Delivery Host (SMTP/LMTP)
892 * - Quarantine Agent
893 * - File stream (--stdout)
894 *
895 * INPUT ARGUMENTS
896 * ATX Agent context defining processing behavior
897 * message Message to be delivered
898 * mailer_args Arguments to pass to local agents via pipe()
899 * username Destination username
900 * stream File stream (if any) for stdout delivery
901 * result Message classification result (DSR_)
902 *
903 * RETURN VALUES
904 * returns 0 on success
905 * EINVAL on permanent failure
906 * EFAILURE on temporary failure
907 * EFILE local agent failure
908 */
909
910 int
deliver_message(AGENT_CTX * ATX,const char * message,const char * mailer_args,const char * username,FILE * stream,int result)911 deliver_message (
912 AGENT_CTX *ATX,
913 const char *message,
914 const char *mailer_args,
915 const char *username,
916 FILE *stream,
917 int result)
918 {
919 char args[1024];
920 char *margs, *mmargs, *arg;
921 FILE *file;
922 int rc;
923
924 #ifdef DAEMON
925
926 /* If QuarantineMailbox defined and delivering a spam, get
927 * name of recipient, truncate possible "+detail", and
928 * add the QuarantineMailbox name (that must include the "+")
929 */
930
931 if ((_ds_read_attribute(agent_config, "QuarantineMailbox")) &&
932 (result == DSR_ISSPAM)) {
933 strlcpy(args, ATX->recipient, sizeof(args));
934
935 /* strip trailing @domain, if present: */
936 arg=index(args, '@');
937 if (arg) *arg = '\0';
938
939 arg=index(args,'+');
940 if (arg != NULL) *arg='\0';
941 strlcat(args,_ds_read_attribute(agent_config, "QuarantineMailbox"),
942 sizeof(args));
943
944 /* append trailing @domain again, if it was present: */
945 arg=index(ATX->recipient, '@');
946 if (arg) strlcat (args, arg, sizeof(args));
947
948 ATX->recipient=args;
949 }
950
951 /* If (using LMTP or SMTP) and (not delivering to stdout) and
952 * (we shouldn't be delivering this to a quarantine agent)
953 * then call deliver_socket to deliver to DeliveryHost
954 */
955
956 if (
957 (USE_LMTP || USE_SMTP) && ! (ATX->flags & DAF_STDOUT) &&
958 (!(result == DSR_ISSPAM &&
959 _ds_read_attribute(agent_config, "QuarantineAgent") &&
960 ATX->PTX && !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "quarantine")))
961 )
962 {
963 return deliver_socket(ATX, message, (USE_LMTP) ? DDP_LMTP : DDP_SMTP);
964 }
965 #endif
966
967 if (message == NULL)
968 return EINVAL;
969
970 /* If we're delivering to stdout, we need to provide a classification for
971 * use by client/daemon setups where the client needs to know the result
972 * in order to support broken returnCodes.
973 */
974
975 if (ATX->sockfd && ATX->flags & DAF_STDOUT)
976 fprintf(stream, "X-Daemon-Classification: %s\n",
977 (result == DSR_ISSPAM) ? "SPAM" : "INNOCENT");
978
979 if (mailer_args == NULL) {
980 fputs (message, stream);
981 return 0;
982 }
983
984 /* Prepare local mailer args and interpolate all special symbols */
985
986 args[0] = 0;
987 margs = strdup (mailer_args);
988 mmargs = margs;
989 arg = strsep (&margs, " ");
990 while (arg != NULL)
991 {
992 char a[256], b[256];
993
994 /* Destination user */
995
996 if (!strcmp (arg, "$u") || !strcmp (arg, "\\$u") ||
997 !strcmp (arg, "%u") || !strcmp(arg, "\\%u"))
998 {
999 strlcpy(a, username, sizeof(a));
1000 }
1001
1002 /* Recipient (from RCPT TO)*/
1003
1004 else if (!strcmp (arg, "%r") || !strcmp (arg, "\\%r"))
1005 {
1006 if (ATX->recipient)
1007 strlcpy(a, ATX->recipient, sizeof(a));
1008 else
1009 strlcpy(a, username, sizeof(a));
1010 }
1011
1012 /* Sender (from MAIL FROM) */
1013
1014 else if (!strcmp (arg, "%s") || !strcmp (arg, "\\%s"))
1015 strlcpy(a, ATX->mailfrom, sizeof(a));
1016
1017 else
1018 strlcpy(a, arg, sizeof(a));
1019
1020 /* Escape special characters */
1021
1022 if (strcmp(a, "\"\"")) {
1023 size_t i;
1024 for(i=0;i<strlen(a);i++) {
1025 if (!(isalnum((unsigned char) a[i]) || a[i] == '+' || a[i] == '_' ||
1026 a[i] == '-' || a[i] == '.' || a[i] == '/' || a[i] == '@')) {
1027 strlcpy(b, a+i, sizeof(b));
1028 a[i] = '\\';
1029 a[i+1] = 0;
1030 strlcat(a, b, sizeof(a));
1031 i++;
1032 }
1033 }
1034 }
1035
1036 if (arg != NULL)
1037 strlcat (args, a, sizeof(args));
1038
1039 arg = strsep(&margs, " ");
1040
1041 if (arg) {
1042 strlcat (args, " ", sizeof (args));
1043 }
1044 }
1045 free (mmargs);
1046
1047 LOGDEBUG ("Opening pipe to LDA: %s", args);
1048 file = popen (args, "w");
1049 if (file == NULL)
1050 {
1051 LOG(LOG_ERR, ERR_LDA_OPEN, args, strerror (errno));
1052 return EFILE;
1053 }
1054
1055 /* Manage local delivery agent failures */
1056
1057 fputs (message, file);
1058 rc = pclose (file);
1059 if (rc == -1) {
1060 LOG(LOG_WARNING, ERR_LDA_STATUS, args, strerror (errno));
1061 return EFILE;
1062 } else if (WIFEXITED (rc)) {
1063 int lda_exit_code;
1064 lda_exit_code = WEXITSTATUS (rc);
1065 if (lda_exit_code == 0) {
1066 LOGDEBUG ("LDA returned success");
1067 } else {
1068 LOG(LOG_ERR, ERR_LDA_EXIT, lda_exit_code, args);
1069 if (_ds_match_attribute(agent_config, "LMTPLDAErrorsPermanent", "on"))
1070 return EINVAL;
1071 else
1072 return lda_exit_code;
1073 }
1074 }
1075 #ifndef _WIN32
1076 else if (WIFSIGNALED (rc))
1077 {
1078 int sig;
1079 sig = WTERMSIG (rc);
1080 LOG(LOG_ERR, ERR_LDA_SIGNAL, sig, args);
1081 return sig;
1082 }
1083 else
1084 {
1085 LOG(LOG_ERR, ERR_LDA_CLOSE, rc);
1086 return rc;
1087 }
1088 #endif
1089
1090 return 0;
1091 }
1092
1093 /*
1094 * tag_message(AGENT_CTX *ATX, ds_message_t message)
1095 *
1096 * DESCRIPTION
1097 * Tags a message's subject line as spam using spamSubject
1098 *
1099 * INPUT ARGUMENTS
1100 * ATX Agent context defining processing behavior
1101 * message Message structure (ds_message_t) to tag
1102 *
1103 * RETURN VALUES
1104 * returns 0 on success
1105 * EINVAL on permanent failure
1106 * EFAILURE on temporary failure
1107 * EFILE local agent failure
1108 */
1109
tag_message(AGENT_CTX * ATX,ds_message_t message)1110 int tag_message(AGENT_CTX *ATX, ds_message_t message)
1111 {
1112 ds_message_part_t block = message->components->first->ptr;
1113 struct nt_node *node_header = block->headers->first;
1114 int tagged = 0;
1115 char spam_subject[16];
1116
1117 strcpy(spam_subject, "[SPAM]");
1118 if (_ds_pref_val(ATX->PTX, "spamSubject")[0] != '\n' &&
1119 _ds_pref_val(ATX->PTX, "spamSubject")[0] != 0)
1120 {
1121 strlcpy(spam_subject, _ds_pref_val(ATX->PTX, "spamSubject"),
1122 sizeof(spam_subject));
1123 }
1124
1125 /* Only scan the first (primary) header of the message. */
1126
1127 while (node_header != NULL)
1128 {
1129 ds_header_t head;
1130
1131 head = (ds_header_t) node_header->ptr;
1132 if (head->heading && !strcasecmp(head->heading, "Subject"))
1133 {
1134
1135 /* CURRENT HEADER: Is this header already tagged? */
1136
1137 if (strncmp(head->data, spam_subject, strlen(spam_subject)))
1138 {
1139 /* Not tagged, so tag it */
1140 long subject_length = strlen(head->data)+strlen(spam_subject)+2;
1141 char *subject = malloc(subject_length);
1142 if (subject != NULL) {
1143 snprintf(subject,
1144 subject_length, "%s %s",
1145 spam_subject,
1146 head->data);
1147 free(head->data);
1148 head->data = subject;
1149 }
1150 }
1151
1152 /* ORIGINAL HEADER: Is this header already tagged? */
1153
1154 if (head->original_data != NULL &&
1155 strncmp(head->original_data, spam_subject, strlen(spam_subject)))
1156 {
1157 /* Not tagged => tag it. */
1158 long subject_length = strlen(head->original_data)+strlen(spam_subject)+2;
1159 char *subject = malloc(subject_length);
1160 if (subject != NULL) {
1161 snprintf(subject,
1162 subject_length, "%s %s",
1163 spam_subject,
1164 head->original_data);
1165 free(head->original_data);
1166 head->original_data = subject;
1167 }
1168 }
1169
1170 tagged = 1;
1171 }
1172 node_header = node_header->next;
1173 }
1174
1175 /* There doesn't seem to be a subject field, so make one */
1176
1177 if (!tagged)
1178 {
1179 char text[80];
1180 ds_header_t header;
1181 snprintf(text, sizeof(text), "Subject: %s", spam_subject);
1182 header = _ds_create_header_field(text);
1183 if (header != NULL)
1184 {
1185 #ifdef VERBOSE
1186 LOGDEBUG("appending header %s: %s", header->heading, header->data);
1187 #endif
1188 nt_add(block->headers, (void *) header);
1189 }
1190 }
1191
1192 return 0;
1193 }
1194
1195 /*
1196 * quarantine_message(AGENT_CTX *ATX, const char *message,
1197 * const char *username)
1198 *
1199 * DESCRIPTION
1200 * Quarantine a message using DSPAM's internal quarantine function
1201 *
1202 * INPUT ARGUMENTS
1203 * ATX Agent context defining processing behavior
1204 * message Text message to quarantine
1205 * username Destination user
1206 *
1207 * RETURN VALUES
1208 * returns 0 on success, standard errors on failure
1209 */
1210
1211 int
quarantine_message(AGENT_CTX * ATX,const char * message,const char * username)1212 quarantine_message (AGENT_CTX *ATX, const char *message, const char *username)
1213 {
1214 char filename[MAX_FILENAME_LENGTH];
1215 char *x, *msg;
1216 int line = 1, i;
1217 FILE *file;
1218
1219 _ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
1220 LOOKUP(ATX->PTX, username), "mbox");
1221 _ds_prepare_path_for(filename);
1222 file = fopen (filename, "a");
1223 if (file == NULL)
1224 {
1225 LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
1226 return EFILE;
1227 }
1228
1229 i = _ds_get_fcntl_lock(fileno(file));
1230 if (i) {
1231 LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
1232 return EFILE;
1233 }
1234
1235 /* Write our own "From " header if the MTA didn't give us one. This
1236 * allows for the viewing of a mailbox from elm or some other local
1237 * client that would otherwise believe the mailbox is corrupt.
1238 */
1239
1240 if (strncmp (message, "From ", 5))
1241 {
1242 char head[128];
1243 time_t tm = time (NULL);
1244
1245 snprintf (head, sizeof (head), "From QUARANTINE %s", ctime (&tm));
1246 fputs (head, file);
1247 }
1248
1249 msg = strdup(message);
1250
1251 if (msg == NULL) {
1252 LOG (LOG_CRIT, ERR_MEM_ALLOC);
1253 return EUNKNOWN;
1254 }
1255
1256 /* TODO: Is there a way to do this without a strdup/strsep ? */
1257
1258 x = strsep (&msg, "\n");
1259 while (x)
1260 {
1261 /* Quote any lines beginning with 'From ' to keep mbox from breaking */
1262
1263 if (!strncmp (x, "From ", 5) && line != 1)
1264 fputs (">", file);
1265 fputs (x, file);
1266 fputs ("\n", file);
1267 line++;
1268 x = strsep (&msg, "\n");
1269 }
1270 free (msg);
1271 fputs ("\n\n", file);
1272
1273 _ds_free_fcntl_lock(fileno(file));
1274 fclose (file);
1275
1276 return 0;
1277 }
1278
1279 /*
1280 * write_web_stats(AGENT_CTX *ATX, const char *username, const char *group,
1281 * struct _ds_spam_totals *totals)
1282 *
1283 * DESCRIPTION
1284 * Writes a .stats file in the user's data directory for use with web UI
1285 *
1286 * INPUT ARGUMENTS
1287 * ATX Agent context defining processing behavior
1288 * username Destination user
1289 * group Group membership
1290 * totals Pointer to processing totals
1291 *
1292 * RETURN VALUES
1293 * returns 0 on success, standard errors on failure
1294 */
1295
1296 int
write_web_stats(AGENT_CTX * ATX,const char * username,const char * group,struct _ds_spam_totals * totals)1297 write_web_stats (
1298 AGENT_CTX *ATX,
1299 const char *username,
1300 const char *group,
1301 struct _ds_spam_totals *totals)
1302 {
1303 char filename[MAX_FILENAME_LENGTH];
1304 FILE *file;
1305
1306 if (!totals)
1307 return EINVAL;
1308
1309 _ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
1310 LOOKUP(ATX->PTX, username), "stats");
1311 _ds_prepare_path_for (filename);
1312 file = fopen (filename, "w");
1313 if (file == NULL) {
1314 LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror (errno));
1315 return EFILE;
1316 }
1317
1318 fprintf (file, "%ld,%ld,%ld,%ld,%ld,%ld\n",
1319 MAX(0, (totals->spam_learned + totals->spam_classified) -
1320 (totals->spam_misclassified + totals->spam_corpusfed)),
1321 MAX(0, (totals->innocent_learned + totals->innocent_classified) -
1322 (totals->innocent_misclassified + totals->innocent_corpusfed)),
1323 totals->spam_misclassified, totals->innocent_misclassified,
1324 totals->spam_corpusfed, totals->innocent_corpusfed);
1325
1326 if (group)
1327 fprintf(file, "%s\n", group);
1328
1329 fclose (file);
1330 return 0;
1331 }
1332
1333 /*
1334 * inoculate_user(AGENT_CTX *ATX, const char *username,
1335 * struct _ds_spam_signature *SIG, const char *message)
1336 *
1337 * DESCRIPTION
1338 * Provide a vaccination for the spam processed to the target user
1339 *
1340 * INPUT ARGUMENTS
1341 * ATX Agent context defining processing behavior
1342 * username Target user
1343 * SIG Signature (if providing signature-based inoculation)
1344 * message Text Message (if providing message-based inoculation)
1345 *
1346 * RETURN VALUES
1347 * returns 0 on success, standard errors on failure
1348 */
1349
1350 int
inoculate_user(AGENT_CTX * ATX,const char * username,struct _ds_spam_signature * SIG,const char * message)1351 inoculate_user (
1352 AGENT_CTX *ATX,
1353 const char *username,
1354 struct _ds_spam_signature *SIG,
1355 const char *message)
1356 {
1357 DSPAM_CTX *INOC;
1358 int do_inoc = 1, result = 0;
1359 int f_all = 0;
1360
1361 LOGDEBUG ("checking if user %s requires this inoculation", username);
1362 if (user_classify(ATX, username, SIG, message) == DSR_ISSPAM) {
1363 do_inoc = 0;
1364 }
1365
1366 if (!do_inoc)
1367 {
1368 LOGDEBUG ("skipping user %s: doesn't require inoculation", username);
1369 return EFAILURE;
1370 }
1371 else
1372 {
1373 LOGDEBUG ("inoculating user %s", username);
1374
1375 if (ATX->flags & DAF_NOISE)
1376 f_all |= DSF_NOISE;
1377
1378 if (ATX->PTX != NULL &&
1379 strcmp(_ds_pref_val(ATX->PTX, "processorBias"), ""))
1380 {
1381 if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
1382 f_all |= DSF_BIAS;
1383 } else {
1384 if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
1385 f_all |= DSF_BIAS;
1386 }
1387
1388 INOC = dspam_create (username,
1389 NULL,
1390 _ds_read_attribute(agent_config, "Home"),
1391 DSM_PROCESS,
1392 f_all);
1393 if (INOC)
1394 {
1395 set_libdspam_attributes(INOC);
1396 if (attach_context(INOC, ATX->dbh)) {
1397 LOG (LOG_WARNING, ERR_CORE_ATTACH);
1398 dspam_destroy(INOC);
1399 return EUNKNOWN;
1400 }
1401
1402 INOC->classification = DSR_ISSPAM;
1403 INOC->source = DSS_INOCULATION;
1404 if (SIG)
1405 {
1406 INOC->flags |= DSF_SIGNATURE;
1407 INOC->signature = SIG;
1408 result = dspam_process (INOC, NULL);
1409 }
1410 else
1411 {
1412 result = dspam_process (INOC, message);
1413 }
1414
1415 if (SIG)
1416 INOC->signature = NULL;
1417 dspam_destroy (INOC);
1418 }
1419 }
1420
1421 return result;
1422 }
1423
1424 /*
1425 * user_classify(AGENT_CTX *ATX, const char *username,
1426 * struct _ds_spam_signature *SIG, const char *message)
1427 *
1428 * DESCRIPTION
1429 * Determine the classification of a message for another user
1430 *
1431 * INPUT ARGUMENTS
1432 * ATX Agent context defining processing behavior
1433 * username Target user
1434 * SIG Signature (if performing signature-based classification)
1435 * message Text Message (if performing message-based ciassification)
1436 *
1437 * RETURN VALUES
1438 * returns DSR_ value, standard errors on failure
1439 */
1440
1441 int
user_classify(AGENT_CTX * ATX,const char * username,struct _ds_spam_signature * SIG,const char * message)1442 user_classify (
1443 AGENT_CTX *ATX,
1444 const char *username,
1445 struct _ds_spam_signature *SIG,
1446 const char *message)
1447 {
1448 DSPAM_CTX *CLX;
1449 int result = 0;
1450 int f_all = 0;
1451
1452 if (SIG == NULL && message == NULL) {
1453 LOG(LOG_WARNING, "user_classify(): SIG == NULL, message == NULL");
1454 return EINVAL;
1455 }
1456
1457 if (ATX->flags & DAF_NOISE)
1458 f_all |= DSF_NOISE;
1459
1460 if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "")) {
1461 if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
1462 f_all |= DSF_BIAS;
1463 } else {
1464 if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
1465 f_all |= DSF_BIAS;
1466 }
1467
1468 /* First see if the user needs to be inoculated */
1469 CLX = dspam_create (username,
1470 NULL,
1471 _ds_read_attribute(agent_config, "Home"),
1472 DSM_CLASSIFY,
1473 f_all);
1474 if (CLX)
1475 {
1476 set_libdspam_attributes(CLX);
1477 if (attach_context(CLX, ATX->dbh)) {
1478 LOG (LOG_WARNING, ERR_CORE_ATTACH);
1479 dspam_destroy(CLX);
1480 return EUNKNOWN;
1481 }
1482
1483 if (SIG)
1484 {
1485 CLX->flags |= DSF_SIGNATURE;
1486 CLX->signature = SIG;
1487 result = dspam_process (CLX, NULL);
1488 }
1489 else
1490 {
1491 if (message == NULL) {
1492 LOG(LOG_WARNING, "user_classify: SIG = %ld, message = NULL\n", (unsigned long) SIG);
1493 if (SIG) CLX->signature = NULL;
1494 dspam_destroy (CLX);
1495 return EFAILURE;
1496 }
1497 result = dspam_process (CLX, message);
1498 }
1499
1500 if (SIG)
1501 CLX->signature = NULL;
1502
1503 if (result)
1504 {
1505 LOGDEBUG ("user_classify() returned error %d", result);
1506 result = EFAILURE;
1507 }
1508 else
1509 result = CLX->result;
1510
1511 dspam_destroy (CLX);
1512 }
1513
1514 return result;
1515 }
1516
1517 /*
1518 * send_notice(AGENT_CTX *ATX, const char *filename, const char *mailer_args,
1519 * const char *username)
1520 *
1521 * DESCRIPTION
1522 * Sends a canned notice to the destination user
1523 *
1524 * INPUT ARGUMENTS
1525 * ATX Agent context defining processing behavior
1526 * filename Filename of canned notice
1527 * mailer_args Local agent arguments
1528 * username Destination user
1529 *
1530 * RETURN VALUES
1531 * returns 0 on success, standard errors on failure
1532 */
1533
send_notice(AGENT_CTX * ATX,const char * filename,const char * mailer_args,const char * username)1534 int send_notice(
1535 AGENT_CTX *ATX,
1536 const char *filename,
1537 const char *mailer_args,
1538 const char *username)
1539 {
1540 FILE *f;
1541 char msgfile[MAX_FILENAME_LENGTH];
1542 buffer *b;
1543 char buf[1024];
1544 time_t now;
1545 int ret;
1546
1547 time(&now);
1548
1549 if (_ds_read_attribute(agent_config, "TxtDirectory")) {
1550 snprintf(msgfile, sizeof(msgfile), "%s/%s", _ds_read_attribute(agent_config, "TxtDirectory"), filename);
1551 } else {
1552 snprintf(msgfile, sizeof(msgfile), "%s/txt/%s", _ds_read_attribute(agent_config, "Home"), filename);
1553 }
1554 f = fopen(msgfile, "r");
1555 if (!f) {
1556 LOG(LOG_ERR, ERR_IO_FILE_OPEN, filename, strerror(errno));
1557 return EFILE;
1558 }
1559
1560 b = buffer_create(NULL);
1561 if (!b) {
1562 LOG(LOG_CRIT, ERR_MEM_ALLOC);
1563 fclose(f);
1564 return EUNKNOWN;
1565 }
1566
1567 strftime(buf,sizeof(buf), "Date: %a, %d %b %Y %H:%M:%S %z\n",
1568 localtime(&now));
1569 buffer_cat(b, buf);
1570
1571 while(fgets(buf, sizeof(buf), f)!=NULL) {
1572 char *s = buf;
1573 char *w = strstr(buf, "$u");
1574 while(w != NULL) {
1575 w[0] = 0;
1576 buffer_cat(b, s);
1577 buffer_cat(b, username);
1578 s = w+2;
1579 w = strstr(s, "$u");
1580 }
1581 buffer_cat(b, s);
1582 }
1583 fclose(f);
1584 ret = deliver_message(ATX, b->data, mailer_args, username,
1585 stdout, DSR_ISINNOCENT);
1586
1587 buffer_destroy(b);
1588
1589 return ret;
1590 }
1591
1592 /*
1593 * process_users(AGENT_CTX *ATX, buffer *message)
1594 *
1595 * DESCRIPTION
1596 * Primary processing loop: cycle through all destination users and process
1597 *
1598 * INPUT ARGUMENTS
1599 * ATX Agent context defining processing behavior
1600 * message Buffer structure containing text message
1601 *
1602 * RETURN VALUES
1603 * returns 0 on success, standard errors on failure
1604 */
1605
process_users(AGENT_CTX * ATX,buffer * message)1606 int process_users(AGENT_CTX *ATX, buffer *message) {
1607 int i = 0, have_rcpts = 0, return_code = 0, retcode = 0;
1608 struct nt_node *node_nt;
1609 struct nt_node *node_rcpt = NULL;
1610 struct nt_c c_nt, c_rcpt;
1611 buffer *parse_message;
1612 agent_result_t presult = NULL;
1613 char *plus, *atsign;
1614 char mailbox[256];
1615 FILE *fout;
1616
1617 if (ATX->sockfd) {
1618 fout = ATX->sockfd;
1619 } else {
1620 fout = stdout;
1621 }
1622
1623 node_nt = c_nt_first (ATX->users, &c_nt);
1624 if (ATX->recipients) {
1625 node_rcpt = c_nt_first (ATX->recipients, &c_rcpt);
1626 have_rcpts = ATX->recipients->items;
1627 }
1628
1629 /* Keep going as long as we have destination users */
1630
1631 while (node_nt || node_rcpt)
1632 {
1633 struct stat s;
1634 char filename[MAX_FILENAME_LENGTH];
1635 int optin, optout;
1636 char *username = NULL;
1637
1638 /* If ServerParameters specifies a --user, there will only be one
1639 * instance on the stack, but possible multiple recipients. So we
1640 * need to recycle.
1641 */
1642
1643 if (node_nt == NULL)
1644 node_nt = ATX->users->first;
1645
1646 /* Set the "current recipient" to either the next item on the rcpt stack
1647 * or the current user if not present.
1648 */
1649
1650
1651 #ifdef EXT_LOOKUP
1652 verified_user = 0;
1653 if (_ds_match_attribute(agent_config, "ExtLookup", "on")) {
1654 LOGDEBUG ("looking up user %s using %s driver.", node_nt->ptr, _ds_read_attribute(agent_config, "ExtLookupDriver"));
1655 username = external_lookup(agent_config, node_nt->ptr, username);
1656 if (username != NULL) {
1657 LOGDEBUG ("external lookup verified user %s", node_nt->ptr);
1658 verified_user = 1;
1659 if (_ds_match_attribute(agent_config, "ExtLookupMode", "map") ||
1660 _ds_match_attribute(agent_config, "ExtLookupMode", "strict")) {
1661 LOGDEBUG ("mapping address %s to uid %s", node_nt->ptr, username);
1662 node_nt->ptr = username;
1663 }
1664 } else if (_ds_match_attribute(agent_config, "ExtLookupMode", "map")) {
1665 LOGDEBUG ("no match for user %s but mode is %s. continuing...", node_nt->ptr, _ds_read_attribute(agent_config, "ExtLookupMode"));
1666 verified_user = 1;
1667 }
1668 } else {
1669 verified_user = 1;
1670 }
1671 #endif
1672 username = node_nt->ptr;
1673
1674 if (node_rcpt) {
1675 ATX->recipient = node_rcpt->ptr;
1676 node_rcpt = c_nt_next (ATX->recipients, &c_rcpt);
1677 } else {
1678
1679 /* We started out using the recipients list and it's exhausted, so quit */
1680 if (have_rcpts)
1681 break;
1682
1683 ATX->recipient = node_nt->ptr;
1684 }
1685
1686 /* If support for "+detail" is enabled, save full mailbox name for
1687 delivery and strip detail for processing */
1688
1689 if (_ds_match_attribute(agent_config, "EnablePlusedDetail", "on")) {
1690 char plused_char = '+';
1691 if (_ds_read_attribute(agent_config, "PlusedCharacter"))
1692 plused_char = _ds_read_attribute(agent_config, "PlusedCharacter")[0];
1693 strlcpy(mailbox, username, sizeof(mailbox));
1694 ATX->recipient = mailbox;
1695 if (_ds_match_attribute(agent_config, "PlusedUserLowercase", "on"))
1696 lc (username, username);
1697 plus = index(username, plused_char);
1698 if (plus) {
1699 atsign = index(plus, '@');
1700 if (atsign)
1701 strcpy(plus, atsign);
1702 else
1703 *plus='\0';
1704 }
1705 }
1706
1707 presult = calloc(1, sizeof(struct agent_result));
1708 parse_message = buffer_create(message->data);
1709 if (parse_message == NULL) {
1710 LOG(LOG_CRIT, ERR_MEM_ALLOC);
1711 presult->exitcode = ERC_PROCESS;
1712 strcpy(presult->text, ERR_MEM_ALLOC);
1713
1714 if (ATX->results) {
1715 nt_add(ATX->results, presult);
1716 if (ATX->results->nodetype == NT_CHAR)
1717 free(presult);
1718 } else
1719 free(presult);
1720
1721 presult = NULL;
1722
1723 continue;
1724 }
1725
1726 /* Determine whether to activate debug. If we're running in daemon mode,
1727 * debug is either on or off (it's a global variable), so this only
1728 * applies to running in client or local processing mode.
1729 */
1730
1731 #ifdef DEBUG
1732 if (!DO_DEBUG &&
1733 (_ds_match_attribute(agent_config, "Debug", "*") ||
1734 _ds_match_attribute(agent_config, "Debug", node_nt->ptr)))
1735 {
1736 // No DebugOpt specified; turn it on for everything
1737 if (!_ds_read_attribute(agent_config, "DebugOpt"))
1738 {
1739 DO_DEBUG = 1;
1740 }
1741 else {
1742 if (_ds_match_attribute(agent_config, "DebugOpt", "process") &&
1743 ATX->source == DSS_NONE &&
1744 ATX->operating_mode == DSM_PROCESS)
1745 {
1746 DO_DEBUG = 1;
1747 }
1748
1749 if (_ds_match_attribute(agent_config, "DebugOpt", "classify") &&
1750 ATX->operating_mode == DSM_CLASSIFY)
1751 {
1752 DO_DEBUG = 1;
1753 }
1754
1755 if (_ds_match_attribute(agent_config, "DebugOpt", "spam") &&
1756 ATX->classification == DSR_ISSPAM &&
1757 ATX->source == DSS_ERROR)
1758 {
1759 DO_DEBUG = 1;
1760 }
1761
1762 if (_ds_match_attribute(agent_config, "DebugOpt", "fp") &&
1763 ATX->classification == DSR_ISINNOCENT &&
1764 ATX->source == DSS_ERROR)
1765 {
1766 DO_DEBUG = 1;
1767 }
1768
1769 if (_ds_match_attribute(agent_config, "DebugOpt", "inoculation") &&
1770 ATX->source == DSS_INOCULATION)
1771 {
1772 DO_DEBUG = 1;
1773 }
1774
1775 if (_ds_match_attribute(agent_config, "DebugOpt", "corpus") &&
1776 ATX->source == DSS_CORPUS)
1777 {
1778 DO_DEBUG = 1;
1779 }
1780 }
1781 }
1782
1783 ATX->status[0] = 0;
1784
1785 if (DO_DEBUG) {
1786 LOGDEBUG ("DSPAM Instance Startup");
1787 LOGDEBUG ("input args: %s", ATX->debug_args);
1788 LOGDEBUG ("pass-thru args: %s", ATX->mailer_args);
1789 LOGDEBUG ("processing user %s", (const char *) node_nt->ptr);
1790 LOGDEBUG ("uid = %d, euid = %d, gid = %d, egid = %d",
1791 getuid(), geteuid(), getgid(), getegid());
1792
1793 /* Write message to dspam.messags */
1794 {
1795 FILE *f;
1796 char m[MAX_FILENAME_LENGTH];
1797 snprintf (m, sizeof (m), "%s/dspam.messages", LOGDIR);
1798 f = fopen (m, "a");
1799 if (f != NULL)
1800 {
1801 fprintf (f, "%s\n", parse_message->data);
1802 fclose (f);
1803 }
1804 }
1805 }
1806 #endif
1807
1808 /*
1809 * Determine if the user is opted in or out
1810 */
1811
1812 ATX->PTX = load_aggregated_prefs(ATX, username);
1813 if (!strcmp(_ds_pref_val(ATX->PTX, "fallbackDomain"), "on")) {
1814 if (username != NULL && strchr(username, '@')) {
1815 char *domain = strchr(username, '@');
1816 username = domain;
1817 } else {
1818 LOG(LOG_ERR, "process_users(): Can not fallback to domains for username '%s' without @domain part.", username);
1819 }
1820 }
1821
1822 ATX->train_pristine = 0;
1823 if ((_ds_match_attribute(agent_config, "TrainPristine", "on") ||
1824 !strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "on")) &&
1825 strcmp(_ds_pref_val(ATX->PTX, "trainPristine"), "off")) {
1826 ATX->train_pristine = 1;
1827 }
1828
1829 _ds_userdir_path(filename,
1830 _ds_read_attribute(agent_config, "Home"),
1831 LOOKUP(ATX->PTX, username), "dspam");
1832 optin = stat(filename, &s);
1833
1834 #ifdef HOMEDIR
1835 if (!optin && (!S_ISDIR(s.st_mode))) {
1836 optin = -1;
1837 LOG(LOG_WARNING, ERR_AGENT_OPTIN_DIR, filename);
1838 }
1839 #endif
1840
1841 _ds_userdir_path(filename,
1842 _ds_read_attribute(agent_config, "Home"),
1843 LOOKUP(ATX->PTX, username), "nodspam");
1844 optout = stat(filename, &s);
1845
1846 /* If the message is too big to process, just deliver it */
1847
1848 if (_ds_read_attribute(agent_config, "MaxMessageSize")) {
1849 if (parse_message->used >
1850 atoi(_ds_read_attribute(agent_config, "MaxMessageSize")))
1851 {
1852 LOG (LOG_INFO, "message too big, delivering");
1853 optout = 0;
1854 }
1855 }
1856
1857 /* Deliver the message if the user has opted not to be filtered */
1858
1859 optout = (optout) ? 0 : 1;
1860 optin = (optin) ? 0 : 1;
1861
1862 if /* opted out implicitly */
1863 (optout || !strcmp(_ds_pref_val(ATX->PTX, "optOut"), "on") ||
1864
1865 /* not opted in (in an opt-in system) */
1866 (_ds_match_attribute(agent_config, "Opt", "in") &&
1867 !optin && strcmp(_ds_pref_val(ATX->PTX, "optIn"), "on")))
1868 {
1869 if (ATX->flags & DAF_DELIVER_INNOCENT)
1870 {
1871 retcode =
1872 deliver_message (ATX, parse_message->data,
1873 (ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
1874 node_nt->ptr, fout, DSR_ISINNOCENT);
1875 if (retcode)
1876 presult->exitcode = ERC_DELIVERY;
1877 if (retcode == EINVAL)
1878 presult->exitcode = ERC_PERMANENT_DELIVERY;
1879 strlcpy(presult->text, ATX->status, sizeof(presult->text));
1880
1881 if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1882 ATX->sockfd_output = 1;
1883 }
1884 }
1885
1886 /* Call process_message(), then handle result appropriately */
1887
1888 else
1889 {
1890 char *result_string = NULL;
1891 int result;
1892 result = process_message (ATX, parse_message, username, &result_string);
1893 presult->classification = result;
1894
1895 #ifdef CLAMAV
1896 if (result_string && !strcmp(result_string, LANG_CLASS_VIRUS)) {
1897 if (_ds_match_attribute(agent_config, "ClamAVResponse", "reject")) {
1898 presult->classification = DSR_ISSPAM;
1899 presult->exitcode = ERC_PERMANENT_DELIVERY;
1900 strlcpy(presult->text, ATX->status, sizeof(presult->text));
1901 free(result_string);
1902 result_string = NULL;
1903 goto RSET;
1904 }
1905 else if (_ds_match_attribute(agent_config, "ClamAVResponse", "spam"))
1906 {
1907 presult->classification = DSR_ISSPAM;
1908 presult->exitcode = ERC_SUCCESS;
1909 result = DSR_ISSPAM;
1910 strlcpy(presult->text, ATX->status, sizeof(presult->text));
1911 } else {
1912 presult->classification = DSR_ISINNOCENT;
1913 presult->exitcode = ERC_SUCCESS;
1914 free(result_string);
1915 result_string = NULL;
1916 goto RSET;
1917 }
1918 }
1919 #endif
1920 free(result_string);
1921 result_string = NULL;
1922
1923 /* Exit code 99 for spam (when using broken return codes) */
1924
1925 if (_ds_match_attribute(agent_config, "Broken", "returnCodes")) {
1926 if (result == DSR_ISSPAM)
1927 return_code = 99;
1928 }
1929
1930 /*
1931 * Classify Only
1932 */
1933
1934 if (ATX->operating_mode == DSM_CLASSIFY)
1935 {
1936 node_nt = c_nt_next (ATX->users, &c_nt);
1937 _ds_pref_free(ATX->PTX);
1938 free(ATX->PTX);
1939 ATX->PTX = NULL;
1940 buffer_destroy(parse_message);
1941 free(presult);
1942 presult = NULL;
1943 i++;
1944 continue;
1945 }
1946
1947 /*
1948 * Classify and Process
1949 */
1950
1951 /* Innocent */
1952
1953 if (result != DSR_ISSPAM)
1954 {
1955 int deliver = 1;
1956
1957 /* Processing Error */
1958
1959 if (result != DSR_ISINNOCENT) {
1960 if (ATX->classification != DSR_NONE) {
1961 deliver = 0;
1962 LOG (LOG_WARNING,
1963 "process_message returned error %d. dropping message.", result);
1964 } else if (ATX->classification == DSR_NONE) {
1965 LOG (LOG_WARNING,
1966 "process_message returned error %d. delivering.", result);
1967 }
1968 }
1969
1970 /* Deliver */
1971
1972 if (deliver && ATX->flags & DAF_DELIVER_INNOCENT) {
1973 LOGDEBUG ("delivering message");
1974 retcode = deliver_message
1975 (ATX, parse_message->data,
1976 (ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
1977 node_nt->ptr, fout, DSR_ISINNOCENT);
1978
1979 if (ATX->sockfd && ATX->flags & DAF_STDOUT)
1980 ATX->sockfd_output = 1;
1981 if (retcode) {
1982 presult->exitcode = ERC_DELIVERY;
1983 if (retcode == EINVAL)
1984 presult->exitcode = ERC_PERMANENT_DELIVERY;
1985 strlcpy(presult->text, ATX->status, sizeof(presult->text));
1986
1987 if (result == DSR_ISINNOCENT &&
1988 _ds_match_attribute(agent_config, "OnFail", "unlearn") &&
1989 ATX->learned)
1990 {
1991 ATX->classification = result;
1992 ATX->source = DSS_ERROR;
1993 ATX->flags |= DAF_UNLEARN;
1994 process_message (ATX, parse_message, username, NULL);
1995 }
1996
1997 }
1998 }
1999 }
2000
2001 /* Spam */
2002
2003 else
2004 {
2005 /* Do not Deliver Spam */
2006
2007 if (! (ATX->flags & DAF_DELIVER_SPAM))
2008 {
2009 retcode = 0;
2010
2011 /* If a specific quarantine has been configured, use it */
2012
2013 if (ATX->source != DSS_CORPUS) {
2014 if (ATX->spam_args[0] != 0 ||
2015 (ATX->PTX != NULL &&
2016 ( !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag") ||
2017 !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "deliver") )
2018 )
2019 )
2020 {
2021 if (ATX->classification == DSR_NONE) {
2022 if (ATX->spam_args[0] != 0) {
2023 retcode = deliver_message
2024 (ATX, parse_message->data,
2025 (ATX->flags & DAF_STDOUT) ? NULL : ATX->spam_args,
2026 node_nt->ptr, fout, DSR_ISSPAM);
2027 if (ATX->sockfd && ATX->flags & DAF_STDOUT)
2028 ATX->sockfd_output = 1;
2029 } else {
2030 retcode = deliver_message
2031 (ATX, parse_message->data,
2032 (ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
2033 node_nt->ptr, fout, DSR_ISSPAM);
2034 if (ATX->sockfd && ATX->flags & DAF_STDOUT)
2035 ATX->sockfd_output = 1;
2036 }
2037
2038 if (retcode)
2039 presult->exitcode = ERC_DELIVERY;
2040 if (retcode == EINVAL)
2041 presult->exitcode = ERC_PERMANENT_DELIVERY;
2042 strlcpy(presult->text, ATX->status, sizeof(presult->text));
2043 }
2044 }
2045 else
2046 {
2047 /* Use standard quarantine procedure */
2048 if (ATX->source == DSS_INOCULATION ||
2049 ATX->classification == DSR_NONE)
2050 {
2051 if (ATX->flags & DAF_SUMMARY) {
2052 retcode = 0;
2053 } else {
2054 if (ATX->managed_group[0] == 0)
2055 retcode =
2056 quarantine_message (ATX, parse_message->data, username);
2057 else
2058 retcode =
2059 quarantine_message (ATX, parse_message->data,
2060 ATX->managed_group);
2061 }
2062 }
2063 }
2064
2065 if (retcode) {
2066 presult->exitcode = ERC_DELIVERY;
2067 if (retcode == EINVAL)
2068 presult->exitcode = ERC_PERMANENT_DELIVERY;
2069 strlcpy(presult->text, ATX->status, sizeof(presult->text));
2070
2071
2072 /* Unlearn the message on a local delivery failure */
2073 if (_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
2074 ATX->learned) {
2075 ATX->classification = result;
2076 ATX->source = DSS_ERROR;
2077 ATX->flags |= DAF_UNLEARN;
2078 process_message (ATX, parse_message, username, NULL);
2079 }
2080 }
2081 }
2082 }
2083
2084 /* Deliver Spam */
2085
2086 else
2087 {
2088 if (ATX->sockfd && ATX->flags & DAF_STDOUT)
2089 ATX->sockfd_output = 1;
2090 retcode = deliver_message
2091 (ATX, parse_message->data,
2092 (ATX->flags & DAF_STDOUT) ? NULL : ATX->mailer_args,
2093 node_nt->ptr, fout, DSR_ISSPAM);
2094
2095 if (retcode) {
2096 presult->exitcode = ERC_DELIVERY;
2097 if (retcode == EINVAL)
2098 presult->exitcode = ERC_PERMANENT_DELIVERY;
2099 strlcpy(presult->text, ATX->status, sizeof(presult->text));
2100
2101
2102 if (_ds_match_attribute(agent_config, "OnFail", "unlearn") &&
2103 ATX->learned) {
2104 ATX->classification = result;
2105 ATX->source = DSS_ERROR;
2106 ATX->flags |= DAF_UNLEARN;
2107 process_message (ATX, parse_message, username, NULL);
2108 }
2109 }
2110 }
2111 }
2112 }
2113
2114 #ifdef CLAMAV
2115 RSET:
2116 #endif
2117 _ds_pref_free(ATX->PTX);
2118 free(ATX->PTX);
2119 ATX->PTX = NULL;
2120 node_nt = c_nt_next (ATX->users, &c_nt);
2121
2122 if (ATX->results) {
2123 nt_add(ATX->results, presult);
2124 if (ATX->results->nodetype == NT_CHAR)
2125 free(presult);
2126 } else
2127 free(presult);
2128 presult = NULL;
2129 LOGDEBUG ("DSPAM Instance Shutdown. Exit Code: %d", return_code);
2130 buffer_destroy(parse_message);
2131 }
2132
2133 if (presult) free(presult);
2134 return return_code;
2135 }
2136 // break
2137 // load_agg
2138 // continue
2139 // return
2140
2141 /*
2142 * find_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX)
2143 *
2144 * DESCRIPTION
2145 * Find and parse DSPAM signature
2146 *
2147 * INPUT ARGUMENTS
2148 * CTX DSPAM context containing message and parameters
2149 * ATX Agent context defining processing behavior
2150 *
2151 * RETURN VALUES
2152 * returns 1 (and sets CTX->signature) if found
2153 *
2154 */
2155
find_signature(DSPAM_CTX * CTX,AGENT_CTX * ATX)2156 int find_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
2157 struct nt_node *node_nt;
2158 struct nt_c c, c2;
2159 ds_message_part_t block = NULL;
2160 char first_boundary[512];
2161 int is_signed = 0, i = 0;
2162 char *signature_begin = NULL, *signature_end, *erase_begin;
2163 int signature_length, have_signature = 0;
2164 struct nt_node *node_header;
2165
2166 first_boundary[0] = 0;
2167
2168 if (ATX->signature[0] != 0)
2169 return 1;
2170
2171 /* Iterate through each message component in search of a signature
2172 * and decode components as necessary
2173 */
2174
2175 node_nt = c_nt_first (CTX->message->components, &c);
2176 while (node_nt != NULL)
2177 {
2178 block = (ds_message_part_t) node_nt->ptr;
2179
2180 if (block->media_type == MT_MULTIPART && block->media_subtype == MST_SIGNED)
2181 is_signed = 1;
2182
2183 if (!strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers"))
2184 is_signed = 2;
2185
2186 #ifdef VERBOSE
2187 LOGDEBUG ("scanning component %d for a DSPAM signature", i);
2188 #endif
2189
2190 if (block->media_type == MT_TEXT
2191 || block->media_type == MT_MESSAGE
2192 || block->media_type == MT_UNKNOWN
2193 || (!i && block->media_type == MT_MULTIPART))
2194 {
2195 char *body;
2196
2197 /* Verbose output of each message component */
2198
2199 #ifdef VERBOSE
2200 if (DO_DEBUG) {
2201 if (block->boundary != NULL)
2202 {
2203 LOGDEBUG (" : Boundary : %s", block->boundary);
2204 }
2205 if (block->terminating_boundary != NULL)
2206 LOGDEBUG (" : Term Boundary: %s", block->terminating_boundary);
2207 LOGDEBUG (" : Encoding : %d", block->encoding);
2208 LOGDEBUG (" : Media Type : %d", block->media_type);
2209 LOGDEBUG (" : Media Subtype: %d", block->media_subtype);
2210 LOGDEBUG (" : Headers:");
2211 node_header = c_nt_first (block->headers, &c2);
2212 while (node_header != NULL)
2213 {
2214 ds_header_t header =
2215 (ds_header_t) node_header->ptr;
2216 LOGDEBUG (" %-32s %s", header->heading, header->data);
2217 node_header = c_nt_next (block->headers, &c2);
2218 }
2219 }
2220 #endif
2221
2222 body = block->body->data;
2223 if (block->encoding == EN_BASE64
2224 || block->encoding == EN_QUOTED_PRINTABLE)
2225 {
2226 if (block->content_disposition != PCD_ATTACHMENT)
2227 {
2228 #ifdef VERBOSE
2229 LOGDEBUG ("decoding message block from encoding type %d",
2230 block->encoding);
2231 #endif
2232
2233 body = _ds_decode_block (block);
2234
2235 if (is_signed)
2236 {
2237 LOGDEBUG
2238 ("message is signed. retaining original text for reassembly");
2239 block->original_signed_body = block->body;
2240 }
2241 else
2242 {
2243 block->encoding = EN_8BIT;
2244
2245 node_header = c_nt_first (block->headers, &c2);
2246 while (node_header != NULL)
2247 {
2248 ds_header_t header =
2249 (ds_header_t) node_header->ptr;
2250 if (!strcasecmp
2251 (header->heading, "Content-Transfer-Encoding"))
2252 {
2253 free (header->data);
2254 header->data = strdup ("8bit");
2255 }
2256 node_header = c_nt_next (block->headers, &c2);
2257 }
2258
2259 buffer_destroy (block->body);
2260 }
2261 block->body = buffer_create (body);
2262 free (body);
2263
2264 body = block->body->data;
2265 }
2266 }
2267
2268 if (!strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers")) {
2269 if (block->headers != NULL && !have_signature)
2270 {
2271 struct nt_node *node_header;
2272 ds_header_t head;
2273
2274 node_header = block->headers->first;
2275 while(node_header != NULL) {
2276 head = (ds_header_t) node_header->ptr;
2277 if (head->heading &&
2278 !strcasecmp(head->heading, "X-DSPAM-Signature")) {
2279 if (!strncmp(head->data, SIGNATURE_BEGIN,
2280 strlen(SIGNATURE_BEGIN)))
2281 {
2282 body = head->data;
2283 }
2284 else
2285 {
2286 strlcpy(ATX->signature, head->data, sizeof(ATX->signature));
2287 have_signature = 1;
2288 }
2289 break;
2290 }
2291 node_header = node_header->next;
2292 }
2293 }
2294 }
2295
2296 if (!ATX->train_pristine &&
2297
2298 /* Don't keep searching if we've already found the signature in the
2299 * headers, and we're using signatureLocation=headers
2300 */
2301 (!have_signature ||
2302 strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"), "headers")))
2303 {
2304 /* Look for signature */
2305 if (body != NULL)
2306 {
2307 int tight = 1;
2308 signature_begin = strstr (body, SIGNATURE_BEGIN);
2309 if (signature_begin == NULL) {
2310 signature_begin = strstr (body, LOOSE_SIGNATURE_BEGIN);
2311 tight = 0;
2312 }
2313
2314 if (signature_begin)
2315 {
2316 erase_begin = signature_begin;
2317 if (tight)
2318 signature_begin += strlen(SIGNATURE_BEGIN);
2319 else {
2320 char *loose = strstr (signature_begin, SIGNATURE_DELIMITER);
2321 if (!loose) {
2322 LOGDEBUG("found loose signature begin, but no delimiter");
2323 goto NEXT;
2324 }
2325 signature_begin = loose + strlen(SIGNATURE_DELIMITER);
2326 }
2327
2328 signature_end = signature_begin;
2329
2330 /* Find the signature's end character */
2331 while (signature_end != NULL
2332 && signature_end[0] != 0
2333 && (isalnum ((int) signature_end[0]) || signature_end[0] == 32 ||
2334 signature_end[0] == ','))
2335 {
2336 signature_end++;
2337 }
2338
2339 if (signature_end != NULL)
2340 {
2341 signature_length = signature_end - signature_begin;
2342
2343 if (signature_length < 128)
2344 {
2345 memcpy (ATX->signature, signature_begin, signature_length);
2346 ATX->signature[signature_length] = 0;
2347
2348 while(isspace( (int) ATX->signature[0]))
2349 {
2350 memmove(ATX->signature, ATX->signature+1, strlen(ATX->signature));
2351 }
2352
2353 if (strcmp(_ds_pref_val(ATX->PTX, "signatureLocation"),
2354 "headers")) {
2355
2356 if (!is_signed && ATX->classification == DSR_NONE) {
2357 memmove(erase_begin, signature_end+1, strlen(signature_end+1)+1);
2358 block->body->used = (long) strlen(body);
2359 }
2360 }
2361 have_signature = 1;
2362 LOGDEBUG ("found signature '%s'", ATX->signature);
2363 }
2364 }
2365 }
2366 }
2367 } /* TrainPristine */
2368 }
2369 NEXT:
2370 node_nt = c_nt_next (CTX->message->components, &c);
2371 i++;
2372 }
2373
2374 CTX->message->protect = is_signed;
2375
2376 return have_signature;
2377 }
2378
2379 /*
2380 * ctx_init(AGENT_CTX *ATX, const char *username)
2381 *
2382 * DESCRIPTION
2383 * Initialize a DSPAM context from an agent context
2384 *
2385 * INPUT ARGUMENTS
2386 * ATX Agent context defining processing behavior
2387 * username Destination user
2388 *
2389 * RETURN VALUES
2390 * pointer to newly allocated DSPAM context, NULL on failure
2391 *
2392 */
2393
ctx_init(AGENT_CTX * ATX,const char * username)2394 DSPAM_CTX *ctx_init(AGENT_CTX *ATX, const char *username) {
2395 /* We NEED a username. Without it we can't do much */
2396 if (username == NULL) {
2397 LOG (LOG_CRIT, ERR_AGENT_USER_UNDEFINED);
2398 return NULL;
2399 }
2400 DSPAM_CTX *CTX;
2401 char filename[MAX_FILENAME_LENGTH];
2402 char ctx_group[128] = { 0 };
2403 int f_all = 0, f_mode = DSM_PROCESS;
2404 FILE *file;
2405
2406 ATX->inoc_users = nt_create (NT_CHAR);
2407 if (ATX->inoc_users == NULL) {
2408 LOG (LOG_CRIT, ERR_MEM_ALLOC);
2409 return NULL;
2410 }
2411
2412 ATX->classify_users = nt_create (NT_CHAR);
2413 if (ATX->classify_users == NULL)
2414 {
2415 nt_destroy(ATX->inoc_users);
2416 LOG (LOG_CRIT, ERR_MEM_ALLOC);
2417 return NULL;
2418 }
2419
2420 /* Set Group Membership */
2421
2422 if (ATX->operating_mode == DSM_CLASSIFY) {
2423 LOGDEBUG ("Group support disabled in classify mode");
2424 } else if (!strcmp(_ds_pref_val(ATX->PTX, "ignoreGroups"), "on")) {
2425 LOGDEBUG ("Ignoring groups due preference ignoreGroups on");
2426 } else if (ATX->operating_mode == DSM_PROCESS) {
2427 snprintf (filename, sizeof (filename), "%s",
2428 _ds_read_attribute(agent_config, "GroupConfig"));
2429 file = fopen (filename, "r");
2430 if (file != NULL)
2431 {
2432 int is_group_member_inoculation = 0;
2433 int is_group_member_classification = 0;
2434 int is_group_member_global = 0;
2435 int is_group_member_shared = 0;
2436 int is_group_member_merged = 0;
2437 char *group;
2438 char buffer[10240];
2439
2440 while (fgets (buffer, sizeof (buffer), file) != NULL)
2441 {
2442 int do_inocgroups = 0;
2443 int do_classgroups = 0;
2444 char *type, *list, *listentry;
2445 chomp (buffer);
2446
2447 if (buffer[0] == 0 || buffer[0] == '#' || buffer[0] == ';')
2448 continue;
2449
2450 list = strdup (buffer);
2451 listentry = strdup (buffer);
2452 group = strtok (buffer, ":");
2453
2454 if (group != NULL)
2455 {
2456 type = strtok (NULL, ":");
2457 if (!type)
2458 continue;
2459
2460 /* Check if user is member of inoculation group */
2461 if (strcasecmp (type, "INOCULATION") == 0 &&
2462 ATX->classification == DSR_ISSPAM &&
2463 ATX->source != DSS_CORPUS)
2464 {
2465 if (is_group_member_shared == 1) {
2466 LOGDEBUG ("skipping innoculation group %s: user %s is already in a shared group", group, username);
2467 /* Process next entry in group file */
2468 continue;
2469 } else {
2470 char *l = list, *u;
2471 strsep (&l, ":");
2472 strsep (&l, ":");
2473 u = strsep (&l, ",");
2474 while (u != NULL) {
2475 if (strcasecmp(u,username) == 0) {
2476 LOGDEBUG ("user %s is member of inoculation group %s", username, group);
2477 is_group_member_inoculation = 1;
2478 do_inocgroups = 1;
2479 break;
2480 }
2481 u = strsep (&l, ",");
2482 }
2483 }
2484 }
2485 /* Check if user is member of classification group */
2486 else if (strcasecmp (type, "CLASSIFICATION") == 0)
2487 {
2488 if (is_group_member_shared == 1) {
2489 LOGDEBUG ("skipping classification or global group %s: user %s is already in a shared group", group, username);
2490 /* Process next entry in group file */
2491 continue;
2492 } else {
2493 char *l = list, *u;
2494 strsep (&l, ":");
2495 strsep (&l, ":");
2496 u = strsep (&l, ",");
2497 while (u != NULL) {
2498 if (u[0] == '*' && strcmp(u,"*") != 0) {
2499 if (is_group_member_classification == 1) {
2500 LOGDEBUG ("skipping global group %s: user %s is already in a classification group", group, username);
2501 break;
2502 }
2503 LOGDEBUG ("user %s is member of global group %s", username, group);
2504 is_group_member_global = 1;
2505 do_classgroups = 1;
2506 break;
2507 } else if (strcasecmp(u,username) == 0) {
2508 if (is_group_member_global == 1) {
2509 LOGDEBUG ("skipping classification group %s: user %s is already in a global group", group, username);
2510 break;
2511 }
2512 LOGDEBUG ("user %s is member of classification group %s", username, group);
2513 is_group_member_classification = 1;
2514 do_classgroups = 1;
2515 break;
2516 }
2517 u = strsep (&l, ",");
2518 }
2519 }
2520 }
2521 /* Process shared and shared,managed group */
2522 else if (strncasecmp (type, "SHARED", 6) == 0)
2523 {
2524 if (is_group_member_shared == 1) {
2525 LOGDEBUG ("skipping shared group %s: user %s is already in a shared group", group, username);
2526 /* Process next entry in group file */
2527 continue;
2528 } else if (is_group_member_merged == 1) {
2529 LOGDEBUG ("skipping shared group %s: user %s is already in a merged group", group, username);
2530 /* Process next entry in group file */
2531 continue;
2532 } else if (is_group_member_inoculation == 1) {
2533 LOGDEBUG ("skipping shared group %s: user %s is already in a inoculation group", group, username);
2534 /* Process next entry in group file */
2535 continue;
2536 } else if (is_group_member_classification == 1) {
2537 LOGDEBUG ("skipping shared group %s: user %s is already in a classification group", group, username);
2538 /* Process next entry in group file */
2539 continue;
2540 } else if (is_group_member_global == 1) {
2541 LOGDEBUG ("skipping shared group %s: user %s is already in a global group", group, username);
2542 /* Process next entry in group file */
2543 continue;
2544 } else {
2545 char *l = list, *u;
2546 strsep (&l, ":");
2547 strsep (&l, ":");
2548 u = strsep (&l, ",");
2549 while (u != NULL) {
2550 if (strcasecmp(u,username) == 0 ||
2551 strcmp(u,"*") == 0 ||
2552 (strncmp(u,"*@",2) == 0 && strchr(username,'@') != NULL && strcasecmp(u+1,strchr(username,'@')) == 0))
2553 {
2554 LOGDEBUG ("assigning user %s to shared group %s", username, group);
2555 strlcpy (ctx_group, group, sizeof (ctx_group));
2556 if (strncasecmp (type + 6, ",MANAGED", 8) == 0) {
2557 LOGDEBUG ("shared group is managed by %s", group);
2558 strlcpy (ATX->managed_group, ctx_group, sizeof(ATX->managed_group));
2559 }
2560 is_group_member_shared = 1;
2561 break;
2562 }
2563 u = strsep (&l, ",");
2564 }
2565 }
2566 /* Process next entry in group file */
2567 continue;
2568 }
2569 /* Process merged group */
2570 else if (strcasecmp (type, "MERGED") == 0 && strcasecmp(group, username) != 0)
2571 {
2572 if (is_group_member_merged == 1) {
2573 LOGDEBUG ("skipping merged group %s: user %s is already in merged group %s", group, username, ctx_group);
2574 /* Process next entry in group file */
2575 continue;
2576 } else if (is_group_member_shared == 1) {
2577 LOGDEBUG ("skipping merged group %s: user %s is already in a shared group", group, username);
2578 /* Process next entry in group file */
2579 continue;
2580 } else if (ATX->flags & DAF_MERGED) {
2581 LOGDEBUG ("BUG in DSPAM. Please report this bug:");
2582 LOGDEBUG (" --> Skipping merged group %s: user %s is already in merged group %s", group, username, ctx_group);
2583 /* Process next entry in group file */
2584 continue;
2585 } else {
2586 char *l = list, *u;
2587 strsep (&l, ":");
2588 strsep (&l, ":");
2589 u = strsep (&l, ",");
2590 while (u != NULL) {
2591 if (strcasecmp(u,username) == 0 || strcmp(u,"*") == 0 ||
2592 (strncmp(u,"*@",2) == 0 && strchr(username,'@') != NULL && strcasecmp(u+1,strchr(username,'@')) == 0))
2593 {
2594 if (is_group_member_merged == 1) {
2595 LOGDEBUG ("skipping entry %s for merged group %s. User is already in merged group.", u, group);
2596 continue;
2597 } else {
2598 LOGDEBUG ("adding user to merged group %s", group);
2599 ATX->flags |= DAF_MERGED;
2600 strlcpy(ctx_group, group, sizeof(ctx_group));
2601 is_group_member_merged = 1;
2602 }
2603 } else if ((strncmp(u,"-",1) == 0 && strcasecmp(u+1,username) == 0) ||
2604 (strncmp(u,"-*@",3) == 0 && strchr(username,'@') != NULL && strcasecmp(u+2,strchr(username,'@')) == 0))
2605 {
2606 if (is_group_member_merged == 0) {
2607 LOGDEBUG ("skipping entry %s for merged group %s. User is already not in merged group.", u, group);
2608 continue;
2609 } else {
2610 LOGDEBUG ("removing user from merged group %s", group);
2611 ATX->flags ^= DAF_MERGED;
2612 ctx_group[0] = 0;
2613 is_group_member_merged = 0;
2614 }
2615 } else {
2616 LOGDEBUG ("unhandled entry %s in merged group %s", u, group);
2617 }
2618 u = strsep (&l, ",");
2619 }
2620 }
2621 /* Process next entry in group file */
2622 continue;
2623 }
2624
2625 /*
2626 * If we are reporting a spam, report it as a spam to all other
2627 * users in the inoculation group
2628 */
2629 if (do_inocgroups)
2630 {
2631 char *l = listentry, *u;
2632 strsep (&l, ":");
2633 strsep (&l, ":");
2634 u = strsep (&l, ",");
2635 while (u != NULL) {
2636 if (strcasecmp(u,username) != 0) {
2637 LOGDEBUG ("adding user %s as target for inoculation", u);
2638 nt_add (ATX->inoc_users, u);
2639 }
2640 u = strsep (&l, ",");
2641 }
2642 }
2643 /*
2644 * When user is member of a global group or classification network
2645 * then consult all other users in the group
2646 */
2647 else if (do_classgroups)
2648 {
2649 char *l = listentry, *u;
2650 strsep (&l, ":");
2651 strsep (&l, ":");
2652 u = strsep (&l, ",");
2653 while (u != NULL) {
2654 if (u[0] == '*' && strcmp(u,"*") != 0) {
2655 /* global classification group */
2656 if (is_group_member_classification == 1) {
2657 LOGDEBUG ("skipping global group (%s) entry %s: user %s is already in a classification group", group, u, username);
2658 continue;
2659 } else if (strcasecmp(u+1,username) != 0 && is_group_member_global == 1) {
2660 LOGDEBUG ("adding %s as classification peer for %s", u+1, username);
2661 ATX->flags |= DAF_GLOBAL;
2662 nt_add (ATX->classify_users, u+1);
2663 } else {
2664 LOGDEBUG ("skipping global group entry %s for user %s", u+1, username);
2665 }
2666 } else if (strcasecmp(u,username) != 0) {
2667 /* classification network group */
2668 if (is_group_member_global == 1) {
2669 LOGDEBUG ("skipping classification group (%s) entry %s: user %s is already in a global group", group, u, username);
2670 continue;
2671 } else if (is_group_member_classification == 1) {
2672 LOGDEBUG ("adding user %s to classification network group %", u, group);
2673 nt_add (ATX->classify_users, u);
2674 } else {
2675 LOGDEBUG ("skipping classification group entry %s for user %s", u, username);
2676 }
2677 }
2678 u = strsep (&l, ",");
2679 }
2680 }
2681 }
2682 free (list);
2683 free (listentry);
2684 }
2685 fclose (file);
2686 }
2687 }
2688
2689 /* Crunch our agent context into a DSPAM context */
2690
2691 f_mode = ATX->operating_mode;
2692 f_all = DSF_SIGNATURE;
2693
2694 if (ATX->flags & DAF_UNLEARN)
2695 f_all |= DSF_UNLEARN;
2696
2697 /* If there is no preference, defer to commandline */
2698 if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "enableBNR"), "")) {
2699 if (!strcmp(_ds_pref_val(ATX->PTX, "enableBNR"), "on"))
2700 f_all |= DSF_NOISE;
2701 } else {
2702 if (ATX->flags & DAF_NOISE)
2703 f_all |= DSF_NOISE;
2704 }
2705
2706 if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "")) {
2707 if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
2708 f_all |= DSF_BIAS;
2709 } else {
2710 if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
2711 f_all |= DSF_BIAS;
2712 }
2713
2714 if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "enableWhitelist"), "")) {
2715 if (!strcmp(_ds_pref_val(ATX->PTX, "enableWhitelist"), "on"))
2716 f_all |= DSF_WHITELIST;
2717 } else {
2718 if (ATX->flags & DAF_WHITELIST)
2719 f_all |= DSF_WHITELIST;
2720 }
2721
2722 if (ATX->flags & DAF_MERGED)
2723 f_all |= DSF_MERGED;
2724
2725 CTX = dspam_create (username,
2726 ctx_group,
2727 _ds_read_attribute(agent_config, "Home"),
2728 f_mode,
2729 f_all);
2730
2731 if (CTX == NULL)
2732 return NULL;
2733
2734 if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "statisticalSedation"), "")) {
2735 CTX->training_buffer = atoi(_ds_pref_val(ATX->PTX, "statisticalSedation"));
2736 LOGDEBUG("sedation level set to: %d", CTX->training_buffer);
2737 } else if (ATX->training_buffer>=0) {
2738 CTX->training_buffer = ATX->training_buffer;
2739 LOGDEBUG("sedation level set to: %d", CTX->training_buffer);
2740 }
2741
2742 if (ATX->PTX != NULL && strcmp(_ds_pref_val(ATX->PTX, "whitelistThreshold"), ""))
2743 CTX->wh_threshold = atoi(_ds_pref_val(ATX->PTX, "whitelistThreshold"));
2744
2745 if (ATX->classification != DSR_NONE) {
2746 CTX->classification = ATX->classification;
2747 CTX->source = ATX->source;
2748 }
2749
2750 if (!( ATX->flags & DAF_FIXED_TR_MODE)
2751 && ATX->PTX != NULL
2752 && strcmp(_ds_pref_val(ATX->PTX, "trainingMode"), "")) {
2753 if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TEFT"))
2754 CTX->training_mode = DST_TEFT;
2755 else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TOE"))
2756 CTX->training_mode = DST_TOE;
2757 else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "TUM"))
2758 CTX->training_mode = DST_TUM;
2759 else if (!strcasecmp(_ds_pref_val(ATX->PTX, "trainingMode"), "NOTRAIN"))
2760 CTX->training_mode = DST_NOTRAIN;
2761 else
2762 CTX->training_mode = ATX->training_mode;
2763 } else {
2764 CTX->training_mode = ATX->training_mode;
2765 }
2766
2767 return CTX;
2768 }
2769
2770 /*
2771 * retrain_message(DSPAM_CTX *CTX, AGENT_CTX *ATX)
2772 *
2773 * DESCRIPTION
2774 * Retrain a message and perform iterative training
2775 *
2776 * INPUT ARGUMENTS
2777 * CTX DSPAM context containing the classification results
2778 * ATX Agent context defining processing behavior
2779 *
2780 * RETURN VALUES
2781 * returns 0 on success, standard errors on failure
2782 */
2783
retrain_message(DSPAM_CTX * CTX,AGENT_CTX * ATX)2784 int retrain_message(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
2785 int do_train = 1, iter = 0, ck_result = 0, t_mode = CTX->source;
2786
2787 /* Train until test conditions are met, 5 iterations max */
2788
2789 if (!_ds_match_attribute(agent_config, "TestConditionalTraining", "on")) {
2790 ck_result = dspam_process (CTX, NULL);
2791 if (ck_result != 0)
2792 return EFAILURE;
2793 } else {
2794 while (do_train && iter < 5)
2795 {
2796 DSPAM_CTX *CLX;
2797 int match;
2798
2799 match = (CTX->classification == DSR_ISSPAM) ?
2800 DSR_ISSPAM : DSR_ISINNOCENT;
2801 iter++;
2802
2803 ck_result = dspam_process (CTX, NULL);
2804 if (ck_result != 0)
2805 return EFAILURE;
2806
2807 /* Only subtract innocent values once */
2808 CTX->source = DSS_CORPUS;
2809
2810 LOGDEBUG ("reclassifying iteration %d result: %d", iter, ck_result);
2811
2812 if (t_mode == DSS_CORPUS)
2813 do_train = 0;
2814
2815 /* Only attempt test-conditional training on a mature corpus */
2816
2817 if (CTX->totals.innocent_learned+CTX->totals.innocent_classified<1000 &&
2818 CTX->classification == DSR_ISSPAM)
2819 {
2820 do_train = 0;
2821 }
2822 else
2823 {
2824 int f_all = DSF_SIGNATURE;
2825
2826 /* CLX = Classify Context */
2827 if (ATX->flags & DAF_NOISE)
2828 f_all |= DSF_NOISE;
2829
2830 if (ATX->PTX != NULL &&
2831 strcmp(_ds_pref_val(ATX->PTX, "processorBias"), ""))
2832 {
2833 if (!strcmp(_ds_pref_val(ATX->PTX, "processorBias"), "on"))
2834 f_all |= DSF_BIAS;
2835 } else {
2836 if (_ds_match_attribute(agent_config, "ProcessorBias", "on"))
2837 f_all |= DSF_BIAS;
2838 }
2839
2840 CLX = dspam_create (CTX->username,
2841 CTX->group,
2842 _ds_read_attribute(agent_config, "Home"),
2843 DSM_CLASSIFY,
2844 f_all);
2845 if (!CLX)
2846 break;
2847
2848 CLX->training_mode = CTX->training_mode;
2849
2850 set_libdspam_attributes(CLX);
2851 if (attach_context(CLX, ATX->dbh)) {
2852 dspam_destroy(CLX);
2853 break;
2854 }
2855
2856 CLX->signature = &ATX->SIG;
2857 ck_result = dspam_process (CLX, NULL);
2858 if (ck_result < 0) {
2859 CLX->signature = NULL;
2860 dspam_destroy(CLX);
2861 return EFAILURE;
2862 }
2863 if (ck_result == 0 || CLX->result == match)
2864 do_train = 0;
2865 CLX->signature = NULL;
2866 dspam_destroy (CLX);
2867 }
2868 }
2869
2870 CTX->source = DSS_ERROR;
2871 }
2872
2873 return 0;
2874 }
2875
2876 /*
2877 * ensure_confident_result(DSPAM_CTX *CTX, AGENT_CTX *ATX, int result)
2878 *
2879 * DESCRIPTION
2880 * Consult a global group or classification network if
2881 * the user's filter instance isn't confident in its result
2882 *
2883 * INPUT ARGUMENTS
2884 * CTX DSPAM context containing classification results
2885 * ATX Agent context defining processing behavior
2886 * result DSR_ processing result
2887 *
2888 * RETURN VALUES
2889 * returns result networks believe the message should be
2890 */
2891
2892 /* ensure_confident_result: consult global group or
2893 clasification network if the user isn't confident in their result */
2894
ensure_confident_result(DSPAM_CTX * CTX,AGENT_CTX * ATX,int result)2895 int ensure_confident_result(DSPAM_CTX *CTX, AGENT_CTX *ATX, int result) {
2896 int was_spam = 0;
2897
2898 /* Exit if no users available for global group or classification network */
2899 if (ATX->classify_users && ATX->classify_users->items == 0)
2900 return result;
2901
2902 /* global groups or classification network only operates on SPAM or INNOCENT */
2903 if (strcmp(CTX->class, LANG_CLASS_WHITELISTED) ==0 ||
2904 strcmp(CTX->class, LANG_CLASS_VIRUS) == 0 ||
2905 strcmp(CTX->class, LANG_CLASS_BLOCKLISTED) == 0 ||
2906 strcmp(CTX->class, LANG_CLASS_BLACKLISTED) == 0)
2907 {
2908 LOGDEBUG ("Not consulting %s group: message class is %s",
2909 (ATX->flags & DAF_GLOBAL) ? "global" : "classification",
2910 CTX->class);
2911 return result;
2912 }
2913
2914 /* Defer to global group */
2915 if (ATX->flags & DAF_GLOBAL &&
2916 ((CTX->totals.innocent_learned + CTX->totals.innocent_corpusfed < 1000 ||
2917 CTX->totals.spam_learned + CTX->totals.spam_corpusfed < 250) ||
2918 (CTX->training_mode == DST_NOTRAIN))
2919 )
2920 {
2921 if (result == DSR_ISSPAM) {
2922 was_spam = 1;
2923 CTX->result = DSR_ISINNOCENT;
2924 result = DSR_ISINNOCENT;
2925 }
2926 CTX->confidence = 0.60f;
2927 }
2928
2929 if (result != DSR_ISSPAM &&
2930 CTX->operating_mode == DSM_PROCESS &&
2931 CTX->classification == DSR_NONE &&
2932 CTX->confidence < 0.65)
2933 {
2934 LOGDEBUG ("consulting %s group member list", (ATX->flags & DAF_GLOBAL) ? "global" : "classification");
2935
2936 struct nt_node *node_int;
2937 struct nt_c c_i;
2938
2939 node_int = c_nt_first (ATX->classify_users, &c_i);
2940 while (node_int != NULL && result != DSR_ISSPAM) {
2941 LOGDEBUG ("checking result for user %s", (const char *) node_int->ptr);
2942 result = user_classify (ATX, (const char *) node_int->ptr, CTX->signature, NULL);
2943 if (result == DSR_ISSPAM) {
2944 LOGDEBUG ("CLASSIFY CATCH: %s", (const char *) node_int->ptr);
2945 CTX->result = result;
2946 }
2947 node_int = c_nt_next (ATX->classify_users, &c_i);
2948 }
2949
2950 /* If the global user thinks it's spam, and the user thought it was
2951 * innocent, retrain the user as a false negative.
2952 */
2953 if (result == DSR_ISSPAM && !was_spam) {
2954 LOGDEBUG ("re-adding as %s", LANG_CLASS_SPAM);
2955 DSPAM_CTX *CTC = malloc(sizeof(DSPAM_CTX));
2956 if (CTC == NULL) {
2957 LOG(LOG_CRIT, ERR_MEM_ALLOC);
2958 return EUNKNOWN;
2959 }
2960 memcpy(CTC, CTX, sizeof(DSPAM_CTX));
2961 CTC->operating_mode = DSM_PROCESS;
2962 CTC->classification = DSR_ISSPAM;
2963 CTC->source = DSS_ERROR;
2964 CTC->flags |= DSF_SIGNATURE;
2965 dspam_process (CTC, NULL);
2966 memcpy(&CTX->totals, &CTC->totals, sizeof(struct _ds_spam_totals));
2967 free(CTC);
2968 CTC = NULL;
2969 CTX->totals.spam_misclassified--;
2970 strncpy(CTX->class, LANG_CLASS_SPAM, sizeof(CTX->class));
2971 /* should we be resetting CTX->probability and CTX->confidence here as well? */
2972 CTX->result = result;
2973 /* If the global user thinks it's innocent, and the user thought it was
2974 * spam, retrain the user as a false positive
2975 */
2976 } else if (result == DSR_ISINNOCENT && was_spam) {
2977 LOGDEBUG ("re-adding as %s", LANG_CLASS_INNOCENT);
2978 DSPAM_CTX *CTC = malloc(sizeof(DSPAM_CTX));
2979 if (CTC == NULL) {
2980 LOG(LOG_CRIT, ERR_MEM_ALLOC);
2981 return EUNKNOWN;
2982 }
2983 memcpy(CTC, CTX, sizeof(DSPAM_CTX));
2984 CTC->operating_mode = DSM_PROCESS;
2985 CTC->classification = DSR_ISINNOCENT;
2986 CTC->source = DSS_ERROR;
2987 CTC->flags |= DSF_SIGNATURE;
2988 dspam_process (CTC, NULL);
2989 memcpy(&CTX->totals, &CTC->totals, sizeof(struct _ds_spam_totals));
2990 free(CTC);
2991 CTC = NULL;
2992 CTX->totals.innocent_misclassified--;
2993 strncpy(CTX->class, LANG_CLASS_INNOCENT, sizeof(CTX->class));
2994 /* should we be resetting CTX->probability and CTX->confidence here as well? */
2995 CTX->result = result;
2996 }
2997 }
2998
2999 return result;
3000 }
3001
3002 /*
3003 * log_prepare(char *buffer, char *value)
3004 *
3005 * DESCRIPTION
3006 * Prepares a value for logging by copying it to the buffer and removing
3007 * all potentially dangerous characters.
3008 *
3009 * INPUT ARGUMENTS
3010 * buffer A 256-byte buffer to store the result into
3011 * value Value to be logged or NULL
3012 *
3013 * RETURN VALUES
3014 * None
3015 */
3016
log_prepare(char * buffer,char * value)3017 static void log_prepare(char *buffer, char *value) {
3018 char *p;
3019
3020 if (!value)
3021 value = "<None Specified>";
3022 strncpy(buffer, value, 255);
3023 buffer[255] = 0;
3024 for (p=buffer; *p; p++)
3025 if (*p >= 0 && *p < 32)
3026 *p = ' ';
3027 }
3028
3029 /*
3030 * log_events(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3031 *
3032 * DESCRIPTION
3033 * Log events to system and user logs
3034 *
3035 * INPUT ARGUMENTS
3036 * CTX DSPAM context
3037 * ATX Agent context defining processing behavior
3038 *
3039 * RETURN VALUES
3040 * returns 0 on success, standard errors on failure
3041 */
3042
log_events(DSPAM_CTX * CTX,AGENT_CTX * ATX)3043 int log_events(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3044 char filename[MAX_FILENAME_LENGTH];
3045 char *subject = NULL, *from = NULL;
3046 struct nt_node *node_nt;
3047 struct nt_c c_nt;
3048 FILE *file;
3049 char class;
3050 char x[1024], subject_buf[256], from_buf[256];
3051 char *messageid = NULL;
3052
3053 if (CTX->message)
3054 messageid = _ds_find_header(CTX->message, "Message-Id");
3055
3056 if (ATX->status[0] == 0 && CTX->source == DSS_ERROR &&
3057 (!(ATX->flags & DAF_UNLEARN)))
3058 {
3059 STATUS("Retrained");
3060 }
3061
3062 if (ATX->status[0] == 0 && CTX->classification == DSR_NONE
3063 && CTX->result == DSR_ISSPAM
3064 && ATX->status[0] == 0)
3065 {
3066 if (_ds_pref_val(ATX->PTX, "spamAction")[0] == 0 ||
3067 !strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "quarantine"))
3068 {
3069 STATUS("Quarantined");
3070 } else if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "tag")) {
3071 STATUS("Tagged");
3072 } else if (!strcmp(_ds_pref_val(ATX->PTX, "spamAction"), "deliver")) {
3073 STATUS("Delivered");
3074 }
3075 }
3076
3077 if (ATX->status[0] == 0 &&
3078 CTX->classification == DSR_NONE &&
3079 CTX->result == DSR_ISINNOCENT)
3080 {
3081 STATUS("Delivered");
3082 }
3083
3084 _ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"), LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group : CTX->username), "log");
3085
3086 if (CTX->message)
3087 {
3088 node_nt = c_nt_first (CTX->message->components, &c_nt);
3089 if (node_nt != NULL)
3090 {
3091 ds_message_part_t block;
3092 block = node_nt->ptr;
3093 if (block->headers != NULL)
3094 {
3095 ds_header_t head;
3096 struct nt_node *node_header;
3097 node_header = block->headers->first;
3098 while(node_header != NULL) {
3099 head = (ds_header_t) node_header->ptr;
3100 if (head) {
3101 if (!strcasecmp(head->heading, "Subject")) {
3102 subject = head->data;
3103 if (from != NULL) break;
3104 } else if (!strcasecmp(head->heading, "From")) {
3105 from = head->data;
3106 if (subject != NULL) break;
3107 }
3108 }
3109 node_header = node_header->next;
3110 }
3111 }
3112 }
3113 }
3114
3115 if (!strcmp(CTX->class, LANG_CLASS_WHITELISTED))
3116 class = 'W';
3117 else if (!strcmp(CTX->class, LANG_CLASS_VIRUS))
3118 class = 'V';
3119 else if (!strcmp(CTX->class, LANG_CLASS_BLACKLISTED))
3120 class = 'A';
3121 else if (!strcmp(CTX->class, LANG_CLASS_BLOCKLISTED))
3122 class = 'O';
3123 else if (CTX->result == DSR_ISSPAM)
3124 class = 'S';
3125 else if (CTX->result == DSR_ISINNOCENT)
3126 class = 'I';
3127 else
3128 class = 'U';
3129
3130 if (CTX->source == DSS_ERROR) {
3131 if (CTX->classification == DSR_ISSPAM)
3132 class = 'M';
3133 else if (CTX->classification == DSR_ISINNOCENT)
3134 class = 'F';
3135 } else if (CTX->source == DSS_INOCULATION)
3136 class = 'N';
3137 else if (CTX->source == DSS_CORPUS)
3138 class = 'C';
3139
3140 if (ATX->flags & DAF_UNLEARN) {
3141 char stat[256];
3142 snprintf(stat, sizeof(stat), "Delivery Failed (%s)",
3143 (ATX->status[0]) ? ATX->status : "No error provided");
3144 STATUS("%s", stat);
3145 class = 'E';
3146 }
3147
3148 log_prepare(from_buf, from);
3149 log_prepare(subject_buf, subject);
3150
3151 /* Write USER.log */
3152
3153 if (_ds_match_attribute(agent_config, "UserLog", "on")) {
3154
3155 snprintf(x, sizeof(x), "%ld\t%c\t%s\t%s\t%s\t%s\t%s\n",
3156 (long) time(NULL),
3157 class,
3158 from_buf,
3159 ATX->signature,
3160 subject_buf,
3161 ATX->status,
3162 (messageid) ? messageid : "");
3163
3164
3165 _ds_prepare_path_for(filename);
3166 file = fopen(filename, "a");
3167 if (file != NULL) {
3168 int i = _ds_get_fcntl_lock(fileno(file));
3169 if (!i) {
3170 fputs(x, file);
3171 fputs("\n", file);
3172 _ds_free_fcntl_lock(fileno(file));
3173 } else {
3174 LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
3175 }
3176 fclose(file);
3177 }
3178 }
3179
3180 /* Write system.log */
3181
3182 if (_ds_match_attribute(agent_config, "SystemLog", "on")) {
3183 snprintf(filename, sizeof(filename), "%s/system.log",
3184 _ds_read_attribute(agent_config, "Home"));
3185 file = fopen(filename, "a");
3186 if (file != NULL) {
3187 int i = _ds_get_fcntl_lock(fileno(file));
3188 if (!i) {
3189
3190 snprintf(x, sizeof(x), "%ld\t%c\t%s\t%s\t%s\t%f\t%s\t%s\t%s\n",
3191 (long) time(NULL),
3192 class,
3193 from_buf,
3194 ATX->signature,
3195 subject_buf,
3196 _ds_gettime()-ATX->timestart,
3197 (CTX->username) ? CTX->username: "",
3198 (ATX->status) ? ATX->status : "",
3199 (messageid) ? messageid : "");
3200 fputs(x, file);
3201 _ds_free_fcntl_lock(fileno(file));
3202 } else {
3203 LOG(LOG_WARNING, ERR_IO_LOCK, filename, i, strerror(errno));
3204 }
3205 fclose(file);
3206 }
3207 }
3208 return 0;
3209 }
3210
3211 /*
3212 * add_xdspam_headers(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3213 *
3214 * DESCRIPTION
3215 * Add X-DSPAM headers to the message being processed
3216 *
3217 * INPUT ARGUMENTS
3218 * CTX DSPAM context containing message and results
3219 * ATX Agent context defining processing behavior
3220 *
3221 * RETURN VALUES
3222 * returns 0 on success, standard errors on failure
3223 */
3224
3225
add_xdspam_headers(DSPAM_CTX * CTX,AGENT_CTX * ATX)3226 int add_xdspam_headers(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3227 struct nt_node *node_nt;
3228 struct nt_c c_nt;
3229
3230 node_nt = c_nt_first (CTX->message->components, &c_nt);
3231 if (node_nt != NULL)
3232 {
3233 ds_message_part_t block = node_nt->ptr;
3234 struct nt_node *node_ft;
3235 struct nt_c c_ft;
3236 if (block != NULL && block->headers != NULL)
3237 {
3238 ds_header_t head;
3239 char data[10240];
3240 char scratch[128];
3241
3242 snprintf(data, sizeof(data), "%s: %s",
3243 (CTX->source == DSS_ERROR) ? "X-DSPAM-Reclassified" : "X-DSPAM-Result",
3244 CTX->class);
3245
3246 head = _ds_create_header_field(data);
3247 if (head != NULL)
3248 {
3249 #ifdef VERBOSE
3250 LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3251 #endif
3252 nt_add (block->headers, (void *) head);
3253 }
3254 else {
3255 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3256 }
3257
3258 if (CTX->source == DSS_NONE) {
3259 char buf[27];
3260 time_t t = time(NULL);
3261 ctime_r(&t, buf);
3262 chomp(buf);
3263 snprintf(data, sizeof(data), "X-DSPAM-Processed: %s", buf);
3264 head = _ds_create_header_field(data);
3265 if (head != NULL)
3266 {
3267 #ifdef VERBOSE
3268 LOGDEBUG("appending header %s: %s", head->heading, head->data);
3269 #endif
3270 nt_add(block->headers, (void *) head);
3271 }
3272 else
3273 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3274 }
3275
3276 if (CTX->source != DSS_ERROR) {
3277 snprintf(data, sizeof(data), "X-DSPAM-Confidence: %01.4f",
3278 CTX->confidence);
3279 head = _ds_create_header_field(data);
3280 if (head != NULL)
3281 {
3282 #ifdef VERBOSE
3283 LOGDEBUG("appending header %s: %s", head->heading, head->data);
3284 #endif
3285 nt_add(block->headers, (void *) head);
3286 }
3287 else
3288 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3289
3290 if (_ds_match_attribute(agent_config, "ImprobabilityDrive", "on"))
3291 {
3292 float probability = CTX->confidence;
3293 char *as;
3294 if (probability > 0.999999)
3295 probability = 0.999999;
3296 if (CTX->result == DSR_ISINNOCENT) {
3297 as = "spam";
3298 } else {
3299 as = "ham";
3300 }
3301 snprintf(data, sizeof(data), "X-DSPAM-Improbability: 1 in %.0f "
3302 "chance of being %s",
3303 1.0+(100*(probability / (1-probability))), as);
3304 head = _ds_create_header_field(data);
3305 if (head != NULL)
3306 {
3307 #ifdef VERBOSE
3308 LOGDEBUG("appending header %s: %s", head->heading, head->data);
3309 #endif
3310 nt_add(block->headers, (void *) head);
3311 }
3312 else
3313 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3314 }
3315
3316
3317 snprintf(data, sizeof(data), "X-DSPAM-Probability: %01.4f",
3318 CTX->probability);
3319
3320 head = _ds_create_header_field(data);
3321 if (head != NULL)
3322 {
3323 #ifdef VERBOSE
3324 LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3325 #endif
3326 nt_add (block->headers, (void *) head);
3327 }
3328 else
3329 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3330
3331 if (CTX->training_mode != DST_NOTRAIN && ATX->signature[0] != 0) {
3332 snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
3333
3334 head = _ds_create_header_field(data);
3335 if (head != NULL)
3336 {
3337 if (strlen(ATX->signature)<5)
3338 {
3339 LOGDEBUG("WARNING: Signature not generated, or invalid");
3340 }
3341 #ifdef VERBOSE
3342 LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3343 #endif
3344 nt_add (block->headers, (void *) head);
3345 }
3346 else
3347 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3348 }
3349
3350 if (CTX->result == DSR_ISSPAM && (ATX->managed_group[0] || (_ds_pref_val(ATX->PTX, "localStore")[0])))
3351 {
3352 snprintf(data, sizeof(data), "X-DSPAM-User: %s", CTX->username);
3353 head = _ds_create_header_field(data);
3354 if (head != NULL)
3355 {
3356 #ifdef VERBOSE
3357 LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3358 #endif
3359 nt_add (block->headers, (void *) head);
3360 }
3361 else
3362 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3363 }
3364
3365 if (!strcmp(_ds_pref_val(ATX->PTX, "showFactors"), "on")) {
3366
3367 if (CTX->factors != NULL) {
3368 snprintf(data, sizeof(data), "X-DSPAM-Factors: %d",
3369 CTX->factors->items);
3370 node_ft = c_nt_first(CTX->factors, &c_ft);
3371 while(node_ft != NULL) {
3372 struct dspam_factor *f = (struct dspam_factor *) node_ft->ptr;
3373 if (f) {
3374 char *s, *t;
3375 strlcat(data, ",\n\t", sizeof(data));
3376 s = f->token_name;
3377 t = scratch;
3378 while (*s && t < scratch + sizeof(scratch) - 16)
3379 if (*s >= ' ' && *s < 0x7f && *s != '%')
3380 *t++ = *s++;
3381 else
3382 t += sprintf(t, "%%%02x", (unsigned char) *s++);
3383 snprintf(t, 15, ", %2.5f", f->value);
3384 strlcat(data, scratch, sizeof(data));
3385 }
3386 node_ft = c_nt_next(CTX->factors, &c_ft);
3387 }
3388 head = _ds_create_header_field(data);
3389 if (head != NULL)
3390 {
3391 #ifdef VERBOSE
3392 LOGDEBUG("appending header %s: %s", head->heading, head->data);
3393 #endif
3394 nt_add(block->headers, (void *) head);
3395 }
3396 }
3397 }
3398
3399 } /* CTX->source != DSS_ERROR */
3400 }
3401 }
3402 return 0;
3403 }
3404
3405 /*
3406 * embed_msgtag(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3407 *
3408 * DESCRIPTION
3409 * Embed a message tag
3410 *
3411 * INPUT ARGUMENTS
3412 * CTX DSPAM context containing the message
3413 * ATX Agent context defining processing behavior
3414 *
3415 * RETURN VALUES
3416 * returns 0 on success, standard errors on failure
3417 */
3418
embed_msgtag(DSPAM_CTX * CTX,AGENT_CTX * ATX)3419 int embed_msgtag(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3420 struct nt_node *node_nt;
3421 struct nt_c c_nt;
3422 char toplevel_boundary[128] = { 0 };
3423 ds_message_part_t block;
3424 int i = 0;
3425 FILE *f;
3426 char buff[1024], msgfile[MAX_FILENAME_LENGTH];
3427 buffer *b;
3428 ATX = ATX; /* Keep compiler happy */
3429
3430 if (CTX->result != DSR_ISSPAM && CTX->result != DSR_ISINNOCENT)
3431 return EINVAL;
3432
3433 node_nt = c_nt_first (CTX->message->components, &c_nt);
3434 if (node_nt == NULL || node_nt->ptr == NULL)
3435 return EFAILURE;
3436
3437 block = node_nt->ptr;
3438
3439 /* Signed messages cannot be tagged */
3440
3441 if (block->media_subtype == MST_SIGNED)
3442 return EINVAL;
3443
3444 /* Load the message tag */
3445 if (_ds_read_attribute(agent_config, "TxtDirectory")) {
3446 snprintf(msgfile, sizeof(msgfile), "%s/msgtag.%s",
3447 _ds_read_attribute(agent_config, "TxtDirectory"),
3448 (CTX->result == DSR_ISSPAM) ? "spam" : "nonspam");
3449 } else {
3450 snprintf(msgfile, sizeof(msgfile), "%s/txt/msgtag.%s",
3451 _ds_read_attribute(agent_config, "Home"),
3452 (CTX->result == DSR_ISSPAM) ? "spam" : "nonspam");
3453 }
3454 f = fopen(msgfile, "r");
3455 if (!f) {
3456 LOG(LOG_ERR, ERR_IO_FILE_OPEN, msgfile, strerror(errno));
3457 return EFILE;
3458 }
3459 b = buffer_create(NULL);
3460 if (!b) {
3461 LOG(LOG_CRIT, ERR_MEM_ALLOC);
3462 fclose(f);
3463 return EUNKNOWN;
3464 }
3465 while(fgets(buff, sizeof(buff), f)!=NULL) {
3466 buffer_cat(b, buff);
3467 }
3468 fclose(f);
3469
3470 if (block->media_type == MT_MULTIPART && block->terminating_boundary != NULL)
3471 {
3472 strlcpy(toplevel_boundary, block->terminating_boundary,
3473 sizeof(toplevel_boundary));
3474 }
3475
3476 while (node_nt != NULL)
3477 {
3478 char *body_close = NULL, *dup = NULL;
3479
3480 block = node_nt->ptr;
3481
3482 /* Append signature to blocks when... */
3483
3484 if (block != NULL
3485
3486 /* Either a text section, or this is a non-multipart message AND...*/
3487 && (block->media_type == MT_TEXT
3488 || (block->boundary == NULL && i == 0
3489 && block->media_type != MT_MULTIPART))
3490 && (toplevel_boundary[0] == 0 || (block->body && block->body->used)))
3491 {
3492 if (block->content_disposition == PCD_ATTACHMENT)
3493 {
3494 node_nt = c_nt_next (CTX->message->components, &c_nt);
3495 i++;
3496 continue;
3497 }
3498
3499 /* Some email clients reformat HTML parts, and require that we include
3500 * the signature before the HTML close tags (because they're stupid)
3501 */
3502
3503 if (body_close == NULL &&
3504 block->body != NULL &&
3505 block->body->data != NULL &&
3506 block->media_subtype == MST_HTML)
3507
3508 {
3509 body_close = strcasestr(block->body->data, "</body");
3510 if (!body_close)
3511 body_close = strcasestr(block->body->data, "</html");
3512 }
3513
3514 /* Save and truncate everything after and including the close tag */
3515 if (body_close)
3516 {
3517 dup = strdup (body_close);
3518 block->body->used -= (long) strlen (dup);
3519 body_close[0] = 0;
3520 }
3521
3522 buffer_cat (block->body, "\n");
3523 buffer_cat (block->body, b->data);
3524
3525 if (dup)
3526 {
3527 buffer_cat (block->body, dup);
3528 free (dup);
3529 }
3530 }
3531
3532 node_nt = c_nt_next (CTX->message->components, &c_nt);
3533 i++;
3534 }
3535 buffer_destroy(b);
3536 return 0;
3537 }
3538
3539 /*
3540 * embed_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3541 *
3542 * DESCRIPTION
3543 * Embed the DSPAM signature in all relevant parts of the message
3544 *
3545 * INPUT ARGUMENTS
3546 * CTX DSPAM context containing the message
3547 * ATX Agent context defining processing behavior
3548 *
3549 * RETURN VALUES
3550 * returns 0 on success, standard errors on failure
3551 */
3552
embed_signature(DSPAM_CTX * CTX,AGENT_CTX * ATX)3553 int embed_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3554 struct nt_node *node_nt;
3555 struct nt_c c_nt;
3556 char toplevel_boundary[128] = { 0 };
3557 ds_message_part_t block;
3558 int i = 0;
3559
3560 if (CTX->training_mode == DST_NOTRAIN || ! ATX->signature[0])
3561 return 0;
3562
3563 node_nt = c_nt_first (CTX->message->components, &c_nt);
3564
3565 if (node_nt == NULL || node_nt->ptr == NULL)
3566 return EFAILURE;
3567
3568 block = node_nt->ptr;
3569
3570 /* Signed messages are handled differently */
3571
3572 if (block->media_subtype == MST_SIGNED)
3573 return embed_signed(CTX, ATX);
3574
3575 if (block->media_type == MT_MULTIPART && block->terminating_boundary != NULL)
3576 {
3577 strlcpy(toplevel_boundary, block->terminating_boundary,
3578 sizeof(toplevel_boundary));
3579 }
3580
3581 while (node_nt != NULL)
3582 {
3583 char *body_close = NULL, *dup = NULL;
3584
3585 block = node_nt->ptr;
3586
3587 /* Append signature to blocks when... */
3588
3589 if (block != NULL
3590
3591 /* Either a text section, or this is a non-multipart message AND...*/
3592 && (block->media_type == MT_TEXT
3593 || (block->boundary == NULL && i == 0
3594 && block->media_type != MT_MULTIPART))
3595 && (toplevel_boundary[0] == 0 || (block->body && block->body->used)))
3596 {
3597 if (block->content_disposition == PCD_ATTACHMENT)
3598 {
3599 node_nt = c_nt_next (CTX->message->components, &c_nt);
3600 i++;
3601 continue;
3602 }
3603
3604 /* Some email clients reformat HTML parts, and require that we include
3605 * the signature before the HTML close tags (because they're stupid)
3606 */
3607
3608 if (body_close == NULL &&
3609 block->body != NULL &&
3610 block->body->data != NULL &&
3611 block->media_subtype == MST_HTML)
3612
3613 {
3614 body_close = strcasestr(block->body->data, "</body");
3615 if (!body_close)
3616 body_close = strcasestr(block->body->data, "</html");
3617 }
3618
3619 /* Save and truncate everything after and including the close tag */
3620 if (body_close)
3621 {
3622 dup = strdup (body_close);
3623 block->body->used -= (long) strlen (dup);
3624 body_close[0] = 0;
3625 }
3626
3627 buffer_cat (block->body, "\n");
3628 buffer_cat (block->body, SIGNATURE_BEGIN);
3629 buffer_cat (block->body, ATX->signature);
3630 buffer_cat (block->body, SIGNATURE_END);
3631 buffer_cat (block->body, "\n\n");
3632
3633 if (dup)
3634 {
3635 buffer_cat (block->body, dup);
3636 buffer_cat (block->body, "\n\n");
3637 free (dup);
3638 }
3639 }
3640
3641 node_nt = c_nt_next (CTX->message->components, &c_nt);
3642 i++;
3643 }
3644 return 0;
3645 }
3646
3647 /*
3648 * embed_signed(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3649 *
3650 * DESCRIPTION
3651 * Embed the DSPAM signature within a signed message
3652 *
3653 * INPUT ARGUMENTS
3654 * CTX DSPAM context containing message
3655 * ATX Agent context defining processing behavior
3656 *
3657 * RETURN VALUES
3658 * returns 0 on success, standard errors on failure
3659 */
3660
embed_signed(DSPAM_CTX * CTX,AGENT_CTX * ATX)3661 int embed_signed(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3662 struct nt_node *node_nt, *node_block, *parent;
3663 struct nt_c c_nt;
3664 ds_message_part_t block, newblock;
3665 ds_header_t field;
3666 char scratch[256], data[256];
3667
3668 node_block = c_nt_first (CTX->message->components, &c_nt);
3669 if (node_block == NULL || node_block->ptr == NULL)
3670 return EFAILURE;
3671
3672 block = node_block->ptr;
3673
3674 /* Construct a new block to contain the signed message */
3675
3676 newblock = (ds_message_part_t) malloc(sizeof(struct _ds_message_part));
3677 if (newblock == NULL)
3678 goto MEM_ALLOC;
3679
3680 newblock->headers = nt_create(NT_PTR);
3681 if (newblock->headers == NULL)
3682 goto MEM_ALLOC;
3683
3684 newblock->boundary = NULL;
3685 newblock->terminating_boundary= block->terminating_boundary;
3686 newblock->encoding = block->encoding;
3687 newblock->original_encoding = block->original_encoding;
3688 newblock->media_type = block->media_type;
3689 newblock->media_subtype = block->media_subtype;
3690 newblock->body = buffer_create (NULL);
3691 newblock->original_signed_body= NULL;
3692
3693 /* Move the relevant headers from the main part to the new block */
3694
3695 parent = NULL;
3696 node_nt = c_nt_first(block->headers, &c_nt);
3697 while(node_nt != NULL) {
3698 field = node_nt->ptr;
3699 if (field) {
3700 if (!strcasecmp(field->heading, "Content-Type") ||
3701 !strcasecmp(field->heading, "Content-Disposition"))
3702 {
3703 struct nt_node *old = node_nt;
3704 node_nt = c_nt_next(block->headers, &c_nt);
3705 if (parent)
3706 parent->next = node_nt;
3707 else
3708 block->headers->first = node_nt;
3709 nt_add(newblock->headers, field);
3710 free(old);
3711 old = NULL;
3712 block->headers->items--;
3713 continue;
3714 }
3715 }
3716 parent = node_nt;
3717 node_nt = c_nt_next(block->headers, &c_nt);
3718 }
3719
3720 /* Create a new top-level boundary */
3721 snprintf(scratch, sizeof(scratch), "DSPAM_MULTIPART_EX-%ld", (long)getpid());
3722 block->terminating_boundary = strdup(scratch);
3723
3724 /* Create a new content-type field */
3725 block->media_type = MT_MULTIPART;
3726 block->media_subtype = MST_MIXED;
3727 snprintf(data, sizeof(data), "Content-Type: multipart/mixed; boundary=%s", scratch);
3728 field = _ds_create_header_field(data);
3729 if (field != NULL)
3730 nt_add(block->headers, field);
3731
3732 /* Insert the new block right below the top headers and blank body */
3733 node_nt = nt_node_create(newblock);
3734 if (node_nt == NULL)
3735 goto MEM_ALLOC;
3736 node_nt->next = node_block->next;
3737 node_block->next = node_nt;
3738 CTX->message->components->items++;
3739
3740 /* Strip the old terminating boundary */
3741
3742 parent = NULL;
3743 node_nt = c_nt_first (CTX->message->components, &c_nt);
3744 while (node_nt)
3745 {
3746 if (!node_nt->next && parent) {
3747 parent->next = NULL;
3748 CTX->message->components->items--;
3749 CTX->message->components->insert = NULL;
3750 _ds_destroy_block(node_nt->ptr);
3751 free(node_nt);
3752 node_nt = NULL;
3753 } else {
3754 parent = node_nt;
3755 node_nt = node_nt->next;
3756 }
3757 }
3758
3759 /* Create a new message part containing only the boundary delimiter */
3760
3761 newblock = (ds_message_part_t)
3762 malloc(sizeof(struct _ds_message_part));
3763 if (newblock == NULL)
3764 goto MEM_ALLOC;
3765
3766 newblock->headers = nt_create(NT_PTR);
3767 if (newblock->headers == NULL)
3768 goto MEM_ALLOC;
3769
3770 newblock->boundary = NULL;
3771 newblock->terminating_boundary= strdup(scratch);
3772 newblock->encoding = EN_7BIT;
3773 newblock->original_encoding = EN_7BIT;
3774 newblock->media_type = MT_TEXT;
3775 newblock->media_subtype = MST_PLAIN;
3776 newblock->body = buffer_create (NULL);
3777 newblock->original_signed_body= NULL;
3778 nt_add (CTX->message->components, newblock);
3779
3780 /* Create a new message part containing the signature */
3781
3782 newblock = (ds_message_part_t) malloc(sizeof(struct _ds_message_part));
3783 if (newblock == NULL)
3784 goto MEM_ALLOC;
3785
3786 newblock->headers = nt_create(NT_PTR);
3787 if (newblock->headers == NULL)
3788 goto MEM_ALLOC;
3789
3790 snprintf(data, sizeof(data), "%s--\n\n", scratch);
3791 newblock->boundary = NULL;
3792 newblock->terminating_boundary= strdup(data);
3793 newblock->encoding = EN_7BIT;
3794 newblock->original_encoding = EN_7BIT;
3795 newblock->media_type = MT_TEXT;
3796 newblock->media_subtype = MST_PLAIN;
3797 snprintf (scratch, sizeof (scratch),
3798 "%s%s%s\n", SIGNATURE_BEGIN, ATX->signature, SIGNATURE_END);
3799 newblock->body = buffer_create (scratch);
3800 newblock->original_signed_body= NULL;
3801
3802 field = _ds_create_header_field ("Content-Type: text/plain");
3803 nt_add (newblock->headers, field);
3804 snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
3805 nt_add (newblock->headers, _ds_create_header_field(data));
3806 nt_add (CTX->message->components, newblock);
3807
3808 return 0;
3809
3810 MEM_ALLOC:
3811 if (newblock) {
3812 if (newblock->headers)
3813 nt_destroy(newblock->headers);
3814 free(newblock);
3815 newblock = NULL;
3816 }
3817
3818 LOG (LOG_CRIT, ERR_MEM_ALLOC);
3819 return EUNKNOWN;
3820 }
3821
3822 /*
3823 * tracksources(DSPAM_CTX *CTX)
3824 *
3825 * DESCRIPTION
3826 * Track the source address of a message, report to syslog and/or RABL
3827 *
3828 * INPUT ARGUMENTS
3829 * CTX DSPAM context containing filter results and message
3830 *
3831 * RETURN VALUES
3832 * returns 0 on success, standard errors on failure
3833 */
3834
tracksource(DSPAM_CTX * CTX)3835 int tracksource(DSPAM_CTX *CTX) {
3836 char ip[32];
3837
3838 if (!dspam_getsource (CTX, ip, sizeof (ip)))
3839 {
3840 if (CTX->totals.innocent_learned + CTX->totals.innocent_classified > 2500) {
3841 if (CTX->result == DSR_ISSPAM &&
3842 strcmp(CTX->class, LANG_CLASS_VIRUS) != 0 &&
3843 _ds_match_attribute(agent_config, "TrackSources", "spam")) {
3844 FILE *file;
3845 char dropfile[MAX_FILENAME_LENGTH];
3846 LOG (LOG_INFO, "spam detected from %s", ip);
3847 if (_ds_read_attribute(agent_config, "RABLQueue")) {
3848 snprintf(dropfile, sizeof(dropfile), "%s/%s",
3849 _ds_read_attribute(agent_config, "RABLQueue"), ip);
3850 file = fopen(dropfile, "w");
3851 if (file != NULL)
3852 fclose(file);
3853 }
3854 } else if (CTX->result == DSR_ISSPAM &&
3855 strcmp(CTX->class, LANG_CLASS_VIRUS) == 0 &&
3856 _ds_match_attribute(agent_config, "TrackSources", "virus"))
3857 {
3858 LOG (LOG_INFO, "infected message from %s", ip);
3859 } else if (CTX->result != DSR_ISSPAM &&
3860 strcmp(CTX->class, LANG_CLASS_VIRUS) != 0 &&
3861 _ds_match_attribute(agent_config, "TrackSources", "nonspam"))
3862 {
3863 LOG (LOG_INFO, "innocent message from %s", ip);
3864 }
3865 }
3866 }
3867 return 0;
3868 }
3869
3870 #ifdef CLAMAV
3871
3872 /*
3873 * has_virus(buffer *message)
3874 *
3875 * DESCRIPTION
3876 * Call ClamAV to determine if the message has a virus
3877 *
3878 * INPUT ARGUMENTS
3879 * message pointer to buffer containing message for scanning
3880 *
3881 * RETURN VALUES
3882 * returns 1 if virus, 0 otherwise
3883 */
3884
has_virus(buffer * message)3885 int has_virus(buffer *message) {
3886 struct sockaddr_in addr;
3887 int sockfd;
3888 int virus = 0;
3889 int yes = 1;
3890 int port = atoi(_ds_read_attribute(agent_config, "ClamAVPort"));
3891 int addr_len;
3892 char *host = _ds_read_attribute(agent_config, "ClamAVHost");
3893 FILE *sock;
3894 FILE *sockout;
3895 char buf[128];
3896
3897 sockfd = socket(AF_INET, SOCK_STREAM, 0);
3898 if (sockfd < 0) {
3899 LOG(LOG_ERR, "socket(AF_INET, SOCK_STREAM, 0): %s", strerror(errno));
3900 return 0;
3901 }
3902 memset(&addr, 0, sizeof(struct sockaddr_in));
3903 addr.sin_family = AF_INET;
3904 addr.sin_addr.s_addr = inet_addr(host);
3905 addr.sin_port = htons(port);
3906 addr_len = sizeof(struct sockaddr_in);
3907 LOGDEBUG("Connecting to %s:%d for virus check", host, port);
3908 if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
3909 LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3910 close(sockfd);
3911 return 0;
3912 }
3913
3914 setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
3915
3916 sock = fdopen(sockfd, "r");
3917 if (sock == NULL) {
3918 LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3919 close(sockfd);
3920 return 0;
3921 }
3922 sockout = fdopen(sockfd, "w");
3923 if (sockout == NULL) {
3924 LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3925 fclose(sock);
3926 close(sockfd);
3927 return 0;
3928 }
3929 fprintf(sockout, "STREAM\r\n");
3930 fflush(sockout);
3931
3932 if ((fgets(buf, sizeof(buf), sock))!=NULL && !strncmp(buf, "PORT", 4)) {
3933 int s_port = atoi(buf+5);
3934 if (feed_clam(s_port, message)==0) {
3935 if ((fgets(buf, sizeof(buf), sock))!=NULL) {
3936 if (!strstr(buf, ": OK"))
3937 virus = 1;
3938 }
3939 }
3940 }
3941 fclose(sock);
3942 fclose(sockout);
3943 close(sockfd);
3944
3945 return virus;
3946 }
3947
3948 /*
3949 * feed_clam(int port, buffer *message)
3950 *
3951 * DESCRIPTION
3952 * Feed a stream to ClamAV for virus detection
3953 *
3954 * INPUT ARGUMENTS
3955 * sockfd port number of stream
3956 * message pointer to buffer containing message for scanning
3957 *
3958 * RETURN VALUES
3959 * returns 0 on success
3960 */
3961
feed_clam(int port,buffer * message)3962 int feed_clam(int port, buffer *message) {
3963 struct sockaddr_in addr;
3964 int sockfd, r, addr_len;
3965 int yes = 1;
3966 long sent = 0;
3967 long size = strlen(message->data);
3968 char *host = _ds_read_attribute(agent_config, "ClamAVHost");
3969
3970 sockfd = socket(AF_INET, SOCK_STREAM, 0);
3971 if (sockfd < 0) {
3972 LOG(LOG_ERR, "socket(AF_INET, SOCK_STREAM, 0): %s", strerror(errno));
3973 return EFAILURE;
3974 }
3975 memset(&addr, 0, sizeof(struct sockaddr_in));
3976 addr.sin_family = AF_INET;
3977 addr.sin_addr.s_addr = inet_addr(host);
3978 addr.sin_port = htons(port);
3979 addr_len = sizeof(struct sockaddr_in);
3980 LOGDEBUG("Connecting to %s:%d for virus stream transmission", host, port);
3981 if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
3982 LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3983 close(sockfd);
3984 return EFAILURE;
3985 }
3986
3987 setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
3988
3989 while(sent<size) {
3990 r = send(sockfd, message->data+sent, size-sent, 0);
3991 if (r <= 0) {
3992 close(sockfd);
3993 return r;
3994 }
3995 sent += r;
3996 }
3997
3998 close(sockfd);
3999 return 0;
4000 }
4001
4002 #endif
4003
4004 /*
4005 * is_blacklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX)
4006 *
4007 * DESCRIPTION
4008 * Determine if the source address of the message is blacklisted
4009 *
4010 * INPUT ARGUMENTS
4011 * CTX DSPAM context containing the message
4012 * ATX Agent context defining processing behavior
4013 *
4014 * RETURN VALUES
4015 * returns 1 if blacklisted, 0 otherwise
4016 */
4017
is_blacklisted(DSPAM_CTX * CTX,AGENT_CTX * ATX)4018 int is_blacklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
4019 #ifdef __CYGWIN__
4020 /* No cygwin support for IP Blacklisting */
4021 return 0;
4022 #else
4023 char ip[32];
4024 int bad = 0;
4025 struct attribute *attrib;
4026 struct addrinfo *res = NULL;
4027 struct sockaddr_in saddr;
4028 char host[32];
4029 char lookup[256];
4030 char *ptr;
4031 char *octet[4];
4032 int i = 3;
4033 octet[0] = octet[1] = octet[2] = octet[3] = NULL;
4034
4035 if (!dspam_getsource (CTX, ip, sizeof (ip))) {
4036 host[0] = 0;
4037 ptr = strtok(ip, ".");
4038 while(ptr != NULL && i>=0 && i<4) {
4039 octet[i] = ptr;
4040 ptr = strtok(NULL, ".");
4041 if (ptr == NULL && i!=0)
4042 return 0;
4043 i--;
4044 }
4045
4046 if (octet[0] == NULL || octet[1] == NULL || octet[2] == NULL || octet[3] == NULL)
4047 return 0;
4048
4049 snprintf(host, sizeof(host), "%s.%s.%s.%s.", octet[0], octet[1], octet[2], octet[3]);
4050
4051 attrib = _ds_find_attribute(agent_config, "Lookup");
4052 while(attrib != NULL) {
4053 int error;
4054 snprintf(lookup, sizeof(lookup), "%s%s", host, attrib->value);
4055 error = getaddrinfo(lookup, NULL, NULL, &res);
4056 if (!error) {
4057 char buff[128];
4058 if (!bad) {
4059 memcpy(&saddr, res->ai_addr, sizeof(struct sockaddr));
4060 #ifdef HAVE_INET_NTOA_R_2
4061 inet_ntoa_r(saddr.sin_addr, buff);
4062 #else
4063 inet_ntoa_r(saddr.sin_addr, buff, sizeof(buff));
4064 #endif
4065 if (strncmp(buff, "127.0.0.", 8) == 0) {
4066 STATUS("Blacklisted (%s)", attrib->value);
4067 bad = 1;
4068 freeaddrinfo(res);
4069 break;
4070 }
4071 }
4072 freeaddrinfo(res);
4073 }
4074 attrib = attrib->next;
4075 }
4076 }
4077
4078 return bad;
4079 #endif
4080 }
4081
4082 /*
4083 * is_blocklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX)
4084 *
4085 * DESCRIPTION
4086 * Determine if the source address of the message is blocklisted on
4087 * the destination user's blocklist
4088 *
4089 * INPUT ARGUMENTS
4090 * CTX DSPAM context containing the message
4091 * ATX Agent context defining processing behavior
4092 *
4093 * RETURN VALUES
4094 * returns 1 if blacklisted, 0 otherwise
4095 */
4096
is_blocklisted(DSPAM_CTX * CTX,AGENT_CTX * ATX)4097 int is_blocklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
4098 char filename[MAX_FILENAME_LENGTH];
4099 FILE *file;
4100 int blocklisted = 0;
4101 _ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
4102 LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
4103 : CTX->username), "blocklist");
4104 file = fopen(filename, "r");
4105 if (file != NULL) {
4106 char *heading = _ds_find_header(CTX->message, "From");
4107 char buf[256];
4108 if (heading) {
4109 char *dup = strdup(heading);
4110 char *domain = strrchr(dup, '@');
4111 if (domain) {
4112 int i;
4113 for(i=0;domain[i] && domain[i]!='\r' && domain[i]!='\n'
4114 && domain[i]!='>' && !isspace((int) domain[i]);i++) { }
4115 domain[i] = 0;
4116 while((fgets(buf, sizeof(buf), file))!=NULL) {
4117 chomp(buf);
4118 if (!strcasecmp(buf, domain+1)) {
4119 blocklisted = 1;
4120 break;
4121 }
4122 }
4123 }
4124 free(dup);
4125 dup = NULL;
4126 }
4127 fclose(file);
4128 }
4129 return blocklisted;
4130 }
4131
4132 /*
4133 * daemon_start(AGENT_CTX *ATX)
4134 *
4135 * DESCRIPTION
4136 * Launch into daemon mode and start listener
4137 *
4138 * INPUT ARGUMENTS
4139 * ATX Agent context defining processing behavior
4140 *
4141 * RETURN VALUES
4142 * returns 0 on successful termination
4143 */
4144
4145 #ifdef DAEMON
daemon_start(AGENT_CTX * ATX)4146 int daemon_start(AGENT_CTX *ATX) {
4147 DRIVER_CTX DTX;
4148 char *pidfile;
4149 ATX = ATX; /* Keep compiler happy */
4150 int exitcode = EXIT_SUCCESS;
4151
4152 if (ATX->fork && fork()) /* Fork DSPAM into the background */
4153 exit(exitcode);
4154
4155 __daemon_run = 1;
4156 __num_threads = 0;
4157 __hup = 0;
4158 pthread_mutex_init(&__lock, NULL);
4159 if (libdspam_init(_ds_read_attribute(agent_config, "StorageDriver"))) {
4160 LOG(LOG_CRIT, ERR_DRV_INIT);
4161 // pthread_mutex_destroy(&__lock);
4162 exit(EXIT_FAILURE);
4163 }
4164
4165 LOG(LOG_INFO, INFO_DAEMON_START);
4166
4167 while(__daemon_run) {
4168
4169 DTX.CTX = dspam_create (NULL, NULL,
4170 _ds_read_attribute(agent_config, "Home"),
4171 DSM_TOOLS, 0);
4172 if (!DTX.CTX)
4173 {
4174 LOG(LOG_ERR, ERR_CORE_INIT);
4175 // pthread_mutex_destroy(&__lock);
4176 // libdspam_shutdown();
4177 exit(EXIT_FAILURE);
4178 }
4179
4180 set_libdspam_attributes(DTX.CTX);
4181 DTX.flags = DRF_STATEFUL;
4182
4183 #ifdef DEBUG
4184 if (DO_DEBUG)
4185 DO_DEBUG = 2;
4186 #endif
4187 if (dspam_init_driver (&DTX))
4188 {
4189 LOG (LOG_WARNING, ERR_DRV_INIT);
4190 // pthread_mutex_destroy(&__lock);
4191 // libdspam_shutdown();
4192 exit(EXIT_FAILURE);
4193 }
4194
4195 pidfile = _ds_read_attribute(agent_config, "ServerPID");
4196 if ( pidfile == NULL )
4197 pidfile = "/var/run/dspam/dspam.pid";
4198
4199 if (pidfile) {
4200 FILE *file;
4201 file = fopen(pidfile, "w");
4202 if (file == NULL) {
4203 LOG(LOG_ERR, ERR_IO_FILE_WRITE, pidfile, strerror(errno));
4204 dspam_shutdown_driver(&DTX);
4205 libdspam_shutdown();
4206 exit(EXIT_FAILURE);
4207 } else {
4208 fprintf(file, "%ld\n", (long) getpid());
4209 fclose(file);
4210 }
4211 }
4212
4213 LOGDEBUG("Spawning daemon listener");
4214
4215 if (daemon_listen(&DTX)) {
4216 LOG(LOG_CRIT, ERR_DAEMON_FAIL);
4217 __daemon_run = 0;
4218 exitcode = EXIT_FAILURE;
4219 } else {
4220 LOG(LOG_WARNING, "Received signal. Waiting for processing threads to exit.");
4221 while(__num_threads) {
4222 struct timeval tv;
4223 tv.tv_sec = 1;
4224 tv.tv_usec = 0;
4225 select(0, NULL, NULL, NULL, &tv);
4226 }
4227 LOG(LOG_WARNING, "Processing threads terminated.");
4228 }
4229
4230 /* only unlink pid file if daemon is shut down */
4231 if (pidfile && !__daemon_run)
4232 unlink(pidfile);
4233
4234 dspam_shutdown_driver(&DTX);
4235 dspam_destroy(DTX.CTX);
4236
4237 /* Reload */
4238 if (__hup) {
4239 LOG(LOG_WARNING, INFO_DAEMON_RELOAD);
4240
4241 if (agent_config)
4242 _ds_destroy_config(agent_config);
4243
4244 agent_config = read_config(NULL);
4245 if (!agent_config) {
4246 LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
4247 pthread_mutex_destroy(&__lock);
4248 libdspam_shutdown();
4249 exit(EXIT_FAILURE);
4250 }
4251
4252 __daemon_run = 1;
4253 __hup = 0;
4254 }
4255 }
4256
4257 LOG(LOG_WARNING, INFO_DAEMON_EXIT);
4258 pthread_mutex_destroy(&__lock);
4259 libdspam_shutdown();
4260
4261 return exitcode;
4262 }
4263 #endif
4264
4265 /*
4266 * load_aggregated_prefs(AGENT_CTX *ATX, const char *username)
4267 *
4268 * DESCRIPTION
4269 * Load and aggregate system+user preferences
4270 *
4271 * INPUT ARGUMENTS
4272 * ATX Agent context defining processing behavior
4273 * username Target user
4274 *
4275 * RETURN VALUES
4276 * pointer to aggregated preference structure, NULL on failure
4277 */
4278
load_aggregated_prefs(AGENT_CTX * ATX,const char * username)4279 agent_pref_t load_aggregated_prefs(AGENT_CTX *ATX, const char *username) {
4280 agent_pref_t PTX = NULL;
4281 agent_pref_t STX = NULL;
4282 agent_pref_t UTX = NULL;
4283
4284 LOGDEBUG("loading preferences for user %s", username);
4285 UTX = _ds_pref_load(agent_config, username,
4286 _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4287
4288 if (!UTX && _ds_match_attribute(agent_config, "FallbackDomains", "on")) {
4289 if (username != NULL && strchr(username, '@')) {
4290 char *domain = strchr(username, '@');
4291 if (domain) {
4292 UTX = _ds_pref_load(agent_config,
4293 domain,
4294 _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4295 if (UTX && !strcmp(_ds_pref_val(UTX, "fallbackDomain"), "on")) {
4296 LOGDEBUG("empty prefs found. falling back to %s", domain);
4297 } else {
4298 _ds_pref_free(UTX);
4299 UTX = NULL;
4300 }
4301 }
4302 } else {
4303 LOG(LOG_ERR, "load_aggregated_prefs(): Can not fallback to domains for username '%s' without @domain part.", username);
4304 }
4305 }
4306
4307 if (!UTX) {
4308 UTX = _ds_pref_load(agent_config, NULL,
4309 _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4310 }
4311
4312 STX = _ds_pref_load(agent_config, NULL,
4313 _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4314
4315 if (!STX || STX[0] == 0) {
4316 if (STX) {
4317 _ds_pref_free(STX);
4318 }
4319 LOGDEBUG("default preferences empty. reverting to dspam.conf preferences.");
4320 STX = pref_config();
4321 } else {
4322 LOGDEBUG("loaded default preferences externally");
4323 }
4324
4325 PTX = _ds_pref_aggregate(STX, UTX);
4326 _ds_pref_free(UTX);
4327 free(UTX);
4328 UTX = NULL;
4329 _ds_pref_free(STX);
4330 free(STX);
4331 STX = NULL;
4332
4333 #ifdef VERBOSE
4334 if (PTX) {
4335 int j;
4336 for(j=0;PTX[j];j++) {
4337 LOGDEBUG("aggregated preference '%s' => '%s'",
4338 PTX[j]->attribute, PTX[j]->value);
4339 }
4340 }
4341 #endif
4342
4343 return PTX;
4344 }
4345
4346 /*
4347 * do_notifications(DSPAM_CTX *CTX, AGENT_CTX *ATX)
4348 *
4349 * DESCRIPTION
4350 * Evaluate and send notifications as necessary
4351 *
4352 * INPUT ARGUMENTS
4353 * CTX DSPAM context
4354 * ATX Agent context defining processing behavior
4355 *
4356 * RETURN VALUES
4357 * returns 0 on success, standard errors in failure
4358 */
4359
do_notifications(DSPAM_CTX * CTX,AGENT_CTX * ATX)4360 int do_notifications(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
4361 char filename[MAX_FILENAME_LENGTH];
4362 FILE *file;
4363
4364 /* First run notification */
4365
4366 if ((_ds_match_attribute(agent_config, "Notifications", "on") ||
4367 !strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "on")) &&
4368 strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "off")) {
4369 _ds_userdir_path(filename,
4370 _ds_read_attribute(agent_config, "Home"),
4371 LOOKUP(ATX->PTX, CTX->username), "firstrun");
4372 file = fopen(filename, "r");
4373 if (file == NULL) {
4374 LOGDEBUG("sending firstrun.txt to %s (%s): %s",
4375 CTX->username, filename, strerror(errno));
4376 send_notice(ATX, "firstrun.txt", ATX->mailer_args, CTX->username);
4377 _ds_prepare_path_for(filename);
4378 file = fopen(filename, "w");
4379 if (file) {
4380 fprintf(file, "%ld\n", (long) time(NULL));
4381 fclose(file);
4382 }
4383 } else {
4384 fclose(file);
4385 }
4386 }
4387
4388
4389 /* First spam notification */
4390
4391 if (CTX->result == DSR_ISSPAM &&
4392 (_ds_match_attribute(agent_config, "Notifications", "on") ||
4393 !strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "on")) &&
4394 strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "off"))
4395 {
4396 _ds_userdir_path(filename,
4397 _ds_read_attribute(agent_config, "Home"),
4398 LOOKUP(ATX->PTX, CTX->username), "firstspam");
4399 file = fopen(filename, "r");
4400 if (file == NULL) {
4401 LOGDEBUG("sending firstspam.txt to %s (%s): %s",
4402 CTX->username, filename, strerror(errno));
4403 send_notice(ATX, "firstspam.txt", ATX->mailer_args, CTX->username);
4404 _ds_prepare_path_for(filename);
4405 file = fopen(filename, "w");
4406 if (file) {
4407 fprintf(file, "%ld\n", (long) time(NULL));
4408 fclose(file);
4409 }
4410 } else {
4411 fclose(file);
4412 }
4413 }
4414
4415 /* Quarantine size notification */
4416
4417 if ((_ds_match_attribute(agent_config, "Notifications", "on") ||
4418 !strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "on")) &&
4419 strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "off")) {
4420 struct stat s;
4421 char qfile[MAX_FILENAME_LENGTH];
4422 int qwarn_size = 1024*1024*2;
4423
4424 if (_ds_read_attribute(agent_config, "QuarantineWarnSize")) {
4425 qwarn_size = atoi(_ds_read_attribute(agent_config, "QuarantineWarnSize"));
4426 if (qwarn_size == INT_MAX && errno == ERANGE) {
4427 LOG (LOG_INFO, "Value for 'QuarantineWarnSize' not valid (will use 2MB for now)");
4428 qwarn_size = 1024*1024*2;
4429 }
4430 }
4431
4432 _ds_userdir_path(qfile, _ds_read_attribute(agent_config, "Home"),
4433 LOOKUP(ATX->PTX, CTX->username), "mbox");
4434
4435 if (!stat(qfile, &s) && s.st_size > qwarn_size) {
4436 _ds_userdir_path(qfile, _ds_read_attribute(agent_config, "Home"),
4437 LOOKUP(ATX->PTX, CTX->username), "mboxwarn");
4438 if (stat(qfile, &s)) {
4439 FILE *f;
4440
4441 _ds_prepare_path_for(qfile);
4442 f = fopen(qfile, "w");
4443 if (f != NULL) {
4444 fprintf(f, "%ld", (long) time(NULL));
4445 fclose(f);
4446
4447 send_notice(ATX, "quarantinefull.txt", ATX->mailer_args, CTX->username);
4448 }
4449 }
4450 }
4451 }
4452
4453 return 0;
4454 }
4455