Logo Search packages:      
Sourcecode: dcc version File versions  Download package

cmn.c

/* Distributed Checksum Clearinghouse
 *
 * threaded version of client library
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.38 $Revision$
 */


#include "cmn_defs.h"

CMN_ACTION action = CMN_REJECT;

CHGHDR chghdr = SETHDR;

static const char *userdirs;
static int userdirs_len;

u_char dcc_query_only = 0;
u_char try_extra_hard = 0;          /* 1=don't quit if DCC server dead */
u_char to_white_only = 0;
const char *mapfile_nm = DCC_MAP_NM_DEF;
const char *main_white_nm;

u_int dcc_ctxt_sn = 1;              /* change X-DCC header server name */

RCPT_ST *rcpt_st_free;

static pthread_mutex_t cmn_wf_mutex;
static pthread_mutex_t work_mutex;

static pthread_mutex_t cwf_mutex;
typedef struct cwf {                /* private whitelist state */
    struct cwf *older, *newer;
    pthread_mutex_t mutex;
    DCC_WF  wf;
} CWF;
static CWF *cur_cwf, cwfs[NUM_CWFS];


void
cmn_init(DCC_EMSG emsg)
{
      /* Some pthreads implementations (e.g. AIX) don't like static
       * POSIX thread initializations */
      pthread_mutex_init(&work_mutex, 0);

      /* start the client library threads and locks */
      dcc_clnt_thread_init();
      dcc_wf_init(&cmn_wf, &cmn_wf_mutex, 0);
      for (cur_cwf = cwfs; cur_cwf <= LAST(cwfs); ++cur_cwf) {
            dcc_wf_init(&cur_cwf->wf, &cur_cwf->mutex, 1);
            cur_cwf->newer = cur_cwf-1;
            cur_cwf->older = cur_cwf+1;
      }
      cur_cwf = cwfs;
      LAST(cwfs)->older = cur_cwf;
      cur_cwf->newer = LAST(cwfs);
      dcc_mutex_init(&cwf_mutex, "");

      if (!dcc_map_info(emsg, mapfile_nm, -1))
            dcc_logbad(EX_USAGE, "%s", emsg);

      if (main_white_nm) {
            if (!dcc_white_rdy(emsg, &cmn_wf, main_white_nm,
                           DCC_WHITE_RDY_LOCK_NEED_MUTEX, 0))
                  dcc_error_msg("%s", emsg);
            dcc_wf_unlock(&cmn_wf);
      }

      totals_init();
}



void
lock_work(void)
{
      int result = pthread_mutex_lock(&work_mutex);
      if (result)
            dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(work_free): %s",
                     ERROR_STR1(result));
}



void
unlock_work(void)
{
      int result = pthread_mutex_unlock(&work_mutex);
      if (result)
            dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(work_free): %s",
                     ERROR_STR1(result));
}



void
cmn_create(CMN_WORK *cwp)
{
      cwp->log_fd = -1;
      cwp->log_fd2 = -1;
}



void
cmn_clear(CMN_WORK *cwp, struct work *wp)
{
      log_stop(cwp);

      if (cwp->num_rcpts)
            rcpts_free(cwp, 1);

      cwp->emsg[0] = '\0';
      cwp->id[0] = '\0';
      memset(&cwp->CMN_WORK_ZERO, 0,
             sizeof(*cwp) - ((char*)&cwp->CMN_WORK_ZERO - (char*)cwp));
      cwp->wp = wp;
      cwp->action = action;
}



void
rcpts_create(int i)
{
      RCPT_ST *rcpt_st;

      rcpt_st = dcc_malloc(sizeof(*rcpt_st)*i);
      rcpt_st_free = rcpt_st;
      memset(rcpt_st, 0, sizeof(*rcpt_st)*i);
      while (--i > 0) {
            rcpt_st->fwd = rcpt_st+1;
            rcpt_st->user_log_fd = -1;
            ++rcpt_st;
      }
}



void
rcpts_free(CMN_WORK *cwp, u_char need_lock)
{
      RCPT_ST *rcpt_st, *next_rcpt_st;

      rcpt_st = cwp->rcpt_st_first;
      if (!rcpt_st)
            return;

      if (need_lock)
            lock_work();
      cwp->rcpt_st_first = 0;
      do {
            next_rcpt_st = rcpt_st->fwd;
            if (rcpt_st->user_log_fd >= 0) {
                  if (0 > close(rcpt_st->user_log_fd))
                        dcc_error_msg("close(%s): %s",
                                    rcpt_st->user_log_nm,
                                    ERROR_STR());
                  rcpt_st->user_log_fd = -1;
            }
            rcpt_st->fwd = rcpt_st_free;
            rcpt_st_free = rcpt_st;
      } while ((rcpt_st = next_rcpt_st) != 0);
      cwp->num_rcpts = 0;

      if (need_lock)
            unlock_work();
}



RCPT_ST *
rcpt_st_alloc(CMN_WORK *cwp,
         u_char unlocked)           /* 1=unlocked on entry & exit */
{
      RCPT_ST *rcpt_st;

      if (cwp->num_rcpts >= MAX_RCPTS) {
            cmn_error_msg(cwp, "too many recipients");
            return 0;
      }

      if (unlocked)
            lock_work();
      rcpt_st = rcpt_st_free;
      if (rcpt_st) {
            rcpt_st_free = rcpt_st->fwd;
            if (unlocked)
                  unlock_work();
      } else {
            if (unlocked)
                  unlock_work();
            rcpt_st = dcc_malloc(sizeof(*rcpt_st));
            memset(rcpt_st, 0, sizeof(*rcpt_st));
            rcpt_st->user_log_fd = -1;
      }
      rcpt_st->fwd = 0;
      rcpt_st->log_pos_white = 0;
      memset(rcpt_st->wtgts, 0, sizeof(rcpt_st->wtgts));
      rcpt_st->flags = 0;
      rcpt_st->user[0] = '\0';
      rcpt_st->dir[0] = '\0';
      rcpt_st->user_log_nm[0] = '\0';
      rcpt_st->embargo_num = 0;

      /* note position in the main log file so that we can later pick out
       * this recipient's part of the envelope */
      rcpt_st->cwp = cwp;
      if (!cwp->rcpt_st_first) {
            cwp->rcpt_st_first = rcpt_st;
            cwp->log_pos_to_first = log_lseek(cwp, SEEK_END);
            rcpt_st->log_pos_to = cwp->log_pos_to_first;
      } else {
            cwp->rcpt_st_last->fwd = rcpt_st;
            rcpt_st->log_pos_to = cwp->log_pos_to_end;
      }
      cwp->rcpt_st_last = rcpt_st;
      ++cwp->num_rcpts;

      return rcpt_st;
}



