~ubuntu-branches/ubuntu/vivid/exim4/vivid

« back to all changes in this revision

Viewing changes to .pc/81_buffer-overrun-in-spam-acl.diff/src/spam.c

  • Committer: Package Import Robot
  • Author(s): Colin Watson
  • Date: 2014-12-02 15:28:46 UTC
  • mfrom: (2.1.42 sid)
  • Revision ID: package-import@ubuntu.com-20141202152846-74f7xzu1l33292ll
Tags: 4.84-4ubuntu1
* Resynchronise with Debian.  Remaining changes:
  - Show Ubuntu distribution in SMTP banner.
  - Don't provide default-mta; in Ubuntu, we want postfix to be the
    default.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*************************************************
 
2
*     Exim - an Internet mail transport agent    *
 
3
*************************************************/
 
4
 
 
5
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */
 
6
/* License: GPL */
 
7
 
 
8
/* Code for calling spamassassin's spamd. Called from acl.c. */
 
9
 
 
10
#include "exim.h"
 
11
#ifdef WITH_CONTENT_SCAN
 
12
#include "spam.h"
 
13
 
 
14
uschar spam_score_buffer[16];
 
15
uschar spam_score_int_buffer[16];
 
16
uschar spam_bar_buffer[128];
 
17
uschar spam_report_buffer[32600];
 
18
uschar prev_user_name[128] = "";
 
19
int spam_ok = 0;
 
20
int spam_rc = 0;
 
21
uschar *prev_spamd_address_work = NULL;
 
22
 
 
23
int
 
24
spam(uschar **listptr)
 
25
{
 
26
  int sep = 0;
 
27
  uschar *list = *listptr;
 
28
  uschar *user_name;
 
29
  uschar user_name_buffer[128];
 
30
  unsigned long mbox_size;
 
31
  FILE *mbox_file;
 
32
  int spamd_sock = -1;
 
33
  uschar spamd_buffer[32600];
 
34
  int i, j, offset, result;
 
35
  uschar spamd_version[8];
 
36
  uschar spamd_score_char;
 
37
  double spamd_threshold, spamd_score;
 
38
  int spamd_report_offset;
 
39
  uschar *p,*q;
 
40
  int override = 0;
 
41
  time_t start;
 
42
  size_t read, wrote;
 
43
  struct sockaddr_un server;
 
44
#ifndef NO_POLL_H
 
45
  struct pollfd pollfd;
 
46
#else                               /* Patch posted by Erik ? for OS X */
 
47
  struct timeval select_tv;         /* and applied by PH */
 
48
  fd_set select_fd;
 
49
#endif
 
50
  uschar *spamd_address_work;
 
51
 
 
52
  /* stop compiler warning */
 
53
  result = 0;
 
54
 
 
55
  /* find the username from the option list */
 
56
  if ((user_name = string_nextinlist(&list, &sep,
 
57
                                     user_name_buffer,
 
58
                                     sizeof(user_name_buffer))) == NULL) {
 
59
    /* no username given, this means no scanning should be done */
 
60
    return FAIL;
 
61
  };
 
62
 
 
63
  /* if username is "0" or "false", do not scan */
 
64
  if ( (Ustrcmp(user_name,"0") == 0) ||
 
65
       (strcmpic(user_name,US"false") == 0) ) {
 
66
    return FAIL;
 
67
  };
 
68
 
 
69
  /* if there is an additional option, check if it is "true" */
 
70
  if (strcmpic(list,US"true") == 0) {
 
71
    /* in that case, always return true later */
 
72
    override = 1;
 
73
  };
 
74
 
 
75
  /* expand spamd_address if needed */
 
76
  if (*spamd_address == '$') {
 
77
    spamd_address_work = expand_string(spamd_address);
 
78
    if (spamd_address_work == NULL) {
 
79
      log_write(0, LOG_MAIN|LOG_PANIC,
 
80
        "spamassassin acl condition: spamd_address starts with $, but expansion failed: %s", expand_string_message);
 
81
      return DEFER;
 
82
    }
 
83
  }
 
84
  else
 
85
    spamd_address_work = spamd_address;
 
86
 
 
87
  /* check if previous spamd_address was expanded and has changed. dump cached results if so */
 
88
  if ( spam_ok && ( prev_spamd_address_work != NULL) && (Ustrcmp(prev_spamd_address_work, spamd_address_work) != 0)) {
 
89
    spam_ok = 0;
 
90
  }
 
91
 
 
92
  /* if we scanned for this username last time, just return */
 
93
  if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) ) {
 
94
    if (override)
 
95
      return OK;
 
96
    else
 
97
      return spam_rc;
 
98
  };
 
