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