void
parse_userdirs(const char *arg)
{
      static DCC_PATH userdirs_path;

      /* add '/' to end of the path without converting it to "/" */
      if (*arg == '\0') {
            userdirs_path[0] = '\0';
            userdirs_len = 0;
      } else {
            strncpy(userdirs_path, arg,
                  sizeof(userdirs_path));
            userdirs_len = strlen(userdirs_path)-1;
            while (userdirs_len > 1 && userdirs_path[userdirs_len] == '/')
                  --userdirs_len;
            userdirs_path[++userdirs_len] = '/';
            userdirs_path[++userdirs_len] = '\0';

      }
      userdirs = userdirs_path;
}



/* sanitize recipient mailbox and per-user log and whitelist directory */
u_char                              /* 0=complain about something */
get_user_dir(RCPT_ST *rcpt_st,
           const char *str1, int str1_len, const char *str2, int str2_len)
{
      char *p;
      char c;
      u_char seen_slash;
      int dots;
      int i;

      if (!userdirs || !dcc_have_logdir) {
            rcpt_st->dir[0] = '\0';
            return 1;
      }

      memcpy(rcpt_st->dir, userdirs, userdirs_len);
      i = userdirs_len;
      if (i+str1_len < ISZ(rcpt_st->dir))
            memcpy(&rcpt_st->dir[i], str1, str1_len);
      i += str1_len;
      if (str2) {
            if (i+str2_len+1 < ISZ(rcpt_st->dir)) {
                  rcpt_st->dir[i++] = '/';
                  memcpy(&rcpt_st->dir[i], str2, str2_len);
            }
            i += str2_len;
      }
      if (i >= ISZ(rcpt_st->dir)) {
            dcc_pemsg(EX_DATAERR, rcpt_st->cwp->emsg,
                    "recipient \"%s\" is too long", rcpt_st->dir);
            rcpt_st->dir[0] = '\0';
            return 0;
      }
      rcpt_st->dir[i] = '\0';

      /* To get a consistent directory name,
       * convert ASCII upper to lower case.
       * Be simplistic about international character sets and
       * avoid locale and portability complications.
       * Refuse insecure paths. */
      seen_slash = 1;               /* userdirs ends with '/' */
      dots = 0;
      p = &rcpt_st->dir[userdirs_len];
      for (;;) {
            c = *p;
            if (c == '/' || c == '\\' || c == '\0') {
                  if (dots == 2) {
                        dcc_pemsg(EX_DATAERR, rcpt_st->cwp->emsg,
                                "path \"%s\" is insecure",
                                rcpt_st->dir);
                        rcpt_st->dir[0] = '\0';
                        return 0;
                  }
                  if (c == '\0')
                        break;
                  seen_slash = 1;
                  dots = 0;
            } else if (c == '.' && seen_slash && dots <= 1) {
                  ++dots;
            } else {
                  *p = DCC_TO_LOWER(c);
                  seen_slash = 0;
                  dots = 0;
            }
            ++p;
      }

      return 1;
}



/* start log file */
u_char                              /* 1=new file, 0=old file or failed */
log_start(CMN_WORK *cwp)
{
      struct tm tm;
      char date_buf[40];

      if (!dcc_have_logdir)
            return 0;
      if (cwp->log_fd >= 0)
            return 0;

#if MAX_LOG_SIZE > 0
      cwp->log_size = 0;
#endif
      cwp->log_fd = dcc_log_open(0, cwp->log_nm, sizeof(cwp->log_nm));
      if (cwp->log_fd < 0)
            return 0;

      strftime(date_buf, sizeof(date_buf), DCC_LOG_DATE_FMT,
             dcc_localtime(time(0), &tm));
      log_print(cwp, DCC_LOG_DATE_PAT"\n", date_buf);
      return 1;
}



static void
log_fd2_close(CMN_WORK *cwp, int flag)
{
      if (cwp->log_fd2 >= 0
          && 0 > close(cwp->log_fd2))
            dcc_error_msg("close(FD2 %s): %s",
                        cwp->log_nm, ERROR_STR());
      cwp->log_fd2 = flag;
}



void
log_stop(CMN_WORK *cwp)
{
      log_fd2_close(cwp, -1);

      if (cwp->log_fd < 0)
            return;

      /* Close before renaming to accomodate WIN32 foolishness.
       * Assuming dcc_mkstemp() works properly, there is no race */
      if (0 > close(cwp->log_fd))
            dcc_error_msg("close(%s): %s", cwp->log_nm, ERROR_STR());
      if (!(cwp->honor & (DCC_HONOR_LOGIT
                      | DCC_HONOR_LOG_ONE
                      | DCC_HONOR_GREY_LOGIT))) {
            /* Delete the log file if it is not interesting */
            unlink(cwp->log_nm);
      } else {
            /* give it a permanent name if it is interesting */
            dcc_log_keep(0, cwp->log_nm, sizeof(cwp->log_nm));
      }
      cwp->log_fd = -1;
}