99
 
 
100
  /* make sure the eml mbox file is spooled up */
 
101
  mbox_file = spool_mbox(&mbox_size, NULL);
 
102
 
 
103
  if (mbox_file == NULL) {
 
104
    /* error while spooling */
 
105
    log_write(0, LOG_MAIN|LOG_PANIC,
 
106
           "spam acl condition: error while creating mbox spool file");
 
107
    return DEFER;
 
108
  };
 
109
 
 
110
  start = time(NULL);
 
111
 
 
112
  /* socket does not start with '/' -> network socket */
 
113
  if (*spamd_address_work != '/') {
 
114
    int num_servers = 0;
 
115
    int current_server;
 
116
    uschar *address = NULL;
 
117
    uschar *spamd_address_list_ptr = spamd_address_work;
 
118
    uschar address_buffer[256];
 
119
    spamd_address_container * spamd_address_vector[32];
 
120
 
 
121
    /* Check how many spamd servers we have
 
122
       and register their addresses */
 
123
    while ((address = string_nextinlist(&spamd_address_list_ptr, &sep,
 
124
                                        address_buffer,
 
125
                                        sizeof(address_buffer))) != NULL) {
 
126
 
 
127
      /* Potential memory leak as we never free the store. */
 
128
      spamd_address_container *this_spamd =
 
129
        (spamd_address_container *)store_get(sizeof(spamd_address_container));
 
130
 
 
131
      /* grok spamd address and port */
 
132
      if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) {
 
133
        log_write(0, LOG_MAIN,
 
134
          "spam acl condition: warning - invalid spamd address: '%s'", address);
 
135
        continue;
 
136
      };
 
137
 
 
138
      spamd_address_vector[num_servers] = this_spamd;
 
139
      num_servers++;
 
140
      if (num_servers > 31)
 
141
        break;
 
142
    };
 
143
 
 
144
    /* check if we have at least one server */
 
145
    if (!num_servers) {
 
146
      log_write(0, LOG_MAIN|LOG_PANIC,
 
147
         "spam acl condition: no useable spamd server addresses in spamd_address configuration option.");
 
148
      (void)fclose(mbox_file);
 
149
      return DEFER;
 
150
    };
 
151
 
 
152
    while ( num_servers > 0 ) {
 
153
      int i;
 
154
 
 
155
      /* Randomly pick a server to try */
 
156
      current_server = random_number( num_servers );
 
157
 
 
158
      debug_printf("trying server %s, port %u\n",
 
159
                   spamd_address_vector[current_server]->tcp_addr,
 
160
                   spamd_address_vector[current_server]->tcp_port);
 
161
 
 
162
      /* contact a spamd */
 
163
      if ( (spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
 
164
        log_write(0, LOG_MAIN|LOG_PANIC,
 
165
           "spam acl condition: error creating IP socket for spamd");
 
166
        (void)fclose(mbox_file);
 
167
        return DEFER;
 
168
      };
 
169
 
 
170
      if (ip_connect( spamd_sock,
 
171
                      AF_INET,
 
172
                      spamd_address_vector[current_server]->tcp_addr,
 
173
                      spamd_address_vector[current_server]->tcp_port,
 
174
                      5 ) > -1) {
 
175
        /* connection OK */
 
176
        break;
 
177
      };
 
178
 
 
179
      log_write(0, LOG_MAIN|LOG_PANIC,
 
180
         "spam acl condition: warning - spamd connection to %s, port %u failed: %s",
 
181
         spamd_address_vector[current_server]->tcp_addr,
 
182
         spamd_address_vector[current_server]->tcp_port,
 
183
         strerror(errno));
 
184
 
 
185
      (void)close(spamd_sock);
 
186
 
 
187
      /* Remove the server from the list. XXX We should free the memory */
 
188
      num_servers--;
 
189
      for( i = current_server; i < num_servers; i++ )
 
190
        spamd_address_vector[i] = spamd_address_vector[i+1];
 
191
    }
 
192
 
 
193
    if ( num_servers == 0 ) {
 
194
      log_write(0, LOG_MAIN|LOG_PANIC, "spam acl condition: all spamd servers failed");
 
195
      (void)fclose(mbox_file);
 
196
      return DEFER;
 
197
    }
 
198
 
 
199
  }
 
200
  else {
 
201
    /* open the local socket */
 
202
 
 
203
    if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
 
204
      log_write(0, LOG_MAIN|LOG_PANIC,
 
205
                "malware acl condition: spamd: unable to acquire socket (%s)",
 
206
                strerror(errno));
 
207
      (void)fclose(mbox_file);
 
208
      return DEFER;
 
209
    }
 
