~ubuntu-branches/ubuntu/precise/dspam/precise

« back to all changes in this revision

Viewing changes to src/dspam.c

  • Committer: Bazaar Package Importer
  • Author(s): Trent Lloyd
  • Date: 2006-03-21 07:23:12 UTC
  • Revision ID: james.westby@ubuntu.com-20060321072312-jba9a1avit4r1y6s
Tags: upstream-3.6.4
ImportĀ upstreamĀ versionĀ 3.6.4

Show diffs side-by-side

added added

removed removed

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