void
log_write(CMN_WORK *cwp, const void *logbuf, u_int len)
{
      int result;

      if (cwp->log_fd < 0)
            return;

      if (!len)
            len = strlen(logbuf);
#if MAX_LOG_SIZE > 0
      cwp->log_size += len;
#endif

      result = write(cwp->log_fd, logbuf, len);
      if ((int)len == result)
            return;

      if (result < 0)
            dcc_error_msg("write(%s): %s", cwp->log_nm, ERROR_STR());
      else
            dcc_error_msg("write(%s,%d)=%d", cwp->log_nm, len, result);
      close(cwp->log_fd);
      cwp->log_fd = -1;
}



static inline void
log_ck_write(void *cwp, const void *logbuf, u_int len)
{
      log_write((CMN_WORK *)cwp, logbuf, len);
}



/* put something into a log file */
static void
vlog_print(CMN_WORK *cwp, const char *p, va_list args)
{
      char logbuf[MAXHOSTNAMELEN*3];

      if (cwp->log_fd < 0)
            return;

      vsnprintf(logbuf, sizeof(logbuf), p, args);
      log_write(cwp, logbuf, 0);
}



void PATTRIB(2,3)
log_print(CMN_WORK *cwp, const char *p, ...)
{
      va_list args;

      va_start(args, p);
      vlog_print(cwp, p, args);
      va_end(args);
}



off_t
log_lseek(CMN_WORK *cwp, int whence)
{
      off_t result;

      if (cwp->log_fd < 0)
            return 0;
      result = lseek(cwp->log_fd, 0, whence);
      if (result == -1) {
            cmn_error_msg(cwp, "lseek(%s, 0, %d): %s",
                        cwp->log_nm, whence, ERROR_STR());
            close(cwp->log_fd);
            cwp->log_fd = -1;
            return 0;
      }
      return result;
}



void
cmn_error_msg(CMN_WORK *cwp, const char *p, ...)
{
      va_list args;

      va_start(args, p);
      dcc_verror_msg(p, args);
      va_end(args);

      va_start(args, p);
      vlog_print(cwp, p, args);
      va_end(args);

      LOG_CMN_EOL(cwp);
      cwp->honor |= DCC_HONOR_LOGIT;
}



void
cmn_trace_msg(CMN_WORK *cwp, const char *p, ...)
{
      va_list args;

      va_start(args, p);
      dcc_vtrace_msg(p, args);
      va_end(args);

      va_start(args, p);
      vlog_print(cwp, p, args);
      va_end(args);

      LOG_CMN_EOL(cwp);
      cwp->honor |= DCC_HONOR_LOGIT;
}



/* open the connection to the nearest DCC server */
u_char
ck_dcc_ctxt(CMN_WORK *cwp)
{
      if (cwp->dcc_ctxt_sn != dcc_ctxt_sn) {
            cwp->dcc_ctxt_sn = dcc_ctxt_sn;
            cwp->dcc_ctxt = dcc_clnt_init(cwp->emsg, cwp->dcc_ctxt, 0,
                                    DCC_CLNT_FG_NO_SRVR_OK
                                    | DCC_CLNT_FG_NO_FAIL);
            if (!cwp->dcc_ctxt) {
                  /* failed to create context */
                  cmn_error_msg(cwp, "%s", cwp->emsg);
                  cwp->dcc_ctxt_sn = 0;
                  return 0;
            }
            cwp->xhdr_len = dcc_xhdr_start(cwp->xhdr, sizeof(cwp->xhdr));
      }
      return 1;
}



/* find and lock a DCC_WF */
static DCC_WF *
find_wf(RCPT_ST *rcpt_st)
{
      CWF *cwf;
      DCC_PATH white_nm;
      const char *white_nm_ptr;
      struct stat sb;
      int i;

      if (rcpt_st->dir[0] == '\0')
            return 0;

      snprintf(white_nm, sizeof(white_nm), "%s/whiteclnt", rcpt_st->dir);
      if (0 > stat(white_nm, &sb)) {
            if (errno != ENOENT
                && (errno != ENOTDIR || dcc_clnt_debug))
                  cmn_error_msg(rcpt_st->cwp, "stat(%s): %s",
                              white_nm, ERROR_STR());
            return 0;
      }

      i = pthread_mutex_lock(&cwf_mutex);
      if (i)
            dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(cwf_mutex): %s",
                     ERROR_STR1(i));

      cwf = cur_cwf;
      for (;;) {
            dcc_wf_lock(&cwf->wf);
            if (cwf->older == cur_cwf) {
                  /* recycle the oldest DCC_WF */
                  white_nm_ptr = white_nm;
                  break;
            }

            if (!strcmp(white_nm, cwf->wf.ascii_nm)) {
                  /* we found a DCC_WF for the target file */
                  white_nm_ptr = 0;
                  break;
            }

            dcc_wf_unlock(&cwf->wf);
            cwf = cwf->older;
      }

      /* move to front */
      if (cwf != cur_cwf) {
            cwf->older->newer = cwf->newer;
            cwf->newer->older = cwf->older;
            cwf->older = cur_cwf;
            cwf->newer = cur_cwf->newer;
            cwf->newer->older = cwf;
            cwf->older->newer = cwf;
            cur_cwf = cwf;
      }
      i = pthread_mutex_unlock(&cwf_mutex);
      if (i)
            dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(cwf_mutex): %s",
                     ERROR_STR1(i));

      if (!dcc_white_rdy(rcpt_st->cwp->emsg, &cwf->wf, white_nm_ptr,
                     DCC_WHITE_RDY_LOCK_KEEP_BOTH, 0)) {
            dcc_wf_unlock(&cwf->wf);
            cmn_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg);
            return 0;
      }

      /* return with both locks */
      return &cwf->wf;
}