210
 
 
211
    server.sun_family = AF_UNIX;
 
212
    Ustrcpy(server.sun_path, spamd_address_work);
 
213
 
 
214
    if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
 
215
      log_write(0, LOG_MAIN|LOG_PANIC,
 
216
                "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)",
 
217
                spamd_address_work, strerror(errno) );
 
218
      (void)fclose(mbox_file);
 
219
      (void)close(spamd_sock);
 
220
      return DEFER;
 
221
    }
 
222
 
 
223
  }
 
224
 
 
225
  if (spamd_sock == -1) {
 
226
    log_write(0, LOG_MAIN|LOG_PANIC,
 
227
        "programming fault, spamd_sock unexpectedly unset");
 
228
    (void)fclose(mbox_file);
 
229
    (void)close(spamd_sock);
 
230
    return DEFER;
 
231
  }
 
232
 
 
233
  /* now we are connected to spamd on spamd_sock */
 
234
  (void)string_format(spamd_buffer,
 
235
           sizeof(spamd_buffer),
 
236
           "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
 
237
           user_name,
 
238
           mbox_size);
 
239
 
 
240
  /* send our request */
 
241
  if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) {
 
242
    (void)close(spamd_sock);
 
243
    log_write(0, LOG_MAIN|LOG_PANIC,
 
244
         "spam acl condition: spamd send failed: %s", strerror(errno));
 
245
    (void)fclose(mbox_file);
 
246
    (void)close(spamd_sock);
 
247
    return DEFER;
 
248
  };
 
249
 
 
250
  /* now send the file */
 
251
  /* spamd sometimes accepts conections but doesn't read data off
 
252
   * the connection.  We make the file descriptor non-blocking so
 
253
   * that the write will only write sufficient data without blocking
 
254
   * and we poll the desciptor to make sure that we can write without
 
255
   * blocking.  Short writes are gracefully handled and if the whole
 
256
   * trasaction takes too long it is aborted.
 
257
   * Note: poll() is not supported in OSX 10.2 and is reported to be
 
258
   *       broken in more recent versions (up to 10.4).
 
259
   */
 
260
#ifndef NO_POLL_H
 
261
  pollfd.fd = spamd_sock;
 
262
  pollfd.events = POLLOUT;
 
263
#endif
 
264
  (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
 
265
  do {
 
266
    read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
 
267
    if (read > 0) {
 
268
      offset = 0;
 
269
again:
 
270
#ifndef NO_POLL_H
 
271
      result = poll(&pollfd, 1, 1000);
 
272
 
 
273
/* Patch posted by Erik ? for OS X and applied by PH */
 
274
#else
 
275
      select_tv.tv_sec = 1;
 
276
      select_tv.tv_usec = 0;
 
277
      FD_ZERO(&select_fd);
 
278
      FD_SET(spamd_sock, &select_fd);
 
279
      result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
 
280
#endif
 
281
/* End Erik's patch */
 
282
 
 
283
      if (result == -1 && errno == EINTR)
 
284
        goto again;
 
285
      else if (result < 1) {
 
286
        if (result == -1)
 
287
          log_write(0, LOG_MAIN|LOG_PANIC,
 
288
            "spam acl condition: %s on spamd socket", strerror(errno));
 
289
        else {
 
290
          if (time(NULL) - start < SPAMD_TIMEOUT)
 
291
            goto again;
 
292
          log_write(0, LOG_MAIN|LOG_PANIC,
 
293
            "spam acl condition: timed out writing spamd socket");
 
294
        }
 
295
        (void)close(spamd_sock);
 
296
        (void)fclose(mbox_file);
 
297
        return DEFER;
 
298
      }
 
299
 
 
300
      wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0);
 
301
      if (wrote == -1)
 
302
      {
 
303
          log_write(0, LOG_MAIN|LOG_PANIC,
 
304
            "spam acl condition: %s on spamd socket", strerror(errno));
 
305
        (void)close(spamd_sock);
 
306
        (void)fclose(mbox_file);
 
307
        return DEFER;
 
308
      }
 
309
      if (offset + wrote != read) {
 
310
        offset += wrote;
 
311
        goto again;
 
312
      }
 
313
    }
 
314
  }
 
315
  while (!feof(mbox_file) && !ferror(mbox_file));
 
316
  if (ferror(mbox_file)) {
 
317
    log_write(0, LOG_MAIN|LOG_PANIC,
 
318
      "spam acl condition: error reading spool file: %s", strerror(errno));
 
319
    (void)close(spamd_sock);
 
320
    (void)fclose(mbox_file);
 
321
    return DEFER;
 
322
  }
 
