source: npl/mailserver/dspam/dspam-3.10.2/src/dspam.c @ c5c522c

gcc484ntopperl-5.22
Last change on this file since c5c522c was c5c522c, checked in by Edwin Eefting <edwin@datux.nl>, 8 years ago

initial commit, transferred from cleaned syn3 svn tree

  • Property mode set to 100644
File size: 125.4 KB
Line 
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"
88int 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
107int
108main (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
283BAIL:
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
337int
338process_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
858RETURN:
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
910int
911deliver_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
1110int 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
1211int
1212quarantine_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
1296int
1297write_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
1350int
1351inoculate_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
1441int
1442user_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
1534int 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
1606int 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
2115RSET:
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
2156int 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    }
2369NEXT:
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
2394DSPAM_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
2784int 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
2895int 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
3017static 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
3043int 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
3226int 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        snprintf(data, sizeof(data), "X-DSPAM-Recipient: %s",
3291                 ATX->recipient);
3292        head = _ds_create_header_field(data);
3293        if (head != NULL)
3294        {
3295#ifdef VERBOSE
3296          LOGDEBUG("appending header %s: %s", head->heading, head->data);
3297#endif
3298          nt_add(block->headers, (void *) head);
3299        }
3300        else
3301          LOG (LOG_CRIT, ERR_MEM_ALLOC);
3302
3303        if (_ds_match_attribute(agent_config, "ImprobabilityDrive", "on"))
3304        {
3305          float probability = CTX->confidence;
3306          char *as;
3307          if (probability > 0.999999)
3308            probability = 0.999999;
3309          if (CTX->result == DSR_ISINNOCENT) {
3310            as = "spam";
3311          } else {
3312            as = "ham";
3313          }
3314          snprintf(data, sizeof(data), "X-DSPAM-Improbability: 1 in %.0f "
3315            "chance of being %s",
3316            1.0+(100*(probability / (1-probability))), as);
3317          head = _ds_create_header_field(data);
3318          if (head != NULL)
3319          {
3320#ifdef VERBOSE
3321            LOGDEBUG("appending header %s: %s", head->heading, head->data);
3322#endif
3323            nt_add(block->headers, (void *) head);
3324          }
3325          else
3326            LOG (LOG_CRIT, ERR_MEM_ALLOC);
3327        }
3328
3329
3330        snprintf(data, sizeof(data), "X-DSPAM-Probability: %01.4f",
3331                 CTX->probability);
3332
3333        head = _ds_create_header_field(data);
3334        if (head != NULL)
3335        {
3336#ifdef VERBOSE
3337          LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3338#endif
3339            nt_add (block->headers, (void *) head);
3340        }
3341        else
3342          LOG (LOG_CRIT, ERR_MEM_ALLOC);
3343
3344        if (CTX->training_mode != DST_NOTRAIN && ATX->signature[0] != 0) {
3345          snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
3346
3347          head = _ds_create_header_field(data);
3348          if (head != NULL)
3349          {
3350            if (strlen(ATX->signature)<5)
3351            {
3352              LOGDEBUG("WARNING: Signature not generated, or invalid");
3353            }
3354#ifdef VERBOSE
3355            LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3356#endif
3357            nt_add (block->headers, (void *) head);
3358          }
3359          else
3360            LOG (LOG_CRIT, ERR_MEM_ALLOC);
3361        }
3362
3363        if (CTX->result == DSR_ISSPAM && (ATX->managed_group[0] || (_ds_pref_val(ATX->PTX, "localStore")[0])))
3364        {
3365          snprintf(data, sizeof(data), "X-DSPAM-User: %s", CTX->username);
3366          head = _ds_create_header_field(data);
3367          if (head != NULL)
3368          {
3369#ifdef VERBOSE
3370            LOGDEBUG ("appending header %s: %s", head->heading, head->data);
3371#endif
3372              nt_add (block->headers, (void *) head);
3373          }
3374          else
3375            LOG (LOG_CRIT, ERR_MEM_ALLOC);
3376        }
3377
3378        if (!strcmp(_ds_pref_val(ATX->PTX, "showFactors"), "on")) {
3379
3380          if (CTX->factors != NULL) {
3381            snprintf(data, sizeof(data), "X-DSPAM-Factors: %d",
3382                     CTX->factors->items);
3383            node_ft = c_nt_first(CTX->factors, &c_ft);
3384            while(node_ft != NULL) {
3385              struct dspam_factor *f = (struct dspam_factor *) node_ft->ptr;
3386              if (f) {
3387                char *s, *t;
3388                strlcat(data, ",\n\t", sizeof(data));
3389                s = f->token_name;
3390                t = scratch;
3391                while (*s && t < scratch + sizeof(scratch) - 16)
3392                  if (*s >= ' ' && *s < 0x7f && *s != '%')
3393                    *t++ = *s++;
3394                  else
3395                    t += sprintf(t, "%%%02x", (unsigned char) *s++);
3396                snprintf(t, 15, ", %2.5f", f->value);
3397                strlcat(data, scratch, sizeof(data));
3398              }
3399              node_ft = c_nt_next(CTX->factors, &c_ft);
3400            }
3401            head = _ds_create_header_field(data);
3402            if (head != NULL)
3403            {
3404#ifdef VERBOSE
3405              LOGDEBUG("appending header %s: %s", head->heading, head->data);
3406#endif
3407              nt_add(block->headers, (void *) head);
3408            }
3409          }
3410        }
3411
3412      } /* CTX->source != DSS_ERROR */
3413    }
3414  }
3415  return 0;
3416}
3417
3418/*
3419 * embed_msgtag(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3420 *
3421 * DESCRIPTION
3422 *   Embed a message tag
3423 *
3424 * INPUT ARGUMENTS
3425 *   CTX          DSPAM context containing the message
3426 *   ATX          Agent context defining processing behavior
3427 *
3428 * RETURN VALUES
3429 *   returns 0 on success, standard errors on failure
3430 */
3431
3432int embed_msgtag(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3433  struct nt_node *node_nt;
3434  struct nt_c c_nt;
3435  char toplevel_boundary[128] = { 0 };
3436  ds_message_part_t block;
3437  int i = 0;
3438  FILE *f;
3439  char buff[1024], msgfile[MAX_FILENAME_LENGTH];
3440  buffer *b;
3441  ATX = ATX; /* Keep compiler happy */
3442
3443  if (CTX->result != DSR_ISSPAM && CTX->result != DSR_ISINNOCENT)
3444      return EINVAL;
3445
3446  node_nt = c_nt_first (CTX->message->components, &c_nt);
3447  if (node_nt == NULL || node_nt->ptr == NULL)
3448    return EFAILURE;
3449
3450  block = node_nt->ptr;
3451
3452  /* Signed messages cannot be tagged */
3453
3454  if (block->media_subtype == MST_SIGNED)
3455    return EINVAL;
3456
3457  /* Load the message tag */
3458  if (_ds_read_attribute(agent_config, "TxtDirectory")) {
3459    snprintf(msgfile, sizeof(msgfile), "%s/msgtag.%s",
3460           _ds_read_attribute(agent_config, "TxtDirectory"),
3461           (CTX->result == DSR_ISSPAM) ? "spam" : "nonspam");
3462  } else {
3463    snprintf(msgfile, sizeof(msgfile), "%s/txt/msgtag.%s",
3464           _ds_read_attribute(agent_config, "Home"),
3465           (CTX->result == DSR_ISSPAM) ? "spam" : "nonspam");
3466  }
3467  f = fopen(msgfile, "r");
3468  if (!f) {
3469    LOG(LOG_ERR, ERR_IO_FILE_OPEN, msgfile, strerror(errno));
3470    return EFILE;
3471  }
3472  b = buffer_create(NULL);
3473  if (!b) {
3474    LOG(LOG_CRIT, ERR_MEM_ALLOC);
3475    fclose(f);
3476    return EUNKNOWN;
3477  }
3478  while(fgets(buff, sizeof(buff), f)!=NULL) {
3479      buffer_cat(b, buff);
3480  }
3481  fclose(f);
3482
3483  if (block->media_type == MT_MULTIPART && block->terminating_boundary != NULL)
3484  {
3485    strlcpy(toplevel_boundary, block->terminating_boundary,
3486            sizeof(toplevel_boundary));
3487  }
3488
3489  while (node_nt != NULL)
3490  {
3491    char *body_close = NULL, *dup = NULL;
3492
3493    block = node_nt->ptr;
3494
3495    /* Append signature to blocks when... */
3496
3497    if (block != NULL
3498
3499        /* Either a text section, or this is a non-multipart message AND...*/
3500        && (block->media_type == MT_TEXT
3501            || (block->boundary == NULL && i == 0
3502                && block->media_type != MT_MULTIPART))
3503        && (toplevel_boundary[0] == 0 || (block->body && block->body->used)))
3504    {
3505      if (block->content_disposition == PCD_ATTACHMENT)
3506      {
3507        node_nt = c_nt_next (CTX->message->components, &c_nt);
3508        i++;
3509        continue;
3510      }
3511
3512      /* Some email clients reformat HTML parts, and require that we include
3513       * the signature before the HTML close tags (because they're stupid)
3514       */
3515
3516      if (body_close            == NULL &&
3517          block->body           != NULL &&
3518          block->body->data     != NULL &&
3519          block->media_subtype  == MST_HTML)
3520
3521      {
3522        body_close = strcasestr(block->body->data, "</body");
3523        if (!body_close)
3524          body_close = strcasestr(block->body->data, "</html");
3525      }
3526
3527      /* Save and truncate everything after and including the close tag */
3528      if (body_close)
3529      {
3530        dup = strdup (body_close);
3531        block->body->used -= (long) strlen (dup);
3532        body_close[0] = 0;
3533      }
3534
3535      buffer_cat (block->body, "\n");
3536      buffer_cat (block->body, b->data);
3537
3538      if (dup)
3539      {
3540        buffer_cat (block->body, dup);
3541        free (dup);
3542      }
3543    }
3544
3545    node_nt = c_nt_next (CTX->message->components, &c_nt);
3546    i++;
3547  }
3548  buffer_destroy(b);
3549  return 0;
3550}
3551
3552/*
3553 * embed_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3554 *
3555 * DESCRIPTION
3556 *   Embed the DSPAM signature in all relevant parts of the message
3557 *
3558 * INPUT ARGUMENTS
3559 *   CTX          DSPAM context containing the message
3560 *   ATX          Agent context defining processing behavior
3561 *
3562 * RETURN VALUES
3563 *   returns 0 on success, standard errors on failure
3564 */
3565
3566int embed_signature(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3567  struct nt_node *node_nt;
3568  struct nt_c c_nt;
3569  char toplevel_boundary[128] = { 0 };
3570  ds_message_part_t block;
3571  int i = 0;
3572
3573  if (CTX->training_mode == DST_NOTRAIN || ! ATX->signature[0])
3574    return 0;
3575
3576  node_nt = c_nt_first (CTX->message->components, &c_nt);
3577
3578  if (node_nt == NULL || node_nt->ptr == NULL)
3579    return EFAILURE;
3580
3581  block = node_nt->ptr;
3582
3583  /* Signed messages are handled differently */
3584
3585  if (block->media_subtype == MST_SIGNED)
3586    return embed_signed(CTX, ATX);
3587
3588  if (block->media_type == MT_MULTIPART && block->terminating_boundary != NULL)
3589  {
3590    strlcpy(toplevel_boundary, block->terminating_boundary,
3591            sizeof(toplevel_boundary));
3592  }
3593
3594  while (node_nt != NULL)
3595  {
3596    char *body_close = NULL, *dup = NULL;
3597
3598    block = node_nt->ptr;
3599
3600    /* Append signature to blocks when... */
3601
3602    if (block != NULL
3603
3604        /* Either a text section, or this is a non-multipart message AND...*/
3605        && (block->media_type == MT_TEXT
3606            || (block->boundary == NULL && i == 0
3607                && block->media_type != MT_MULTIPART))
3608        && (toplevel_boundary[0] == 0 || (block->body && block->body->used)))
3609    {
3610      if (block->content_disposition == PCD_ATTACHMENT)
3611      {
3612        node_nt = c_nt_next (CTX->message->components, &c_nt);
3613        i++;
3614        continue;
3615      }
3616
3617      /* Some email clients reformat HTML parts, and require that we include
3618       * the signature before the HTML close tags (because they're stupid)
3619       */
3620
3621      if (body_close            == NULL &&
3622          block->body           != NULL &&
3623          block->body->data     != NULL &&
3624          block->media_subtype  == MST_HTML)
3625
3626      {
3627        body_close = strcasestr(block->body->data, "</body");
3628        if (!body_close)
3629          body_close = strcasestr(block->body->data, "</html");
3630      }
3631
3632      /* Save and truncate everything after and including the close tag */
3633      if (body_close)
3634      {
3635        dup = strdup (body_close);
3636        block->body->used -= (long) strlen (dup);
3637        body_close[0] = 0;
3638      }
3639
3640      buffer_cat (block->body, "\n");
3641      buffer_cat (block->body, SIGNATURE_BEGIN);
3642      buffer_cat (block->body, ATX->signature);
3643      buffer_cat (block->body, SIGNATURE_END);
3644      buffer_cat (block->body, "\n\n");
3645
3646      if (dup)
3647      {
3648        buffer_cat (block->body, dup);
3649        buffer_cat (block->body, "\n\n");
3650        free (dup);
3651      }
3652    }
3653
3654    node_nt = c_nt_next (CTX->message->components, &c_nt);
3655    i++;
3656  }
3657  return 0;
3658}
3659
3660/*
3661 * embed_signed(DSPAM_CTX *CTX, AGENT_CTX *ATX)
3662 *
3663 * DESCRIPTION
3664 *   Embed the DSPAM signature within a signed message
3665 *
3666 * INPUT ARGUMENTS
3667 *   CTX          DSPAM context containing message
3668 *   ATX          Agent context defining processing behavior
3669 *
3670 * RETURN VALUES
3671 *   returns 0 on success, standard errors on failure
3672 */
3673
3674int embed_signed(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
3675  struct nt_node *node_nt, *node_block, *parent;
3676  struct nt_c c_nt;
3677  ds_message_part_t block, newblock;
3678  ds_header_t field;
3679  char scratch[256], data[256];
3680
3681  node_block = c_nt_first (CTX->message->components, &c_nt);
3682  if (node_block == NULL || node_block->ptr == NULL)
3683    return EFAILURE;
3684
3685  block = node_block->ptr;
3686
3687  /* Construct a new block to contain the signed message */
3688
3689  newblock = (ds_message_part_t) malloc(sizeof(struct _ds_message_part));
3690  if (newblock == NULL)
3691    goto MEM_ALLOC;
3692
3693  newblock->headers = nt_create(NT_PTR);
3694  if (newblock->headers == NULL)
3695    goto MEM_ALLOC;
3696
3697  newblock->boundary            = NULL;
3698  newblock->terminating_boundary= block->terminating_boundary;
3699  newblock->encoding            = block->encoding;
3700  newblock->original_encoding   = block->original_encoding;
3701  newblock->media_type          = block->media_type;
3702  newblock->media_subtype       = block->media_subtype;
3703  newblock->body                = buffer_create (NULL);
3704  newblock->original_signed_body= NULL;
3705
3706  /* Move the relevant headers from the main part to the new block */
3707
3708  parent = NULL;
3709  node_nt = c_nt_first(block->headers, &c_nt);
3710  while(node_nt != NULL) {
3711    field = node_nt->ptr;
3712    if (field) {
3713      if (!strcasecmp(field->heading, "Content-Type") ||
3714          !strcasecmp(field->heading, "Content-Disposition"))
3715      {
3716        struct nt_node *old = node_nt;
3717        node_nt = c_nt_next(block->headers, &c_nt);
3718        if (parent)
3719          parent->next = node_nt;
3720        else
3721          block->headers->first = node_nt;
3722        nt_add(newblock->headers, field);
3723        free(old);
3724        old = NULL;
3725        block->headers->items--;
3726        continue;
3727      }
3728    }
3729    parent = node_nt;
3730    node_nt = c_nt_next(block->headers, &c_nt);
3731  }
3732
3733  /* Create a new top-level boundary */
3734  snprintf(scratch, sizeof(scratch), "DSPAM_MULTIPART_EX-%ld", (long)getpid());
3735  block->terminating_boundary = strdup(scratch);
3736
3737  /* Create a new content-type field */
3738  block->media_type    = MT_MULTIPART;
3739  block->media_subtype = MST_MIXED;
3740  snprintf(data, sizeof(data), "Content-Type: multipart/mixed; boundary=%s", scratch);
3741  field = _ds_create_header_field(data);
3742  if (field != NULL)
3743    nt_add(block->headers, field);
3744
3745  /* Insert the new block right below the top headers and blank body */
3746  node_nt = nt_node_create(newblock);
3747  if (node_nt == NULL)
3748    goto MEM_ALLOC;
3749  node_nt->next = node_block->next;
3750  node_block->next = node_nt;
3751  CTX->message->components->items++;
3752
3753  /* Strip the old terminating boundary */
3754
3755  parent = NULL;
3756  node_nt = c_nt_first (CTX->message->components, &c_nt);
3757  while (node_nt)
3758  {
3759    if (!node_nt->next && parent) {
3760      parent->next = NULL;
3761      CTX->message->components->items--;
3762      CTX->message->components->insert = NULL;
3763      _ds_destroy_block(node_nt->ptr);
3764      free(node_nt);
3765      node_nt = NULL;
3766    } else {
3767      parent = node_nt;
3768      node_nt = node_nt->next;
3769    }
3770  }
3771
3772  /* Create a new message part containing only the boundary delimiter */
3773
3774  newblock = (ds_message_part_t)
3775    malloc(sizeof(struct _ds_message_part));
3776  if (newblock == NULL)
3777    goto MEM_ALLOC;
3778
3779  newblock->headers = nt_create(NT_PTR);
3780  if (newblock->headers == NULL)
3781    goto MEM_ALLOC;
3782
3783  newblock->boundary            = NULL;
3784  newblock->terminating_boundary= strdup(scratch);
3785  newblock->encoding            = EN_7BIT;
3786  newblock->original_encoding   = EN_7BIT;
3787  newblock->media_type          = MT_TEXT;
3788  newblock->media_subtype       = MST_PLAIN;
3789  newblock->body                = buffer_create (NULL);
3790  newblock->original_signed_body= NULL;
3791  nt_add (CTX->message->components, newblock);
3792
3793  /* Create a new message part containing the signature */
3794
3795  newblock = (ds_message_part_t) malloc(sizeof(struct _ds_message_part));
3796  if (newblock == NULL)
3797    goto MEM_ALLOC;
3798
3799  newblock->headers = nt_create(NT_PTR);
3800  if (newblock->headers == NULL)
3801    goto MEM_ALLOC;
3802
3803  snprintf(data, sizeof(data), "%s--\n\n", scratch);
3804  newblock->boundary            = NULL;
3805  newblock->terminating_boundary= strdup(data);
3806  newblock->encoding            = EN_7BIT;
3807  newblock->original_encoding   = EN_7BIT;
3808  newblock->media_type          = MT_TEXT;
3809  newblock->media_subtype       = MST_PLAIN;
3810  snprintf (scratch, sizeof (scratch),
3811    "%s%s%s\n", SIGNATURE_BEGIN, ATX->signature, SIGNATURE_END);
3812  newblock->body                = buffer_create (scratch);
3813  newblock->original_signed_body= NULL;
3814
3815  field = _ds_create_header_field ("Content-Type: text/plain");
3816  nt_add (newblock->headers, field);
3817  snprintf(data, sizeof(data), "X-DSPAM-Signature: %s", ATX->signature);
3818  nt_add (newblock->headers, _ds_create_header_field(data));
3819  nt_add (CTX->message->components, newblock);
3820
3821  return 0;
3822
3823MEM_ALLOC:
3824  if (newblock) {
3825    if (newblock->headers)
3826      nt_destroy(newblock->headers);
3827    free(newblock);
3828    newblock = NULL;
3829  }
3830
3831  LOG (LOG_CRIT, ERR_MEM_ALLOC);
3832  return EUNKNOWN;
3833}
3834
3835/*
3836 * tracksources(DSPAM_CTX *CTX)
3837 *
3838 * DESCRIPTION
3839 *   Track the source address of a message, report to syslog and/or RABL
3840 *
3841 * INPUT ARGUMENTS
3842 *   CTX          DSPAM context containing filter results and message
3843 *
3844 * RETURN VALUES
3845 *   returns 0 on success, standard errors on failure
3846 */
3847
3848int tracksource(DSPAM_CTX *CTX) {
3849  char ip[32];
3850
3851  if (!dspam_getsource (CTX, ip, sizeof (ip)))
3852  {
3853    if (CTX->totals.innocent_learned + CTX->totals.innocent_classified > 2500) {
3854      if (CTX->result == DSR_ISSPAM &&
3855          strcmp(CTX->class, LANG_CLASS_VIRUS) != 0 &&
3856          _ds_match_attribute(agent_config, "TrackSources", "spam")) {
3857        FILE *file;
3858        char dropfile[MAX_FILENAME_LENGTH];
3859        LOG (LOG_INFO, "spam detected from %s", ip);
3860        if (_ds_read_attribute(agent_config, "RABLQueue")) {
3861          snprintf(dropfile, sizeof(dropfile), "%s/%s",
3862            _ds_read_attribute(agent_config, "RABLQueue"), ip);
3863          file = fopen(dropfile, "w");
3864          if (file != NULL)
3865            fclose(file);
3866        }
3867      } else if (CTX->result == DSR_ISSPAM &&
3868          strcmp(CTX->class, LANG_CLASS_VIRUS) == 0 &&
3869          _ds_match_attribute(agent_config, "TrackSources", "virus"))
3870      {
3871        LOG (LOG_INFO, "infected message from %s", ip);
3872      } else if (CTX->result != DSR_ISSPAM &&
3873          strcmp(CTX->class, LANG_CLASS_VIRUS) != 0 &&
3874          _ds_match_attribute(agent_config, "TrackSources", "nonspam"))
3875      {
3876        LOG (LOG_INFO, "innocent message from %s", ip);
3877      }
3878    }
3879  }
3880  return 0;
3881}
3882
3883#ifdef CLAMAV
3884
3885/*
3886 * has_virus(buffer *message)
3887 *
3888 * DESCRIPTION
3889 *   Call ClamAV to determine if the message has a virus
3890 *
3891 * INPUT ARGUMENTS
3892 *    message     pointer to buffer containing message for scanning
3893 *
3894 * RETURN VALUES
3895 *   returns 1 if virus, 0 otherwise
3896 */
3897
3898int has_virus(buffer *message) {
3899  struct sockaddr_in addr;
3900  int sockfd;
3901  int virus = 0;
3902  int yes = 1;
3903  int port = atoi(_ds_read_attribute(agent_config, "ClamAVPort"));
3904  int addr_len;
3905  char *host = _ds_read_attribute(agent_config, "ClamAVHost");
3906  FILE *sock;
3907  FILE *sockout;
3908  char buf[128];
3909
3910  sockfd = socket(AF_INET, SOCK_STREAM, 0);
3911  if (sockfd < 0) {
3912    LOG(LOG_ERR, "socket(AF_INET, SOCK_STREAM, 0): %s", strerror(errno));
3913    return 0;
3914  }
3915  memset(&addr, 0, sizeof(struct sockaddr_in));
3916  addr.sin_family = AF_INET;
3917  addr.sin_addr.s_addr = inet_addr(host);
3918  addr.sin_port = htons(port);
3919  addr_len = sizeof(struct sockaddr_in);
3920  LOGDEBUG("Connecting to %s:%d for virus check", host, port);
3921  if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
3922    LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3923    close(sockfd);
3924    return 0;
3925  }
3926
3927  setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
3928
3929  sock = fdopen(sockfd, "r");
3930  if (sock == NULL) {
3931    LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3932    close(sockfd);
3933    return 0;
3934  }
3935  sockout = fdopen(sockfd, "w");
3936  if (sockout == NULL) {
3937    LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3938    fclose(sock);
3939    close(sockfd);
3940    return 0;
3941  }
3942  fprintf(sockout, "STREAM\r\n");
3943  fflush(sockout);
3944
3945  if ((fgets(buf, sizeof(buf), sock))!=NULL && !strncmp(buf, "PORT", 4)) {
3946    int s_port = atoi(buf+5);
3947    if (feed_clam(s_port, message)==0) {
3948      if ((fgets(buf, sizeof(buf), sock))!=NULL) {
3949        if (!strstr(buf, ": OK"))
3950          virus = 1;
3951      }
3952    }
3953  }
3954  fclose(sock);
3955  fclose(sockout);
3956  close(sockfd);
3957
3958  return virus;
3959}
3960
3961/*
3962 * feed_clam(int port, buffer *message)
3963 *
3964 * DESCRIPTION
3965 *   Feed a stream to ClamAV for virus detection
3966 *
3967 * INPUT ARGUMENTS
3968 *    sockfd      port number of stream
3969 *    message     pointer to buffer containing message for scanning
3970 *
3971 * RETURN VALUES
3972 *   returns 0 on success
3973 */
3974
3975int feed_clam(int port, buffer *message) {
3976  struct sockaddr_in addr;
3977  int sockfd, r, addr_len;
3978  int yes = 1;
3979  long sent = 0;
3980  long size = strlen(message->data);
3981  char *host = _ds_read_attribute(agent_config, "ClamAVHost");
3982
3983  sockfd = socket(AF_INET, SOCK_STREAM, 0);
3984  if (sockfd < 0) {
3985    LOG(LOG_ERR, "socket(AF_INET, SOCK_STREAM, 0): %s", strerror(errno));
3986    return EFAILURE;
3987  }
3988  memset(&addr, 0, sizeof(struct sockaddr_in));
3989  addr.sin_family = AF_INET;
3990  addr.sin_addr.s_addr = inet_addr(host);
3991  addr.sin_port = htons(port);
3992  addr_len = sizeof(struct sockaddr_in);
3993  LOGDEBUG("Connecting to %s:%d for virus stream transmission", host, port);
3994  if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
3995    LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
3996    close(sockfd);
3997    return EFAILURE;
3998  }
3999
4000  setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
4001
4002  while(sent<size) {
4003    r = send(sockfd, message->data+sent, size-sent, 0);
4004    if (r <= 0) {
4005      close(sockfd);
4006      return r;
4007    }
4008    sent += r;
4009  }
4010
4011  close(sockfd);
4012  return 0;
4013}
4014
4015#endif
4016
4017/*
4018 * is_blacklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX)
4019 *
4020 * DESCRIPTION
4021 *   Determine if the source address of the message is blacklisted
4022 *
4023 * INPUT ARGUMENTS
4024 *   CTX          DSPAM context containing the message
4025 *   ATX          Agent context defining processing behavior
4026 *
4027 * RETURN VALUES
4028 *   returns 1 if blacklisted, 0 otherwise
4029 */
4030
4031int is_blacklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
4032#ifdef __CYGWIN__
4033  /* No cygwin support for IP Blacklisting */
4034  return 0;
4035#else
4036  char ip[32];
4037  int bad = 0;
4038  struct attribute *attrib;
4039  struct addrinfo *res = NULL;
4040  struct sockaddr_in saddr;
4041  char host[32];
4042  char lookup[256];
4043  char *ptr;
4044  char *octet[4];
4045  int i = 3;
4046  octet[0] = octet[1] = octet[2] = octet[3] = NULL;
4047
4048  if (!dspam_getsource (CTX, ip, sizeof (ip))) {
4049    host[0] = 0;
4050    ptr = strtok(ip, ".");
4051    while(ptr != NULL && i>=0 && i<4) {
4052      octet[i] = ptr;
4053      ptr = strtok(NULL, ".");
4054      if (ptr == NULL && i!=0)
4055        return 0;
4056      i--;
4057    }
4058
4059    if (octet[0] == NULL || octet[1] == NULL || octet[2] == NULL || octet[3] == NULL)
4060      return 0;
4061
4062    snprintf(host, sizeof(host), "%s.%s.%s.%s.", octet[0], octet[1], octet[2], octet[3]);
4063
4064    attrib = _ds_find_attribute(agent_config, "Lookup");
4065    while(attrib != NULL) {
4066      int error;
4067      snprintf(lookup, sizeof(lookup), "%s%s", host, attrib->value);
4068      error = getaddrinfo(lookup, NULL, NULL, &res);
4069      if (!error) {
4070        char buff[128];
4071        if (!bad) {
4072          memcpy(&saddr, res->ai_addr, sizeof(struct sockaddr));
4073#ifdef HAVE_INET_NTOA_R_2
4074          inet_ntoa_r(saddr.sin_addr, buff);
4075#else
4076          inet_ntoa_r(saddr.sin_addr, buff, sizeof(buff));
4077#endif
4078          if (strncmp(buff, "127.0.0.", 8) == 0) {
4079            STATUS("Blacklisted (%s)", attrib->value);
4080            bad = 1;
4081            freeaddrinfo(res);
4082            break;
4083          }
4084        }
4085        freeaddrinfo(res);
4086      }
4087      attrib = attrib->next;
4088    }
4089  }
4090
4091  return bad;
4092#endif
4093}
4094
4095/*
4096 * is_blocklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX)
4097 *
4098 * DESCRIPTION
4099 *   Determine if the source address of the message is blocklisted on
4100 *   the destination user's blocklist
4101 *
4102 * INPUT ARGUMENTS
4103 *   CTX          DSPAM context containing the message
4104 *   ATX          Agent context defining processing behavior
4105 *
4106 * RETURN VALUES
4107 *   returns 1 if blacklisted, 0 otherwise
4108 */
4109
4110int is_blocklisted(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
4111  char filename[MAX_FILENAME_LENGTH];
4112  FILE *file;
4113  int blocklisted = 0;
4114  _ds_userdir_path(filename, _ds_read_attribute(agent_config, "Home"),
4115                   LOOKUP(ATX->PTX, (ATX->managed_group[0]) ? ATX->managed_group
4116                                     : CTX->username), "blocklist");
4117  file = fopen(filename, "r");
4118  if (file != NULL) {
4119    char *heading = _ds_find_header(CTX->message, "From");
4120    char buf[256];
4121    if (heading) {
4122      char *dup = strdup(heading);
4123      char *domain = strrchr(dup, '@');
4124      if (domain) {
4125        int i;
4126        for(i=0;domain[i] && domain[i]!='\r' && domain[i]!='\n'
4127             && domain[i]!='>' && !isspace((int) domain[i]);i++) { }
4128        domain[i] = 0;
4129        while((fgets(buf, sizeof(buf), file))!=NULL) {
4130          chomp(buf);
4131          if (!strcasecmp(buf, domain+1)) {
4132            blocklisted = 1;
4133            break;
4134          }
4135        }
4136      }
4137      free(dup);
4138      dup = NULL;
4139    }
4140    fclose(file);
4141  }
4142  return blocklisted;
4143}
4144
4145/*
4146 * daemon_start(AGENT_CTX *ATX)
4147 *
4148 * DESCRIPTION
4149 *   Launch into daemon mode and start listener
4150 *
4151 * INPUT ARGUMENTS
4152 *   ATX          Agent context defining processing behavior
4153 *
4154 * RETURN VALUES
4155 *   returns 0 on successful termination
4156 */
4157
4158#ifdef DAEMON
4159int daemon_start(AGENT_CTX *ATX) {
4160  DRIVER_CTX DTX;
4161  char *pidfile;
4162  ATX = ATX; /* Keep compiler happy */
4163  int exitcode = EXIT_SUCCESS;
4164
4165  if (ATX->fork && fork())    /* Fork DSPAM into the background */
4166    exit(exitcode);
4167
4168  __daemon_run  = 1;
4169  __num_threads = 0;
4170  __hup = 0;
4171  pthread_mutex_init(&__lock, NULL);
4172  if (libdspam_init(_ds_read_attribute(agent_config, "StorageDriver"))) {
4173    LOG(LOG_CRIT, ERR_DRV_INIT);
4174    // pthread_mutex_destroy(&__lock);
4175    exit(EXIT_FAILURE);
4176  }
4177
4178  LOG(LOG_INFO, INFO_DAEMON_START);
4179
4180  while(__daemon_run) {
4181
4182    DTX.CTX = dspam_create (NULL, NULL,
4183                      _ds_read_attribute(agent_config, "Home"),
4184                      DSM_TOOLS, 0);
4185    if (!DTX.CTX)
4186    {
4187      LOG(LOG_ERR, ERR_CORE_INIT);
4188      // pthread_mutex_destroy(&__lock);
4189      // libdspam_shutdown();
4190      exit(EXIT_FAILURE);
4191    }
4192
4193    set_libdspam_attributes(DTX.CTX);
4194    DTX.flags = DRF_STATEFUL;
4195
4196#ifdef DEBUG
4197    if (DO_DEBUG)
4198      DO_DEBUG = 2;
4199#endif
4200    if (dspam_init_driver (&DTX))
4201    {
4202      LOG (LOG_WARNING, ERR_DRV_INIT);
4203      // pthread_mutex_destroy(&__lock);
4204      // libdspam_shutdown();
4205      exit(EXIT_FAILURE);
4206    }
4207
4208    pidfile = _ds_read_attribute(agent_config, "ServerPID");
4209    if ( pidfile == NULL )
4210      pidfile = "/var/run/dspam/dspam.pid";
4211
4212    if (pidfile) {
4213      FILE *file;
4214      file = fopen(pidfile, "w");
4215      if (file == NULL) {
4216        LOG(LOG_ERR, ERR_IO_FILE_WRITE, pidfile, strerror(errno));
4217        dspam_shutdown_driver(&DTX);
4218        libdspam_shutdown();
4219        exit(EXIT_FAILURE);
4220      } else {
4221        fprintf(file, "%ld\n", (long) getpid());
4222        fclose(file);
4223      }
4224    }
4225
4226    LOGDEBUG("Spawning daemon listener");
4227
4228    if (daemon_listen(&DTX)) {
4229      LOG(LOG_CRIT, ERR_DAEMON_FAIL);
4230      __daemon_run = 0;
4231      exitcode = EXIT_FAILURE;
4232    } else {
4233      LOG(LOG_WARNING, "Received signal. Waiting for processing threads to exit.");
4234      while(__num_threads) {
4235        struct timeval tv;
4236        tv.tv_sec = 1;
4237        tv.tv_usec = 0;
4238        select(0, NULL, NULL, NULL, &tv);
4239      }
4240      LOG(LOG_WARNING, "Processing threads terminated.");
4241    }
4242
4243    /* only unlink pid file if daemon is shut down */
4244    if (pidfile && !__daemon_run)
4245      unlink(pidfile);
4246
4247    dspam_shutdown_driver(&DTX);
4248    dspam_destroy(DTX.CTX);
4249
4250    /* Reload */
4251    if (__hup) {
4252      LOG(LOG_WARNING, INFO_DAEMON_RELOAD);
4253
4254      if (agent_config)
4255        _ds_destroy_config(agent_config);
4256
4257      agent_config = read_config(NULL);
4258      if (!agent_config) {
4259        LOG(LOG_ERR, ERR_AGENT_READ_CONFIG);
4260        pthread_mutex_destroy(&__lock);
4261        libdspam_shutdown();
4262        exit(EXIT_FAILURE);
4263      }
4264
4265      __daemon_run = 1;
4266      __hup = 0;
4267    }
4268  }
4269
4270  LOG(LOG_WARNING, INFO_DAEMON_EXIT);
4271  pthread_mutex_destroy(&__lock);
4272  libdspam_shutdown();
4273
4274  return exitcode;
4275}
4276#endif
4277
4278/*
4279 * load_aggregated_prefs(AGENT_CTX *ATX, const char *username)
4280 *
4281 * DESCRIPTION
4282 *   Load and aggregate system+user preferences
4283 *
4284 * INPUT ARGUMENTS
4285 *   ATX          Agent context defining processing behavior
4286 *   username     Target user
4287 *
4288 * RETURN VALUES
4289 *   pointer to aggregated preference structure, NULL on failure
4290 */
4291
4292agent_pref_t load_aggregated_prefs(AGENT_CTX *ATX, const char *username) {
4293  agent_pref_t PTX = NULL;
4294  agent_pref_t STX = NULL;
4295  agent_pref_t UTX = NULL;
4296
4297  LOGDEBUG("loading preferences for user %s", username);
4298  UTX = _ds_pref_load(agent_config, username,
4299                      _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4300
4301  if (!UTX && _ds_match_attribute(agent_config, "FallbackDomains", "on")) {
4302    if (username != NULL && strchr(username, '@')) {
4303      char *domain = strchr(username, '@');
4304      if (domain) {
4305        UTX = _ds_pref_load(agent_config,
4306                            domain,
4307                            _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4308        if (UTX && !strcmp(_ds_pref_val(UTX, "fallbackDomain"), "on")) {
4309          LOGDEBUG("empty prefs found. falling back to %s", domain);
4310        } else {
4311          _ds_pref_free(UTX);
4312          UTX = NULL;
4313        }
4314      }
4315    } else {
4316      LOG(LOG_ERR, "load_aggregated_prefs(): Can not fallback to domains for username '%s' without @domain part.", username);
4317    }
4318  }
4319
4320  if (!UTX) {
4321    UTX = _ds_pref_load(agent_config, NULL,
4322                        _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4323  }
4324
4325  STX =  _ds_pref_load(agent_config, NULL,
4326                        _ds_read_attribute(agent_config, "Home"), ATX->dbh);
4327
4328  if (!STX || STX[0] == 0) {
4329    if (STX) {
4330      _ds_pref_free(STX);
4331    }
4332    LOGDEBUG("default preferences empty. reverting to dspam.conf preferences.");
4333    STX = pref_config();
4334  } else {
4335    LOGDEBUG("loaded default preferences externally");
4336  }
4337
4338  PTX = _ds_pref_aggregate(STX, UTX);
4339  _ds_pref_free(UTX);
4340  free(UTX);
4341  UTX = NULL;
4342  _ds_pref_free(STX);
4343  free(STX);
4344  STX = NULL;
4345
4346#ifdef VERBOSE
4347  if (PTX) {
4348    int j;
4349    for(j=0;PTX[j];j++) {
4350      LOGDEBUG("aggregated preference '%s' => '%s'",
4351               PTX[j]->attribute, PTX[j]->value);
4352    }
4353  }
4354#endif
4355
4356  return PTX;
4357}
4358
4359/*
4360 * do_notifications(DSPAM_CTX *CTX, AGENT_CTX *ATX)
4361 *
4362 * DESCRIPTION
4363 *   Evaluate and send notifications as necessary
4364 *
4365 * INPUT ARGUMENTS
4366 *   CTX          DSPAM context
4367 *   ATX          Agent context defining processing behavior
4368 *
4369 * RETURN VALUES
4370 *   returns 0 on success, standard errors in failure
4371 */
4372
4373int do_notifications(DSPAM_CTX *CTX, AGENT_CTX *ATX) {
4374  char filename[MAX_FILENAME_LENGTH];
4375  FILE *file;
4376
4377  /* First run notification */
4378
4379  if ((_ds_match_attribute(agent_config, "Notifications", "on") ||
4380      !strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "on")) &&
4381      strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "off")) {
4382    _ds_userdir_path(filename,
4383                    _ds_read_attribute(agent_config, "Home"),
4384                    LOOKUP(ATX->PTX, CTX->username), "firstrun");
4385    file = fopen(filename, "r");
4386    if (file == NULL) {
4387      LOGDEBUG("sending firstrun.txt to %s (%s): %s",
4388               CTX->username, filename, strerror(errno));
4389      send_notice(ATX, "firstrun.txt", ATX->mailer_args, CTX->username);
4390      _ds_prepare_path_for(filename);
4391      file = fopen(filename, "w");
4392      if (file) {
4393        fprintf(file, "%ld\n", (long) time(NULL));
4394        fclose(file);
4395      }
4396    } else {
4397      fclose(file);
4398    }
4399  }
4400
4401
4402  /* First spam notification */
4403
4404  if (CTX->result == DSR_ISSPAM &&
4405       (_ds_match_attribute(agent_config, "Notifications", "on") ||
4406        !strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "on")) &&
4407       strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "off"))
4408  {
4409    _ds_userdir_path(filename,
4410                    _ds_read_attribute(agent_config, "Home"),
4411                    LOOKUP(ATX->PTX, CTX->username), "firstspam");
4412    file = fopen(filename, "r");
4413    if (file == NULL) {
4414      LOGDEBUG("sending firstspam.txt to %s (%s): %s",
4415               CTX->username, filename, strerror(errno));
4416      send_notice(ATX, "firstspam.txt", ATX->mailer_args, CTX->username);
4417      _ds_prepare_path_for(filename);
4418      file = fopen(filename, "w");
4419      if (file) {
4420        fprintf(file, "%ld\n", (long) time(NULL));
4421        fclose(file);
4422      }
4423    } else {
4424      fclose(file);
4425    }
4426  }
4427
4428  /* Quarantine size notification */
4429
4430  if ((_ds_match_attribute(agent_config, "Notifications", "on") ||
4431      !strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "on")) &&
4432      strcasecmp(_ds_pref_val(ATX->PTX, "notifications"), "off")) {
4433    struct stat s;
4434    char qfile[MAX_FILENAME_LENGTH];
4435    int qwarn_size = 1024*1024*2;
4436
4437    if (_ds_read_attribute(agent_config, "QuarantineWarnSize")) {
4438      qwarn_size = atoi(_ds_read_attribute(agent_config, "QuarantineWarnSize"));
4439      if (qwarn_size == INT_MAX && errno == ERANGE) {
4440        LOG (LOG_INFO, "Value for 'QuarantineWarnSize' not valid (will use 2MB for now)");
4441        qwarn_size = 1024*1024*2;
4442      }
4443    }
4444
4445    _ds_userdir_path(qfile, _ds_read_attribute(agent_config, "Home"),
4446                     LOOKUP(ATX->PTX, CTX->username), "mbox");
4447
4448    if (!stat(qfile, &s) && s.st_size > qwarn_size) {
4449      _ds_userdir_path(qfile, _ds_read_attribute(agent_config, "Home"),
4450                       LOOKUP(ATX->PTX, CTX->username), "mboxwarn");
4451      if (stat(qfile, &s)) {
4452        FILE *f;
4453
4454        _ds_prepare_path_for(qfile);
4455        f = fopen(qfile, "w");
4456        if (f != NULL) {
4457          fprintf(f, "%ld", (long) time(NULL));
4458          fclose(f);
4459
4460          send_notice(ATX, "quarantinefull.txt", ATX->mailer_args, CTX->username);
4461        }
4462      }
4463    }
4464  }
4465
4466  return 0;
4467}
Note: See TracBrowser for help on using the repository browser.