/* check a whitelist for a target address */
static u_char                             /* 1=unambiguous result */
ask_white_to(CMN_WORK *cwp, RCPT_ST *rcpt_st, DCC_WF *wf,
           DCC_SUM sum,       /* check this DCC_CK_ENV_TO checksum */
           const char *str,
           u_char keep_lock)        /* 0=unlock both if unambiguous */
{
      DCC_PATH abs_nm;

      switch (dcc_white_sum(cwp->emsg, wf, DCC_CK_ENV_TO, sum, 1)) {
      case DCC_WHITE_ERROR:
            cmn_error_msg(cwp, "%s", cwp->emsg);
            rcpt_st->flags |= RCPT_ST_WHITE;    /* fail conservatively */
            break;

      case DCC_WHITE_HALF_LISTED:
            /* "OK2" for the RCPT in the local whitelist does not mean
             * the address is half whitelisted, but that it is
             * explicitly ok to reject or discard spam for it. */
            if (cwp->action != CMN_IGNORE)
                  rcpt_st->flags &= ~RCPT_ST_DCC_OFF;
            return 0;

      case DCC_WHITE_LISTED:
            log_print(cwp, "%s(env_To %s)-->OK\n",
                    dcc_fnm2path(abs_nm, wf->ascii_nm), str);
            rcpt_st->flags |= RCPT_ST_WHITE;
            break;

      case DCC_WHITE_UNLISTED:
            return 0;

      case DCC_WHITE_BLACK:
            log_print(cwp, "%s(env_To %s)-->spam\n",
                    dcc_fnm2path(abs_nm, wf->ascii_nm), str);
            rcpt_st->flags |= RCPT_ST_BLACK;
            rcpt_st->flags &= ~RCPT_ST_DCC_OFF;
            cwp->honor |= DCC_HONOR_LOCAL_ISSPAM;
            break;
      }

      /* we have an umabiguous answer */
      if (!keep_lock) {
            if (!dcc_white_unlock(cwp->emsg, wf))
                  cmn_error_msg(cwp, "%s", cwp->emsg);
            dcc_wf_unlock(wf);
      }
      return 1;
}



static DCC_WHITE_RESULT
log_white_msg(CMN_WORK *cwp, DCC_WF *wf, DCC_WHITE_RESULT result)
{
      DCC_PATH abs_nm;

      switch (result) {
      case DCC_WHITE_ERROR:
            cmn_error_msg(cwp, "%s", cwp->emsg);
            log_print(cwp, "%s-->OK\n",
                    dcc_fnm2path(abs_nm, wf->ascii_nm));
            return DCC_WHITE_LISTED;    /* fail conservatively */

      case DCC_WHITE_LISTED:
            log_print(cwp, "%s-->OK\n",
                    dcc_fnm2path(abs_nm, wf->ascii_nm));
            break;

      case DCC_WHITE_HALF_LISTED:
      case DCC_WHITE_UNLISTED:
            break;

      case DCC_WHITE_BLACK:
            log_print(cwp, "%s-->spam\n",
                    dcc_fnm2path(abs_nm, wf->ascii_nm));
            break;
      }
      return result;
}



/* check a whitelist for the checksums of the message */
static inline u_char                /* 1=black or white result */
ck_white_msg(RCPT_ST *rcpt_st, DCC_WHITE_RESULT result)
{
      switch (result) {
      case DCC_WHITE_ERROR:         /* fail conservatively */
      case DCC_WHITE_LISTED:
            rcpt_st->flags |= RCPT_ST_WHITE;
            break;

      case DCC_WHITE_HALF_LISTED:
      case DCC_WHITE_UNLISTED:
            return 0;

      case DCC_WHITE_BLACK:
            rcpt_st->flags |= RCPT_ST_BLACK;
            rcpt_st->cwp->honor |= DCC_HONOR_LOCAL_ISSPAM;
            break;
      }
      return 1;
}



static void
want_grey_on(const DCC_WF *wf)
{
      static u_char once;
      DCC_PATH abs_nm;

      if (once)
            return;
      once = 1;

      if (!grey_on)
            dcc_error_msg("%s wants greylisting but it is turned off",
                        dcc_fnm2path(abs_nm, wf->ascii_nm));
}



