Beispiel #1
0
int
spam(const uschar **listptr)
{
int sep = 0;
const uschar *list = *listptr;
uschar *user_name;
uschar user_name_buffer[128];
unsigned long mbox_size;
FILE *mbox_file;
int spamd_sock = -1;
uschar spamd_buffer[32600];
int i, j, offset, result;
uschar spamd_version[8];
uschar spamd_short_result[8];
uschar spamd_score_char;
double spamd_threshold, spamd_score, spamd_reject_score;
int spamd_report_offset;
uschar *p,*q;
int override = 0;
time_t start;
size_t read, wrote;
#ifndef NO_POLL_H
struct pollfd pollfd;
#else                               /* Patch posted by Erik ? for OS X */
struct timeval select_tv;         /* and applied by PH */
fd_set select_fd;
#endif
uschar *spamd_address_work;
spamd_address_container * sd;

/* stop compiler warning */
result = 0;

/* find the username from the option list */
if ((user_name = string_nextinlist(&list, &sep,
				   user_name_buffer,
				   sizeof(user_name_buffer))) == NULL)
  {
  /* no username given, this means no scanning should be done */
  return FAIL;
  }

/* if username is "0" or "false", do not scan */
if ( (Ustrcmp(user_name,"0") == 0) ||
     (strcmpic(user_name,US"false") == 0) )
  return FAIL;

/* if there is an additional option, check if it is "true" */
if (strcmpic(list,US"true") == 0)
  /* in that case, always return true later */
  override = 1;

/* expand spamd_address if needed */
if (*spamd_address == '$')
  {
  spamd_address_work = expand_string(spamd_address);
  if (spamd_address_work == NULL)
    {
    log_write(0, LOG_MAIN|LOG_PANIC,
      "%s spamd_address starts with $, but expansion failed: %s",
      loglabel, expand_string_message);
    return DEFER;
    }
  }
else
  spamd_address_work = spamd_address;

DEBUG(D_acl) debug_printf("spamd: addrlist '%s'\n", spamd_address_work);

/* check if previous spamd_address was expanded and has changed. dump cached results if so */
if (  spam_ok
   && prev_spamd_address_work != NULL
   && Ustrcmp(prev_spamd_address_work, spamd_address_work) != 0
   )
  spam_ok = 0;

/* if we scanned for this username last time, just return */
if (spam_ok && Ustrcmp(prev_user_name, user_name) == 0)
  return override ? OK : spam_rc;

/* make sure the eml mbox file is spooled up */
mbox_file = spool_mbox(&mbox_size, NULL);

if (mbox_file == NULL)
  {
  /* error while spooling */
  log_write(0, LOG_MAIN|LOG_PANIC,
	 "%s error while creating mbox spool file", loglabel);
  return DEFER;
  }

start = time(NULL);

  {
  int num_servers = 0;
  int current_server;
  uschar * address;
  const uschar * spamd_address_list_ptr = spamd_address_work;
  spamd_address_container * spamd_address_vector[32];

  /* Check how many spamd servers we have
     and register their addresses */
  sep = 0;				/* default colon-sep */
  while ((address = string_nextinlist(&spamd_address_list_ptr, &sep,
				      NULL, 0)) != NULL)
    {
    const uschar * sublist;
    int sublist_sep = -(int)' ';	/* default space-sep */
    unsigned args;
    uschar * s;

    DEBUG(D_acl) debug_printf("spamd: addr entry '%s'\n", address);
    sd = (spamd_address_container *)store_get(sizeof(spamd_address_container));

    for (sublist = address, args = 0, spamd_param_init(sd);
	 (s = string_nextinlist(&sublist, &sublist_sep, NULL, 0));
	 args++
	 )
      {
	DEBUG(D_acl) debug_printf("spamd:  addr parm '%s'\n", s);
	switch (args)
	{
	case 0:   sd->hostspec = s;
		  if (*s == '/') args++;	/* local; no port */
		  break;
	case 1:   sd->hostspec = string_sprintf("%s %s", sd->hostspec, s);
		  break;
	default:  spamd_param(s, sd);
		  break;
	}
      }
    if (args < 2)
      {
      log_write(0, LOG_MAIN,
	"%s warning - invalid spamd address: '%s'", loglabel, address);
      continue;
      }

    spamd_address_vector[num_servers] = sd;
    if (++num_servers > 31)
      break;
    }

  /* check if we have at least one server */
  if (!num_servers)
    {
    log_write(0, LOG_MAIN|LOG_PANIC,
       "%s no useable spamd server addresses in spamd_address configuration option.",
       loglabel);
    goto defer;
    }

  current_server = spamd_get_server(spamd_address_vector, num_servers);
  sd = spamd_address_vector[current_server];
  for(;;)
    {
    uschar * errstr;

    DEBUG(D_acl) debug_printf("spamd: trying server %s\n", sd->hostspec);

    for (;;)
      {
      if (  (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
         || sd->retry <= 0
	 )
	break;
      DEBUG(D_acl) debug_printf("spamd: server %s: retry conn\n", sd->hostspec);
      while (sd->retry > 0) sd->retry = sleep(sd->retry);
      }
    if (spamd_sock >= 0)
      break;

    log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr);
    sd->is_failed = TRUE;

    current_server = spamd_get_server(spamd_address_vector, num_servers);
    if (current_server < 0)
      {
      log_write(0, LOG_MAIN|LOG_PANIC, "%s all spamd servers failed", loglabel);
      goto defer;
      }
    sd = spamd_address_vector[current_server];
    }
  }

(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
/* now we are connected to spamd on spamd_sock */
if (sd->is_rspamd)
  {				/* rspamd variant */
  uschar *req_str;
  const uschar * helo;
  const uschar * fcrdns;
  const uschar * authid;

  req_str = string_sprintf("CHECK RSPAMC/1.3\r\nContent-length: %lu\r\n"
    "Queue-Id: %s\r\nFrom: <%s>\r\nRecipient-Number: %d\r\n",
    mbox_size, message_id, sender_address, recipients_count);
  for (i = 0; i < recipients_count; i ++)
    req_str = string_sprintf("%sRcpt: <%s>\r\n", req_str, recipients_list[i].address);
  if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0')
    req_str = string_sprintf("%sHelo: %s\r\n", req_str, helo);
  if ((fcrdns = expand_string(US"$sender_host_name")) != NULL && *fcrdns != '\0')
    req_str = string_sprintf("%sHostname: %s\r\n", req_str, fcrdns);
  if (sender_host_address != NULL)
    req_str = string_sprintf("%sIP: %s\r\n", req_str, sender_host_address);
  if ((authid = expand_string(US"$authenticated_id")) != NULL && *authid != '\0')
    req_str = string_sprintf("%sUser: %s\r\n", req_str, authid);
  req_str = string_sprintf("%s\r\n", req_str);
  wrote = send(spamd_sock, req_str, Ustrlen(req_str), 0);
  }
else
  {				/* spamassassin variant */
  (void)string_format(spamd_buffer,
	  sizeof(spamd_buffer),
	  "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
	  user_name,
	  mbox_size);
  /* send our request */
  wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
  }

if (wrote == -1)
  {
  (void)close(spamd_sock);
  log_write(0, LOG_MAIN|LOG_PANIC,
       "%s spamd %s send failed: %s", loglabel, callout_address, strerror(errno));
  goto defer;
  }

/* now send the file */
/* spamd sometimes accepts conections but doesn't read data off
 * the connection.  We make the file descriptor non-blocking so
 * that the write will only write sufficient data without blocking
 * and we poll the desciptor to make sure that we can write without
 * blocking.  Short writes are gracefully handled and if the whole
 * trasaction takes too long it is aborted.
 * Note: poll() is not supported in OSX 10.2 and is reported to be
 *       broken in more recent versions (up to 10.4).
 */
#ifndef NO_POLL_H
pollfd.fd = spamd_sock;
pollfd.events = POLLOUT;
#endif
(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
do
  {
  read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
  if (read > 0)
    {
    offset = 0;
again:
#ifndef NO_POLL_H
    result = poll(&pollfd, 1, 1000);

/* Patch posted by Erik ? for OS X and applied by PH */
#else
    select_tv.tv_sec = 1;
    select_tv.tv_usec = 0;
    FD_ZERO(&select_fd);
    FD_SET(spamd_sock, &select_fd);
    result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
#endif
/* End Erik's patch */

    if (result == -1 && errno == EINTR)
      goto again;
    else if (result < 1)
      {
      if (result == -1)
	log_write(0, LOG_MAIN|LOG_PANIC,
	  "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
      else
	{
	if (time(NULL) - start < sd->timeout)
	  goto again;
	log_write(0, LOG_MAIN|LOG_PANIC,
	  "%s timed out writing spamd %s, socket", loglabel, callout_address);
	}
      (void)close(spamd_sock);
      goto defer;
      }

    wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0);
    if (wrote == -1)
      {
      log_write(0, LOG_MAIN|LOG_PANIC,
	  "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
      (void)close(spamd_sock);
      goto defer;
      }
    if (offset + wrote != read)
      {
      offset += wrote;
      goto again;
      }
    }
  }
while (!feof(mbox_file) && !ferror(mbox_file));

if (ferror(mbox_file))
  {
  log_write(0, LOG_MAIN|LOG_PANIC,
    "%s error reading spool file: %s", loglabel, strerror(errno));
  (void)close(spamd_sock);
  goto defer;
  }

(void)fclose(mbox_file);

/* we're done sending, close socket for writing */
shutdown(spamd_sock,SHUT_WR);

/* read spamd response using what's left of the timeout.  */
memset(spamd_buffer, 0, sizeof(spamd_buffer));
offset = 0;
while ((i = ip_recv(spamd_sock,
		   spamd_buffer + offset,
		   sizeof(spamd_buffer) - offset - 1,
		   sd->timeout - time(NULL) + start)) > 0)
  offset += i;
spamd_buffer[offset] = '\0';	/* guard byte */

/* error handling */
if (i <= 0 && errno != 0)
  {
  log_write(0, LOG_MAIN|LOG_PANIC,
       "%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno));
  (void)close(spamd_sock);
  return DEFER;
  }

/* reading done */
(void)close(spamd_sock);

if (sd->is_rspamd)
  {				/* rspamd variant of reply */
  int r;
  if (  (r = sscanf(CS spamd_buffer,
	  "RSPAMD/%7s 0 EX_OK\r\nMetric: default; %7s %lf / %lf / %lf\r\n%n",
	  spamd_version, spamd_short_result, &spamd_score, &spamd_threshold,
	  &spamd_reject_score, &spamd_report_offset)) != 5
     || spamd_report_offset >= offset		/* verify within buffer */
     )
    {
    log_write(0, LOG_MAIN|LOG_PANIC,
	      "%s cannot parse spamd %s, output: %d", loglabel, callout_address, r);
    return DEFER;
    }
  /* now parse action */
  p = &spamd_buffer[spamd_report_offset];

  if (Ustrncmp(p, "Action: ", sizeof("Action: ") - 1) == 0)
    {
    p += sizeof("Action: ") - 1;
    q = &spam_action_buffer[0];
    while (*p && *p != '\r' && (q - spam_action_buffer) < sizeof(spam_action_buffer) - 1)
      *q++ = *p++;
    *q = '\0';
    }
  }
else
  {				/* spamassassin */
  /* dig in the spamd output and put the report in a multiline header,
  if requested */
  if (sscanf(CS spamd_buffer,
       "SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
       spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3)
    {
      /* try to fall back to pre-2.50 spamd output */
      if (sscanf(CS spamd_buffer,
	   "SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
	   spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3)
	{
	log_write(0, LOG_MAIN|LOG_PANIC,
		  "%s cannot parse spamd %s output", loglabel, callout_address);
	return DEFER;
	}
    }

  Ustrcpy(spam_action_buffer,
    spamd_score >= spamd_threshold ? "reject" : "no action");
  }

/* Create report. Since this is a multiline string,
we must hack it into shape first */
p = &spamd_buffer[spamd_report_offset];
q = spam_report_buffer;
while (*p != '\0')
  {
  /* skip \r */
  if (*p == '\r')
    {
    p++;
    continue;
    }
  *q++ = *p;
  if (*p++ == '\n')
    {
    /* add an extra space after the newline to ensure
    that it is treated as a header continuation line */
    *q++ = ' ';
    }
  }
/* NULL-terminate */
*q-- = '\0';
/* cut off trailing leftovers */
while (*q <= ' ')
  *q-- = '\0';

spam_report = spam_report_buffer;
spam_action = spam_action_buffer;

/* create spam bar */
spamd_score_char = spamd_score > 0 ? '+' : '-';
j = abs((int)(spamd_score));
i = 0;
if (j != 0)
  while ((i < j) && (i <= MAX_SPAM_BAR_CHARS))
     spam_bar_buffer[i++] = spamd_score_char;
else
  {
  spam_bar_buffer[0] = '/';
  i = 1;
  }
spam_bar_buffer[i] = '\0';
spam_bar = spam_bar_buffer;

/* create "float" spam score */
(void)string_format(spam_score_buffer, sizeof(spam_score_buffer),
	"%.1f", spamd_score);
spam_score = spam_score_buffer;

/* create "int" spam score */
j = (int)((spamd_score + 0.001)*10);
(void)string_format(spam_score_int_buffer, sizeof(spam_score_int_buffer),
	"%d", j);
spam_score_int = spam_score_int_buffer;

/* compare threshold against score */
spam_rc = spamd_score >= spamd_threshold
  ? OK	/* spam as determined by user's threshold */
  : FAIL;	/* not spam */

/* remember expanded spamd_address if needed */
if (spamd_address_work != spamd_address)
  prev_spamd_address_work = string_copy(spamd_address_work);

/* remember user name and "been here" for it */
Ustrcpy(prev_user_name, user_name);
spam_ok = 1;

return override
  ? OK		/* always return OK, no matter what the score */
  : spam_rc;

defer:
  (void)fclose(mbox_file);
  return DEFER;
}
Beispiel #2
0
int regex(uschar **listptr) {
  int sep = 0;
  uschar *list = *listptr;
  uschar *regex_string;
  uschar regex_string_buffer[1024];
  unsigned long mbox_size;
  FILE *mbox_file;
  pcre *re;
  pcre_list *re_list_head = NULL;
  pcre_list *re_list_item;
  const char *pcre_error;
  int pcre_erroffset;
  uschar *linebuffer;
  long f_pos = 0;

  /* reset expansion variable */
  regex_match_string = NULL;

  if (mime_stream == NULL) {
    /* We are in the DATA ACL */
    mbox_file = spool_mbox(&mbox_size, NULL);
    if (mbox_file == NULL) {
      /* error while spooling */
      log_write(0, LOG_MAIN|LOG_PANIC,
             "regex acl condition: error while creating mbox spool file");
      return DEFER;
    };
  }
  else {
    f_pos = ftell(mime_stream);
    mbox_file = mime_stream;
  };

  /* precompile our regexes */
  while ((regex_string = string_nextinlist(&list, &sep,
                                           regex_string_buffer,
                                           sizeof(regex_string_buffer))) != NULL) {

    /* parse option */
    if ( (strcmpic(regex_string,US"false") == 0) ||
         (Ustrcmp(regex_string,"0") == 0) ) {
      /* explicitly no matching */
      continue;
    };

    /* compile our regular expression */
    re = pcre_compile( CS regex_string,
                       0,
                       &pcre_error,
                       &pcre_erroffset,
                       NULL );

    if (re == NULL) {
      log_write(0, LOG_MAIN,
           "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset);
      continue;
    }
    else {
      re_list_item = store_get(sizeof(pcre_list));
      re_list_item->re = re;
      re_list_item->pcre_text = string_copy(regex_string);
      re_list_item->next = re_list_head;
      re_list_head = re_list_item;
    };
  };

  /* no regexes -> nothing to do */
  if (re_list_head == NULL) {
    return FAIL;
  };

  /* match each line against all regexes */
  linebuffer = store_get(32767);
  while (fgets(CS linebuffer, 32767, mbox_file) != NULL) {
    if ( (mime_stream != NULL) && (mime_current_boundary != NULL) ) {
      /* check boundary */
      if (Ustrncmp(linebuffer,"--",2) == 0) {
        if (Ustrncmp((linebuffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0)
          /* found boundary */
          break;
      };
    };
    re_list_item = re_list_head;
    do {
      /* try matcher on the line */
      if (pcre_exec(re_list_item->re, NULL, CS linebuffer,
      (int)Ustrlen(linebuffer), 0, 0, NULL, 0) >= 0) {
        Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023);
        regex_match_string = regex_match_string_buffer;
        if (mime_stream == NULL)
          (void)fclose(mbox_file);
        else {
          clearerr(mime_stream);
          fseek(mime_stream,f_pos,SEEK_SET);
        };
        return OK;
      };
      re_list_item = re_list_item->next;
    } while (re_list_item != NULL);
  };

  if (mime_stream == NULL)
    (void)fclose(mbox_file);
  else {
    clearerr(mime_stream);
    fseek(mime_stream,f_pos,SEEK_SET);
  };

  /* no matches ... */
  return FAIL;
}
Beispiel #3
0
int
regex(const uschar **listptr)
{
unsigned long mbox_size;
FILE *mbox_file;
pcre_list *re_list_head;
uschar *linebuffer;
long f_pos = 0;
int ret = FAIL;

/* reset expansion variable */
regex_match_string = NULL;

if (!mime_stream)				/* We are in the DATA ACL */
  {
  if (!(mbox_file = spool_mbox(&mbox_size, NULL)))
    {						/* error while spooling */
    log_write(0, LOG_MAIN|LOG_PANIC,
	   "regex acl condition: error while creating mbox spool file");
    return DEFER;
    }
  }
else
  {
  if ((f_pos = ftell(mime_stream)) < 0)
    {
    log_write(0, LOG_MAIN|LOG_PANIC,
	   "regex acl condition: mime_stream: %s", strerror(errno));
    return DEFER;
    }
  mbox_file = mime_stream;
  }

/* precompile our regexes */
if (!(re_list_head = compile(*listptr)))
  return FAIL;			/* no regexes -> nothing to do */

/* match each line against all regexes */
linebuffer = store_get(32767);
while (fgets(CS linebuffer, 32767, mbox_file))
  {
  if (  mime_stream && mime_current_boundary		/* check boundary */
     && Ustrncmp(linebuffer, "--", 2) == 0
     && Ustrncmp((linebuffer+2), mime_current_boundary,
		  Ustrlen(mime_current_boundary)) == 0)
      break;						/* found boundary */

  if ((ret = matcher(re_list_head, linebuffer, (int)Ustrlen(linebuffer))) == OK)
    goto done;
  }
/* no matches ... */

done:
if (!mime_stream)
  (void)fclose(mbox_file);
else
  {
  clearerr(mime_stream);
  fseek(mime_stream, f_pos, SEEK_SET);
  }

return ret;
}