323
 
 
324
  (void)fclose(mbox_file);
 
325
 
 
326
  /* we're done sending, close socket for writing */
 
327
  shutdown(spamd_sock,SHUT_WR);
 
328
 
 
329
  /* read spamd response using what's left of the timeout.
 
330
   */
 
331
  memset(spamd_buffer, 0, sizeof(spamd_buffer));
 
332
  offset = 0;
 
333
  while((i = ip_recv(spamd_sock,
 
334
                     spamd_buffer + offset,
 
335
                     sizeof(spamd_buffer) - offset - 1,
 
336
                     SPAMD_TIMEOUT - time(NULL) + start)) > 0 ) {
 
337
    offset += i;
 
338
  }
 
339
 
 
340
  /* error handling */
 
341
  if((i <= 0) && (errno != 0)) {
 
342
    log_write(0, LOG_MAIN|LOG_PANIC,
 
343
         "spam acl condition: error reading from spamd socket: %s", strerror(errno));
 
344
    (void)close(spamd_sock);
 
345
    return DEFER;
 
346
  }
 
347
 
 
348
  /* reading done */
 
349
  (void)close(spamd_sock);
 
350
 
 
351
  /* dig in the spamd output and put the report in a multiline header, if requested */
 
352
  if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
 
353
             spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
 
354
 
 
355
    /* try to fall back to pre-2.50 spamd output */
 
356
    if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
 
357
               spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
 
358
      log_write(0, LOG_MAIN|LOG_PANIC,
 
359
         "spam acl condition: cannot parse spamd output");
 
360
      return DEFER;
 
361
    };
 
362
  };
 
363
 
 
364
  /* Create report. Since this is a multiline string,
 
365
  we must hack it into shape first */
 
366
  p = &spamd_buffer[spamd_report_offset];
 
367
  q = spam_report_buffer;
 
368
  while (*p != '\0') {
 
369
    /* skip \r */
 
370
    if (*p == '\r') {
 
371
      p++;
 
372
      continue;
 
373
    };
 
374
    *q = *p;
 
375
    q++;
 
376
    if (*p == '\n') {
 
377
      /* add an extra space after the newline to ensure
 
378
      that it is treated as a header continuation line */
 
379
      *q = ' ';
 
380
      q++;
 
381
    };
 
382
    p++;
 
383
  };
 
384
  /* NULL-terminate */
 
385
  *q = '\0';
 
386
  q--;
 
387
  /* cut off trailing leftovers */
 
388
  while (*q <= ' ') {
 
389
    *q = '\0';
 
390
    q--;
 
391
  };
 
392
  spam_report = spam_report_buffer;
 
393
 
 
394
  /* create spam bar */
 
395
  spamd_score_char = spamd_score > 0 ? '+' : '-';
 
396
  j = abs((int)(spamd_score));
 
397
  i = 0;
 
398
  if( j != 0 ) {
 
399
    while((i < j) && (i <= MAX_SPAM_BAR_CHARS))
 
400
       spam_bar_buffer[i++] = spamd_score_char;
 
401
  }
 
402
  else{
 
403
    spam_bar_buffer[0] = '/';
 
404
    i = 1;
 
405
  }
 
406
  spam_bar_buffer[i] = '\0';
 
407
  spam_bar = spam_bar_buffer;
 
408
 
 
409
  /* create "float" spam score */
 
410
  (void)string_format(spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score);
 
411
  spam_score = spam_score_buffer;
 
412
 
 
413
  /* create "int" spam score */
 
414
  j = (int)((spamd_score + 0.001)*10);
 
415
  (void)string_format(spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j);
 
416
  spam_score_int = spam_score_int_buffer;
 
417
 
 
418
  /* compare threshold against score */
 
419
  if (spamd_score >= spamd_threshold) {
 
420
    /* spam as determined by user's threshold */
 
421
    spam_rc = OK;
 
422
  }
 
423
  else {
 
424
    /* not spam */
 
425
    spam_rc = FAIL;
 
426
  };
 
427
 
 
428
  /* remember expanded spamd_address if needed */
 
429
  if (spamd_address_work != spamd_address) {
 
430
    prev_spamd_address_work = string_copy(spamd_address_work);
 
431
  }
 
432
  /* remember user name and "been here" for it */
 
433
  Ustrcpy(prev_user_name, user_name);
 
434
  spam_ok = 1;
 
435
 
 
436
  if (override) {
 
437
    /* always return OK, no matter what the score */
 
438
    return OK;
 
439
  }
 
440
  else {
 
441
    return spam_rc;
 
442
  };
 
443
}
 
444
 
 
445
#endif