/* check the whitelists for one target */
static void
ask_white_rcpt(CMN_WORK *cwp, RCPT_ST *rcpt_st, DCC_WHITE_RESULT global_result)
{
      DCC_SUM addr_sum;
      DCC_WHITE_RESULT private_result;
      DCC_WF *wf;

      rcpt_st->log_pos_white = log_lseek(cwp, SEEK_END);
      rcpt_st->flags |= cwp->rcpt_st_flags & (RCPT_ST_DCC_OFF
                                    | RCPT_ST_GREY_OFF
                                    | RCPT_ST_GREY_LOG_OFF
                                    | RCPT_ST_LOG_ALL
                                    | RCPT_ST_DNSBL_ON);

      wf = find_wf(rcpt_st);
      if (wf) {
            /* per-user whitelist overrides global whitelist */
            if (wf->info_flags & DCC_WHITE_FG_GREY_ON) {
                  rcpt_st->flags &= ~RCPT_ST_GREY_OFF;
                  want_grey_on(wf);
            }
            if (!grey_on || (wf->info_flags & DCC_WHITE_FG_GREY_OFF))
                  rcpt_st->flags |= RCPT_ST_GREY_OFF;

            if (wf->info_flags & DCC_WHITE_FG_GREY_LOG_ON) {
                  rcpt_st->flags &= ~RCPT_ST_GREY_LOG_OFF;
                  want_grey_on(wf);
            } else if (wf->info_flags & DCC_WHITE_FG_GREY_LOG_OFF) {
                  rcpt_st->flags |= RCPT_ST_GREY_LOG_OFF;
            }

            if (wf->info_flags & DCC_WHITE_FG_LOG_ALL) {
                  rcpt_st->flags |= RCPT_ST_LOG_ALL;
            } else if (wf->info_flags & DCC_WHITE_FG_LOG_NORMAL) {
                  rcpt_st->flags &= ~RCPT_ST_LOG_ALL;
            }

            if ((wf->info_flags & DCC_WHITE_FG_DNSBL_ON)
                && dnsbls) {
                  rcpt_st->flags |= RCPT_ST_DNSBL_ON;
            } else if (wf->info_flags & DCC_WHITE_FG_DNSBL_OFF) {
                  rcpt_st->flags &= ~RCPT_ST_DNSBL_ON;
            }
      }

      /* If the MTA has decide the message is or is not spam,
       * then it is regardless of the whitelists */
      if (cwp->honor & (DCC_HONOR_MTA_NOTSPAM | DCC_HONOR_MTA_ISSPAM)) {
            if (cwp->honor & DCC_HONOR_MTA_NOTSPAM)
                  rcpt_st->flags |= RCPT_ST_WHITE;
            else
                  rcpt_st->flags |= RCPT_ST_BLACK;
            if (wf) {
                  if (!dcc_white_unlock(cwp->emsg, wf))
                        cmn_error_msg(cwp, "%s", cwp->emsg);
                  dcc_wf_unlock(wf);
            }
            return;
      }

      dcc_str2ck(rcpt_st->env_to_sum, 0, 0, rcpt_st->env_to);
      if (rcpt_st->user[0])
            dcc_str2ck(addr_sum, 0, 0, rcpt_st->user);

      if (wf) {
            if ((wf->info_flags & DCC_WHITE_FG_DCC_OFF)
                && !(rcpt_st->flags & RCPT_ST_BLACK))
                  rcpt_st->flags |= RCPT_ST_DCC_OFF;
            else if ((wf->info_flags & DCC_WHITE_FG_DCC_ON)
                   && cwp->action != CMN_IGNORE)
                  rcpt_st->flags &= ~RCPT_ST_DCC_OFF;

            /* Check env_to value in the per-user whitelist.
             * If we get a black or white answer, we are finished.
             * We not pay attention to the main global whitelist lest
             * it contradict the per-user whitelist. */
            if (ask_white_to(cwp, rcpt_st, wf, rcpt_st->env_to_sum,
                         rcpt_st->env_to, 0))
                  return;

            /* then check the mailbox name (after aliases etc.) */
            if (rcpt_st->user[0] != '\0'
                && ask_white_to(cwp, rcpt_st, wf, addr_sum,
                            rcpt_st->user, 0))
                  return;
      }

      /* check global whitelist for env_to & mailbox */
      if (ask_white_to(cwp, rcpt_st, &cmn_wf, rcpt_st->env_to_sum,
                   rcpt_st->env_to, 1)) {
            if (wf) {
                  if (!dcc_white_unlock(cwp->emsg, wf))
                        cmn_error_msg(cwp, "%s", cwp->emsg);
                  dcc_wf_unlock(wf);
            }
            return;
      }
      /* then check the mailbox name (after aliases etc.) */
      if (rcpt_st->user[0]
          && ask_white_to(cwp, rcpt_st, &cmn_wf, addr_sum,
                      rcpt_st->user, 1)) {
            if (wf) {
                  if (!dcc_white_unlock(cwp->emsg, wf))
                        cmn_error_msg(cwp, "%s", cwp->emsg);
                  dcc_wf_unlock(wf);
            }
            return;
      }

      /* check the other checksums in the per-user whitelist
       * and unlock it */
      if (wf) {
            private_result = dcc_white_cks(cwp->emsg, wf, &cwp->cks,
                                     rcpt_st->wtgts, 0, 1, 0);
            /* if the per-user whitelist has a black or white result,
             * then we are finished */
            if (ck_white_msg(rcpt_st, private_result)) {
                  log_white_msg(rcpt_st->cwp, wf, private_result);
                  return;
            }
      }

      /* If the per-user whitelist did not have a black or white answer,
       * but the global whitelist had an unambiguous result, then
       * mark the message according to that result */
      if (ck_white_msg(rcpt_st, global_result)) {
            memcpy(rcpt_st->wtgts, cwp->wtgts, sizeof(rcpt_st->wtgts));
            return;
      }

      /* If we had a DNS blacklist hit on a URL and this recipient believes
       * the DNS blacklists, then it is spam */
      if ((rcpt_st->flags & RCPT_ST_DNSBL_ON)
          && cwp->cks.dnsbl
          && cwp->cks.dnsbl->hit != DNSBL_HIT_NONE) {
            rcpt_st->flags |= RCPT_ST_BLACK;
            rcpt_st->cwp->honor |= DCC_HONOR_LOCAL_ISSPAM;
      }
}



/* check the whitelists for all targets */
void
cmn_ask_white(CMN_WORK *cwp, u_char mta_grey_query)
{
      RCPT_ST *rcpt_st;
      DCC_WHITE_RESULT global_result;

      if (dcc_dnsbl_log_print(cwp->cks.dnsbl,
                        (void(*)(void *, const char*, ...))log_print,
                        cwp))
            cwp->honor |= DCC_HONOR_LOGIT;

      cwp->log_pos_white_first = log_lseek(cwp, SEEK_END);

      if (cwp->honor & (DCC_HONOR_MTA_NOTSPAM
                    | DCC_HONOR_MTA_ISSPAM)) {
            /* lock and map the global whiteclnt file so we can check
             * for logging options */
            dcc_white_rdy(cwp->emsg, &cmn_wf, 0,
                        DCC_WHITE_RDY_LOCK_NEED_BOTH, &global_result);
      } else {
            /* Check the main whitelist and the whitelists for the
             * recipients.
             * Use the main whitelist only for recipients whose individual
             * whitelists don't give a black or white answer.
             * Check the main whitelist first (and so even if not
             * necessary) so that problems with it are in all of the logs
             * and to simplify merging the global and per-user whitelist
             * results. */
            global_result = dcc_white_cks(cwp->emsg, &cmn_wf, &cwp->cks,
                                    cwp->wtgts, 0, 0, 1);
      }
      global_result = log_white_msg(cwp, &cmn_wf, global_result);
      if (cwp->action == CMN_IGNORE
          || ((to_white_only
             && !(cmn_wf.info_flags & DCC_WHITE_FG_DCC_ON))
            || (cmn_wf.info_flags & DCC_WHITE_FG_DCC_OFF)))
            cwp->rcpt_st_flags |= RCPT_ST_DCC_OFF;

      if (cmn_wf.info_flags & DCC_WHITE_FG_GREY_ON)
            want_grey_on(&cmn_wf);
      if (!grey_on || (cmn_wf.info_flags & DCC_WHITE_FG_GREY_OFF))
            cwp->rcpt_st_flags |= RCPT_ST_GREY_OFF;

      if (cmn_wf.info_flags & DCC_WHITE_FG_GREY_LOG_OFF)
            cwp->rcpt_st_flags |= RCPT_ST_GREY_LOG_OFF;
      if (cmn_wf.info_flags & DCC_WHITE_FG_GREY_LOG_ON) {
            cwp->rcpt_st_flags &= ~RCPT_ST_GREY_LOG_OFF;
            want_grey_on(&cmn_wf);
      }

      if (cmn_wf.info_flags & DCC_WHITE_FG_LOG_ALL)
            cwp->rcpt_st_flags |= RCPT_ST_LOG_ALL;
      if ((cmn_wf.info_flags & DCC_WHITE_FG_DNSBL_ON)
          && dnsbls)
            cwp->rcpt_st_flags |= RCPT_ST_DNSBL_ON;

      for (rcpt_st = cwp->rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
            /* maybe this recipient is whitelisted or a spam trap */
            ask_white_rcpt(cwp, rcpt_st, global_result);

            /* We need to know if all targets are whitelisted for the DCC
             * before we ask the DCC server.  Mail sent only to whitelisted
             * targets should not be reported to the DCC server.
             * For that we need a count of white-listed targets */
            if (!(rcpt_st->flags & RCPT_ST_BLACK)
                && (rcpt_st->flags & RCPT_ST_WHITE)) {
                  ++cwp->white_tgts;
            }

            if (rcpt_st->flags & RCPT_ST_GREY_OFF) {
                  rcpt_st->grey_result = DCC_GREY_ASK_OFF;
                  continue;
            }

            rcpt_st->grey_result = dcc_ask_grey(cwp->emsg,
                            cwp->dcc_ctxt,
                            ((rcpt_st->flags & RCPT_ST_BLACK)
                             || grey_query_only || mta_grey_query)
                            ? DCC_OP_GREY_QUERY
                            : (rcpt_st->flags & (RCPT_ST_WHITE
                                          | RCPT_ST_GREY_OFF))
                            ? DCC_OP_GREY_WHITE
                            : DCC_OP_GREY_REPORT,
                            rcpt_st->msg_sum,
                            rcpt_st->triple_sum,
                            &cwp->cks,
                            rcpt_st->env_to_sum,
                            &rcpt_st->embargo_num,
                            &cwp->early_grey_tgts,
                            &cwp->late_grey_tgts);

            switch (rcpt_st->grey_result) {
            case DCC_GREY_ASK_OFF:
                  break;
            case DCC_GREY_ASK_FAIL:
                  cmn_error_msg(cwp, "%s", cwp->emsg);
                  /* If we are trying hard, assume the
                   * message would have been embargoed */
                  if (try_extra_hard)
                        cwp->honor |= (DCC_HONOR_GREY_EMBARGO
                                     | DCC_HONOR_GREY_LOGIT);
                  break;
            case DCC_GREY_ASK_EMBARGO:
                  cwp->honor |= (DCC_HONOR_GREY_EMBARGO
                               | DCC_HONOR_GREY_LOGIT);
                  break;
            case DCC_GREY_ASK_EMBARGO_END:
                  cwp->honor |= DCC_HONOR_GREY_LOGIT;
                  rcpt_st->flags |= RCPT_ST_GREY_END;
                  break;
            case DCC_GREY_ASK_PASS:
                  break;
            case DCC_GREY_ASK_WHITE:
                  rcpt_st->flags |= RCPT_ST_GREY_WHITE;
                  break;
            }
      }

      cwp->log_pos_white_last = log_lseek(cwp, SEEK_END);

      if (!dcc_white_unlock(cwp->emsg, &cmn_wf))
            cmn_error_msg(cwp, "%s", cwp->emsg);
      dcc_wf_unlock(&cmn_wf);
}



/* ask a DCC server */
int                           /* <0=big problem, 0=retryable, 1=ok */
cmn_ask_dcc(CMN_WORK *cwp, u_char mta_query)
{
      DCC_TGTS tgts;
      int i;

      /* Talk to the DCC server and make the X-DCC header.
       * If we have blacklist entries for it, then we'll tell the DCC
       * server it is spam and say so in the X-DCC header.
       * Note that a target count of 0 is a query. */
      if (dcc_query_only || mta_query) {
            tgts = 0;
      } else if (cwp->honor & (DCC_HONOR_LOCAL_ISSPAM
                         | DCC_HONOR_MTA_ISSPAM)) {
            tgts = DCC_TGTS_TOO_MANY;
      } else if (cwp->honor & DCC_HONOR_GREY_EMBARGO) {
            tgts = cwp->early_grey_tgts;
      } else {
            tgts = cwp->tgts - cwp->late_grey_tgts;
      }
      i = dcc_ask(cwp->emsg, cwp->dcc_ctxt, try_extra_hard,
                &cwp->header, &cwp->cks, &cwp->honor, tgts);
      if (i <= 0) {
            cmn_error_msg(cwp, "%s", cwp->emsg);
            return i;
      }

      /* if we are talking to a new server,
       * remember to fix the X-DCC headers of the other contexts */
      if (cwp->xhdr_len != cwp->header.start_len
          || strncmp(cwp->header.buf, cwp->xhdr, cwp->xhdr_len)) {
            if (dcc_clnt_debug)
                  cmn_trace_msg(cwp, DCC_XHDR_START
                              "header changed from %s to %.*s",
                              cwp->xhdr,
                              (int)cwp->header.start_len,
                              cwp->header.buf);
            cwp->xhdr_len = dcc_xhdr_start(cwp->xhdr, sizeof(cwp->xhdr));
            cwp->dcc_ctxt_sn = ++dcc_ctxt_sn;
      }

      /* log the header */
      log_write(cwp, cwp->header.buf, cwp->header.used);
      LOG_CMN_EOL(cwp);

      return 1;
}



u_char
user_log_write(RCPT_ST *rcpt_st, const void *buf, u_int len)
{
      DCC_PATH abs_nm;
      int result;

      if (rcpt_st->user_log_fd < 0)
            return 0;

      if (!len)
            len = strlen(buf);
      result = write(rcpt_st->user_log_fd, buf, len);
      if (result == (int)len)
            return 1;

      if (result < 0)
            cmn_error_msg(rcpt_st->cwp, "write(%s): %s",
                        dcc_fnm2path(abs_nm, rcpt_st->user_log_nm),
                        ERROR_STR());
      else
            cmn_error_msg(rcpt_st->cwp,
                        "write(%s)=%d instead of %d",
                        dcc_fnm2path(abs_nm, rcpt_st->user_log_nm),
                        result, (int)len);
      close(rcpt_st->user_log_fd);
      rcpt_st->user_log_fd = -1;
      return 0;
}



static void
user_log_print(RCPT_ST *rcpt_st, const char *p, ...)
{
      char logbuf[MAXHOSTNAMELEN*3];
      va_list args;

      va_start(args, p);
      vsnprintf(logbuf, sizeof(logbuf), p, args);
      va_end(args);
      user_log_write(rcpt_st, logbuf, 0);
}



static void
user_log_ck_write(void *rcpt_st0, const void *buf, u_int len)
{
      user_log_write((RCPT_ST *)rcpt_st0, buf, len);
}



static void
user_log_block(RCPT_ST *rcpt_st,    /* copy from main log file to this */
             off_t start,           /* starting here */
             off_t stop)            /* and ending here */
{
      DCC_PATH abs_nm;
            CMN_WORK *cwp;
      char buf[4096];
      off_t len;
      int result;

      cwp = rcpt_st->cwp;

      if (rcpt_st->user_log_fd < 0
          || cwp->log_fd2 < 0)
            return;

      if (start == -1 || stop == -1) {
            cmn_error_msg(cwp, "bogus user_log_block position");
            return;
      }

      if (-1 == lseek(cwp->log_fd2, start, SEEK_SET)) {
            cmn_error_msg(cwp, "lseek(%s,%d,SEEK_SET): %s",
                        dcc_fnm2path(abs_nm, rcpt_st->user_log_nm),
                        (int)start, ERROR_STR());
            log_fd2_close(cwp, -2);
            return;
      }

      while ((len = stop - start) != 0) {
            if (len > (off_t)sizeof(buf))
                  len = sizeof(buf);
            result = read(cwp->log_fd2, buf, len);
            if (result != len) {
                  if (result < 0)
                        cmn_error_msg(cwp, "user_log_block"
                                " read(%s): %s",
                                dcc_fnm2path(abs_nm, cwp->log_nm),
                                ERROR_STR());
                  else
                        cmn_error_msg(cwp, "user_log_block"
                                " read(%s)=%d instead of %d",
                                dcc_fnm2path(abs_nm, cwp->log_nm),
                                result, (int)len);
                  log_fd2_close(cwp, -2);
                  return;
            }
            if (!user_log_write(rcpt_st, buf, len))
                  return;
            start += len;
      }
}



static void
user_log_msg(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
      DCC_PATH abs_nm;
      DCC_PATH rcpt_logdir;
      int log_mode;
      int i;

      cwp->honor |= DCC_HONOR_LOG_ONE;

      snprintf(rcpt_logdir, sizeof(rcpt_logdir), "%s/log", rcpt_st->dir);
      i = dcc_logdir_ck(cwp->emsg, rcpt_logdir, &log_mode);
      if (i <= 0) {
            if (i < 0)
                  cmn_error_msg(cwp, "%s", cwp->emsg);
            return;
      }

      /* get an independent FD for the main log file that can be
       * repositioned without affecting additional output to the main log. */
      if (cwp->log_fd2 < 0) {
            /* give up if things are already broken */
            if (cwp->log_fd2 != -1)
                  return;
            /* Some systems don't synchronize the meta data among FDs for
             * a file, causing the second FD to appear to be truncated. */
            if (fsync(cwp->log_fd) < 0)
                  cmn_error_msg(cwp, "fsync(%s): %s",
                              dcc_fnm2path(abs_nm, cwp->log_nm),
                              ERROR_STR());
            cwp->log_fd2 = open(cwp->log_nm, O_RDONLY, 0);
            if (cwp->log_fd2 < 0) {
                  cmn_error_msg(cwp, "open(%s): %s",
                              dcc_fnm2path(abs_nm, cwp->log_nm),
                              ERROR_STR());
                  cwp->log_fd2 = -2;
                  return;
            }
      }

      /* create the user's log file */
      rcpt_st->user_log_fd = dcc_mkstemp(cwp->emsg, rcpt_st->user_log_nm,
                                 sizeof(rcpt_st->user_log_nm),
                                 rcpt_logdir, DCC_FIN_LOG_PAT,
                                 0, -1, log_mode & 0644);
      if (rcpt_st->user_log_fd < 0) {
            cmn_error_msg(cwp, "%s", cwp->emsg);
            return;
      }

      /* copy envelope before env_To line */
      user_log_block(rcpt_st,
                   0, cwp->log_pos_to_first);
      user_log_block(rcpt_st,       /* copy this env_To line */
                   rcpt_st->log_pos_to,
                   rcpt_st->fwd
                   ? rcpt_st->fwd->log_pos_to
                   : cwp->log_pos_to_end);
      user_log_block(rcpt_st,       /* copy the body of the message */
                   cwp->log_pos_to_end,
                   cwp->log_pos_white_first);
      user_log_block(rcpt_st,       /* copy whitelist error messages */
                   rcpt_st->log_pos_white,
                   rcpt_st->fwd
                   ? rcpt_st->fwd->log_pos_white
                   : cwp->log_pos_white_last);

      /* log the X-DCC header if it exists */
      USER_LOG_EOL(rcpt_st);
      if (cwp->header.buf[0] != '\0') {
            user_log_write(rcpt_st, cwp->header.buf, cwp->header.used);
            USER_LOG_EOL(rcpt_st);
      }

      /* log the checksums and their counts */
      dcc_print_cks(rcpt_st, &cwp->cks, rcpt_st->wtgts,
                  user_log_ck_write);
      USER_LOG_EOL(rcpt_st);
      dcc_print_grey(rcpt_st, rcpt_st->grey_result, rcpt_st->embargo_num,
                   0, rcpt_st->env_to,
                   rcpt_st->msg_sum, rcpt_st->triple_sum,
                   user_log_ck_write);
}



/* after having checked each user or recipient,
 *    dispose of the message for each */
static void
user_deliver(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
      /* create the per-user log file */
      if (rcpt_st->dir[0] != '\0'
          && ((cwp->honor & DCC_HONOR_LOGIT)
            || ((cwp->honor & DCC_HONOR_GREY_LOGIT)
                && !(rcpt_st->flags & RCPT_ST_GREY_LOG_OFF))
            || (rcpt_st->flags & RCPT_ST_LOG_ALL)))
            user_log_msg(cwp, rcpt_st);

      if ((cwp->honor & DCC_HONOR_GREY_EMBARGO)
          && cwp->deliver_tgts != 0) {
            user_reject(cwp, rcpt_st, USER_REJECT_GREY);
            if (rcpt_st->embargo_num != 0) {
                  user_log_print(rcpt_st, "result:"
                               " temporary greylist embargo #%d\n",
                               rcpt_st->embargo_num);
            } else {
                  user_log_print(rcpt_st, "result:"
                               " temporary greylist embargo\n");
            }
            return;
      }

      if (!(rcpt_st->flags & RCPT_ST_BLACK)) {
            if (rcpt_st->flags & RCPT_ST_GREY_END)
                  USER_LOG_CAPTION(rcpt_st, "result: accept"
                               " after greylist embargo\n");
            else if (rcpt_st->flags & RCPT_ST_GREY_WHITE)
                  USER_LOG_CAPTION(rcpt_st, "result: accept; "
                               " greylist whitelist\n");
            else
                  USER_LOG_CAPTION(rcpt_st, "result: accept\n");
            return;
      }

      if (rcpt_st->flags & RCPT_ST_DCC_OFF) {
            if (to_white_only)
                  USER_LOG_CAPTION(rcpt_st,
                               "result: -W ignore and accept\n");
            else if (cwp->action == CMN_IGNORE)
                  USER_LOG_CAPTION(rcpt_st,
                               "result: -a IGNORE and accept\n");
            else
                  USER_LOG_CAPTION(rcpt_st,
                               "result: DCC-off accept\n");
            return;
      }

      if (cwp->deliver_tgts != 0) {
            user_reject(cwp, rcpt_st, USER_REJECT_RCPT);
            if (cwp->action == CMN_DISCARD) {
                  USER_LOG_CAPTION(rcpt_st, "result: discard\n");
            } else {
                  log_print(cwp, "result: discard forced for %s\n",
                          rcpt_st->env_to);
                  USER_LOG_CAPTION(rcpt_st,
                               "result: discard forced by"
                               " other target's whitelist\n");
            }
      } else {
            if (cwp->action == CMN_DISCARD) {
                  USER_LOG_CAPTION(rcpt_st, "result: discard\n");
            } else {
                  user_reject(cwp, rcpt_st, USER_REJECT_ALL);
                  USER_LOG_CAPTION(rcpt_st, "result: reject\n");
            }
      }
}



void
users_process(CMN_WORK *cwp)
{
      RCPT_ST *rcpt_st;
      u_char need_eol;

      /* log the checksums, DCC server counts and global whitelist values */
      dcc_print_cks(cwp, &cwp->cks, cwp->wtgts, log_ck_write);
      LOG_CMN_EOL(cwp);

      /* mark recipients who won't receive it */
      need_eol = 0;
      for (rcpt_st = cwp->rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
            dcc_print_grey(cwp, rcpt_st->grey_result, rcpt_st->embargo_num,
                         &need_eol,
                         rcpt_st->env_to,
                         rcpt_st->msg_sum,
                         rcpt_st->triple_sum,
                         log_ck_write);

            if (!(rcpt_st->flags & RCPT_ST_WHITE)) {
                  /* Recall whether the DCC server said it is spam. */
                  if (cwp->honor & DCC_HONOR_SRVR_ISSPAM)
                        rcpt_st->flags |= RCPT_ST_BLACK;
                  /* If the MTA said the message is spam,
                   * then it is even if the DCC filtering is off. */
                  if (cwp->honor & (DCC_HONOR_MTA_ISSPAM
                                | DCC_HONOR_LOCAL_ISSPAM)) {
                        rcpt_st->flags |= RCPT_ST_BLACK;
                        rcpt_st->flags &= ~RCPT_ST_DCC_OFF;
                  }
            }

            if ((rcpt_st->flags & RCPT_ST_BLACK)
                && !(rcpt_st->flags & RCPT_ST_DCC_OFF))
                  ++cwp->reject_tgts;
            else
                  ++cwp->deliver_tgts;
      }
      if (need_eol)
            LOG_CMN_EOL(cwp);

      if (cwp->honor & (DCC_HONOR_SRVR_ISSPAM
                    | DCC_HONOR_MTA_ISSPAM
                    | DCC_HONOR_LOCAL_ISSPAM))
            cwp->honor |= DCC_HONOR_LOGIT;

      /* create individual log files and trim target list */
      for (rcpt_st = cwp->rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
            user_deliver(cwp, rcpt_st);

            if (rcpt_st->user_log_fd >= 0) {
                  if (0 > close(rcpt_st->user_log_fd))
                        cmn_error_msg(cwp, "close(user %s): %s",
                                    rcpt_st->user_log_nm,
                                    ERROR_STR());
                  rcpt_st->user_log_fd = -1;
            }
      }

      log_fd2_close(cwp, -2);
}

Generated by  Doxygen 1.6.0   Back to index