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

work.c

/* Distributed Checksum Clearinghouse
 *
 * work on a job in the server
 *
 * 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.137 $Revision$
 */

#include "dccd_defs.h"

static int prev_q_delay_us;
static float q_delays_us;
static int q_ops;

DCCD_STATS dccd_stats;

u_char query_only;                  /* 1=treat reports as queries */

u_char grey_weak_body;              /* 1=ignore bodies for greylisting */
u_char grey_weak_ip;                /* 1=a good triple whitelists addr */

int grey_embargo = DEF_GREY_EMBARGO;      /* seconds to delay new traffic */
int grey_window = DEF_GREY_WINDOW;  /* wait as long as this */
int grey_white = DEF_GREY_WHITE;    /* remember this long */


/* report cache used to detect duplicate or retransmitted reports */
static RIDC *ridc_newest, *ridc_oldest;
static RIDC **ridc_hash;
static int ridc_hash_len;

static void send_resp(const QUEUE *, DCC_HDR *);
static void send_error(const QUEUE *, const char *, ...) PATTRIB(2,3);
static void send_emsg(const QUEUE *, int);
#define SEND_EMSG(q) send_emsg(q,__LINE__)
static void repeat_resp(QUEUE *);


static inline RIDC **
ridc_hash_fnc(DCC_HDR *hdr)
{
      u_int32_t sum;

      /* The client's (ID,RID,HID,PID) should be unique and constant for
       * retransmissions of a single request.  It should
       * make a reasonable hash value.  We cannot trust it entirely, if
       * only because of anonymous clients */
      sum = (hdr->sender
             + hdr->op_nums.h + hdr->op_nums.p + hdr->op_nums.r);

      return &ridc_hash[mhash(sum, ridc_hash_len)];
}



static void
ridc_ref(RIDC *ridc)
{
      ridc->last_used = db_time.tv_sec;
      if (ridc->newer)
            ridc->newer->older = ridc->older;
      else
            return;                 /* it's already newest */
      if (ridc->older)
            ridc->older->newer = ridc->newer;
      else
            ridc_oldest = ridc->newer;
      ridc->older = ridc_newest;
      ridc->newer = 0;
      ridc_newest->newer = ridc;
      ridc_newest = ridc;
}



/* get a free report cache block */
static RIDC *
ridc_get_free(void)
{
      RIDC *ridc;
      time_t stale = db_time.tv_sec - DCC_MAX_DELAY_SEC;

      for (ridc = ridc_oldest; ridc != 0; ridc = ridc->newer) {
            if (ridc->last_used < stale) {
                  /* found one, so recycle it */
                  if (ridc->fwd)
                        ridc->fwd->bak = ridc->bak;
                  if (ridc->bak)
                        ridc->bak->fwd = ridc->fwd;
                  else if (ridc->hash)
                        *ridc->hash = ridc->fwd;
                  ridc->bak = 0;
                  ridc_ref(ridc);
                  return ridc;
            }
      }

      /* there are no free blocks that are old enough to recycle */
      return 0;
}



/* make some or some more RID blocks and (re)build the hash table */
static void
ridc_make(void)
{
      int new_len, old_len, j;
      RIDC *ridc, *ridc2, **ridch, **old_ridc_hash;

      new_len = queue_max;
      ridc = dcc_malloc(new_len*sizeof(*ridc));
      if (!ridc)
            dcc_logbad(EX_OSERR, "malloc(%d RIDC blocks) failed",
                     new_len);
      memset(ridc, 0, new_len*sizeof(*ridc));
      for (j = 0; j < new_len; ++j, ++ridc) {   /* make the new blocks oldest */
            if (!ridc_oldest) {
                  ridc_oldest = ridc_newest = ridc;
            } else {
                  ridc_oldest->older = ridc;
                  ridc->newer = ridc_oldest;
                  ridc_oldest = ridc;
            }
      }

      /* rebuild and expand the hash table */
      old_len = ridc_hash_len;
      ridc_hash_len += new_len;
      old_ridc_hash = ridc_hash;
      ridc_hash = dcc_malloc(ridc_hash_len*sizeof(*ridch));
      if (!ridc_hash)
            dcc_logbad(EX_OSERR, "malloc(%d RIDC hash table) failed",
                     ridc_hash_len);
      memset(ridc_hash, 0, ridc_hash_len*sizeof(*ridch));
      if (old_len != 0) {
            do {
                  for (ridc = old_ridc_hash[--old_len];
                       ridc != 0;
                       ridc = ridc2) {
                        ridch = ridc_hash_fnc(&ridc->hdr);
                        ridc2 = ridc->fwd;
                        ridc->bak = 0;
                        ridc->hash = ridch;
                        if ((ridc->fwd = *ridch) != 0)
                              ridc->fwd->bak = ridc;
                        *ridch = ridc;
                  }
            } while (old_len != 0);
            dcc_free(old_ridc_hash);
      }
}



/* get the report cache block for an operation */
u_char                              /* 0=new operation, 1=retransmission */
ridc_get(QUEUE *q)
{
      RIDC *ridc, **ridch;

      for (;;) {
            if (ridc_hash) {
                  /* look for the existing report cache block */
                  ridch = ridc_hash_fnc(&q->pkt.hdr);
                  for (ridc = *ridch; ridc; ridc = ridc->fwd) {
                        /* Reports are relatively small, so we
                         * can afford to not trust the client's
                         * RID to be unique.  Compare all but the
                         * client's transmission #. 
                         * Also check client's UDP port # because
                         * it should be unchanged regardless of
                         * multi-homing. */
                        if (ridc->clnt_port == q->clnt_su.ipv4.sin_port
                            && !memcmp(&ridc->hdr, &q->pkt.hdr,
                                     sizeof(ridc->hdr)
                                     - sizeof(ridc->hdr.op_nums.t))) {
                              /* found it, so make it newest */
                              ridc_ref(ridc);
                              q->ridc = ridc;
                              return 1;
                        }
                  }

                  /* the block does not already exist, so create it */
                  ridc = ridc_get_free();
                  if (ridc)
                        break;
            }
            /* we are out of report cache blocks, so make more */
            ridc_make();

            /* re-hash because our previous pointer is invalid */
      }

      memcpy(&ridc->hdr, &q->pkt.hdr, sizeof(ridc->hdr));
      ridc->clnt_port = q->clnt_su.ipv4.sin_port;
      ridc->op = DCC_OP_INVALID;
      ridc->len = 0;
      ridc->hash = ridch;
      ridc->fwd = *ridch;
      if (ridc->fwd)
            ridc->fwd->bak = ridc;
      *ridch = ridc;

      q->ridc = ridc;
      return 0;
}



/* recompute the queue delay */
void
cycle_q_delay(void)
{
      if (q_ops == 0) {
            prev_q_delay_us = 0;
      } else {
            prev_q_delay_us = q_delays_us / q_ops;
      }
      q_delays_us = 0.0;
      q_ops = 0;
}


static int                    /* microseconds of delay */
get_q_delay_us(QUEUE *q)
{
      int usecs;

      if (q_ops == 0) {
            usecs = 0;
      } else {
            usecs = q_delays_us / q_ops;
      }
      if (usecs < prev_q_delay_us)
            usecs = prev_q_delay_us;

      usecs += q->delay_us;
      return usecs;
}



/* get a unique timestamp */
static void
get_ts(DCC_TS ts)
{
      static struct timeval prev_time;

      gettimeofday(&db_time, 0);

      /* Try to make the next timestamp unique, but only as long
       * as time itself marches forward.  This must work many times
       * a second, or the resoltion of DCC timestaps.
       * Worse, the increment can exhaust values from future seconds.
       * Forget about it if the problem lasts for more than 5 minutes. */
      if (db_time.tv_sec > prev_time.tv_sec
          || (db_time.tv_sec == prev_time.tv_sec
            && db_time.tv_usec > prev_time.tv_usec)
          || db_time.tv_sec < prev_time.tv_sec-5*60) {
            /* either the current time is good enough or we must
             * give up and use it to make the timestamp */
            prev_time = db_time;

      } else {
            /* fudge the previous timestamp to make it good enough */
            prev_time.tv_usec += DCC_TS_USEC_MULT;
            if (prev_time.tv_usec > 1000*1000) {
                  prev_time.tv_usec -= 1000*1000;
                  ++prev_time.tv_sec;
            }
      }

      dcc_timeval2ts(ts, &prev_time, 0);
}



/* see if a count just passed a multiple of a threshold and so is
 *    worth flooding or summarizing */
static inline u_char
summarize_thold(DCC_CK_TYPES type,
            DCC_TGTS rpt_tgts,      /* targets in this report */
            DCC_TGTS ck_tgts) /* grand total */
{
      DCC_TGTS thold, q1, q2;

      thold = oflod_thold[type];
      if (thold == 0)
            return 1;
      q1 = (ck_tgts - rpt_tgts) / thold;
      q2 = ck_tgts/thold;
      return (q1 < 3 && q1 != q2);
}



/* generate a summary record of checksum counts
 *    db_sts.sumrcd points to the record */
static u_char
summarize_rcd(u_char dly)           /* 1=working on delayed records */
{
      DB_RCD new;
      DB_STATE *rcd_st;
      DCC_TGTS rcd_tgts, ck_tgts, new_tgts, sub_total;
      DCC_CK_TYPES type;
      DB_RCD_CK *cur_ck, *new_ck, *found_ck;
      DB_PTR oldest, prev;
      int cur_num_cks;
      int limit;
      u_char rcd_needed;            /* 1=have created rcd to add */
      u_char ck_needed;       /* 0=junk cksum, 2=needed in new rcd */
      u_char undelay_ok;            /* 1=ok to remove delay bit */
      u_char move_ok;

      /* For each checksum whose flooding was delayed but is now needed,
       * generate a fake record that will be flooded */
      cur_num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
      cur_ck = db_sts.sumrcd.d.r->cks;
      new_tgts = 0;
      undelay_ok = 1;
      move_ok = 1;
      rcd_needed = 0;
      new_ck = new.cks;
      do {
            /* Sum counts of all delayed reports for this checksum */
            type = DB_CK_TYPE(cur_ck);
            if (DB_TEST_NOKEEP(db_nokeep_cks, type))
                  continue;

            if (dly) {
                  switch (db_lookup(dcc_emsg, type, cur_ck->sum,
                                0, MAX_HASH_ENTRIES,
                                &db_sts.hash, &db_sts.rcd2,
                                &found_ck)) {
                  case DB_FOUND_SYSERR:
                  case DB_FOUND_LATER:
                        db_broken(dcc_emsg);
                        return 0;
                  case DB_FOUND_EMPTY:
                  case DB_FOUND_CHAIN:
                  case DB_FOUND_INTRUDER:
                        db_broken("missing hash entry for %s in "L_HPAT,
                                dcc_type2str_err(type, 0, 1),
                                db_sts.rcd2.s.rptr);
                        return 0;
                  case DB_FOUND_IT:
                        break;
                  }
                  rcd_st = &db_sts.rcd2;

            } else {
                  /* skip trudging through the hash table to find the
                   * most recent instance of the checksum if we
                   * are dealing with a new record */
                  found_ck = cur_ck;
                  rcd_st = &db_sts.sumrcd;
            }

            /* spam is handled immediately,
             * but it is harmless to "undelay" it */
            ck_tgts = DB_TGTS_CK(found_ck);
            if (ck_tgts == DCC_TGTS_TOO_MANY)
                  continue;
            if (DB_CK_OBS(found_ck))
                  ck_needed = 0;
            else
                  ck_needed = 1;

            oldest = DB_PTR_MAX;
            sub_total = 0;
            for (limit = 1000; limit >= 0; --limit) {
                  /* stop adding reports at the first summary or
                   * compressed record in the hash chain */
                  if (DB_RCD_SUMRY(rcd_st->d.r)
                      || DB_RCD_ID(rcd_st->d.r) == DCC_ID_COMP)
                        break;

                  /* honor deletions */
                  rcd_tgts = DB_TGTS_RCD(rcd_st->d.r);
                  if (rcd_tgts == 0)
                        break;

                  /* We can only summarize our own delayed reports
                   * to keep loops in the flooding topology from
                   * inflating totals. */
                  if (DB_RCD_DELAY(rcd_st->d.r)
                      && DB_RCD_ID(rcd_st->d.r) == my_srvr_id) {
                        oldest = rcd_st->s.rptr;
                        sub_total = db_sum_ck(sub_total, rcd_tgts);
                        /* if we summarize more than the record we
                         * just added, then we cannot simply
                         * convert that new record */
                        if (db_sts.sumrcd.s.rptr != rcd_st->s.rptr)
                              undelay_ok = 0;
                  }
                  prev = DB_PTR_EX(found_ck->prev);
                  if (prev == DB_PTR_NULL)
                        break;
                  rcd_st = &db_sts.rcd2;
                  found_ck = db_map_rcd_ck(dcc_emsg, rcd_st,
                                     prev, type);
                  if (!found_ck) {
                        db_broken(dcc_emsg);
                        return 0;
                  }
            }
            /* honor deletions and summaries between our record
             * and the start of the hash chain */
            if (sub_total == 0) {
                  move_ok = 0;
                  continue;
            }

            if (ck_needed == 1) {
                  if (dly) {
                        /* We are processing delayed reports.  Flood
                         * only one delayed summary per delay period */
                        if (ck_tgts >= oflod_thold[type]
                            && (flod_mmaps == 0
                              || oldest <= flod_mmaps->delay_pos))
                              ck_needed = 2;
                  } else {
                        /* We are considering a summary based on
                         * a report just received from a client
                         * or by flooding */
                        if (summarize_thold(type, sub_total, ck_tgts))
                              ck_needed = 2;
                  }
            }

            if (new_ck != new.cks) {
                  /* We have already begun a summary record.
                   *  Try to extend it. */
                  if (sub_total == new_tgts) {
                        new_ck->type_fgs = type;
                        memcpy(new_ck->sum, cur_ck->sum,
                               sizeof(new_ck->sum));
                        ++new.fgs_num_cks;
                        ++new_ck;
                        if (ck_needed == 2)
                              rcd_needed = 1;
                        continue;
                  }
                  /* We cannot extend the current summary record.
                   * If we don't really need the checksum, then
                   * forget the checksum */
                  if (ck_needed != 2) {
                        move_ok = 0;
                        continue;
                  }
                  /* We really need to summarize the checksum.
                   * So add the current summary record
                   * to the database and start a new summary */
                  if (rcd_needed
                      && !db_add_rcd(dcc_emsg, &new)) {
                        db_broken(dcc_emsg);
                        return 0;
                  }
                  rcd_needed = 0;
                  undelay_ok = 0;
            }

            /* start a new summary record */
            new.srvr_id_auth = my_srvr_id;
            get_ts(new.ts);
            new_tgts = sub_total;
            DB_TGTS_RCD_SET(&new, new_tgts);
            new.fgs_num_cks = DB_RCD_FG_SUMRY+1;
            new_ck = new.cks;
            new_ck->type_fgs = type;
            memcpy(new_ck->sum, cur_ck->sum,
                   sizeof(new_ck->sum));
            new_ck->prev = DB_PTR_CP(DB_PTR_NULL);
            ++new_ck;
            if (ck_needed == 2)
                  rcd_needed = 1;
      }  while (++cur_ck, --cur_num_cks > 0);

      /* finished if nothing more to summarize */
      if (!rcd_needed)
            return 1;

      /* Add the last summary record */
      if (undelay_ok) {
            /* If possible, instead of adding a new record,
             * change the preceding record to not be delayed
             * That is possible if the preceding record has
             * not yet been passed by the flooding */
            if (db_sts.sumrcd.s.rptr >= oflods_max_pos
                && oflods_max_pos != 0) {
                  db_sts.sumrcd.d.r->fgs_num_cks &= ~DB_RCD_FG_DELAY;
                  db_sts.sumrcd.b->flags |= DB_BUF_FG_MSYNC;
                  return 1;
            }

            /* failing that, try to move the record by making a new copy
             * and deleting the original */
            if (move_ok) {
                  memcpy(&new, db_sts.sumrcd.d.r,
                         (sizeof(new) - sizeof(new.cks)
                        + (DB_NUM_CKS(db_sts.sumrcd.d.r)
                           * sizeof(new.cks[0]))));
                  new.fgs_num_cks &= ~DB_RCD_FG_DELAY;
                  DB_TGTS_RCD_SET(db_sts.sumrcd.d.r, 0);
                  rcd_tgts = DB_TGTS_RCD(&new);
                  cur_num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
                  cur_ck = db_sts.sumrcd.d.r->cks;
                  do {
                        new_tgts = DB_TGTS_CK(cur_ck);
                        if (new_tgts != DCC_TGTS_TOO_MANY) {
                              new_tgts -= rcd_tgts;
                              DB_TGTS_CK_SET(cur_ck, new_tgts);
                        }
                  }  while (++cur_ck, --cur_num_cks > 0);
                  db_sts.sumrcd.b->flags |= DB_BUF_FG_MSYNC;
            }
      }

      if (!db_add_rcd(dcc_emsg, &new)) {
            db_broken(dcc_emsg);
            return 0;
      }
      return 1;
}



/* generate a delayed summary if necessary
 *    The target record is specified by db_sts.sumrcd.  It might be changed */
u_char
summarize_dly(void)
{
      DCC_CK_TYPES type;
      const DB_RCD_CK *cur_ck;
      DB_RCD_CK *found_ck;
      int cur_num_cks;
      DCC_TGTS ck_tgts;

      /* look for a checksum that could be summarized */
      cur_num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
      cur_ck = db_sts.sumrcd.d.r->cks;
      do {
            type = DB_CK_TYPE(cur_ck);
            if (DB_TEST_NOKEEP(db_nokeep_cks, type))
                  continue;

            switch (db_lookup(dcc_emsg, type, cur_ck->sum,
                          0, MAX_HASH_ENTRIES,
                          &db_sts.hash, &db_sts.rcd2,
                          &found_ck)) {
            case DB_FOUND_SYSERR:
            case DB_FOUND_LATER:
                  db_broken(dcc_emsg);
                  return 0;
            case DB_FOUND_EMPTY:
            case DB_FOUND_CHAIN:
            case DB_FOUND_INTRUDER:
                  db_broken("missing hash entry for %s in "L_HPAT,
                          dcc_type2str_err(type, 0, 1),
                          db_sts.rcd2.s.rptr);
                  return 0;
            case DB_FOUND_IT:
                  break;
            }

            /* nothing to do if the checksum has already been summarized */
            if (DB_RCD_SUMRY(db_sts.rcd2.d.r))
                  continue;

            /* spam reports are ignored or not delayed */
            ck_tgts = DB_TGTS_CK(found_ck);
            if (ck_tgts == DCC_TGTS_TOO_MANY)
                  continue;

            /* generate a summary for a bulk checksum */
            if (ck_tgts >= oflod_thold[type]) {
                  if (!summarize_rcd(1)) {
                        db_broken(dcc_emsg);
                        return 0;
                  }
                  return 1;
            }
      }  while (++cur_ck, --cur_num_cks > 0);

      return 1;
}



/* see if a incoming flooded checksum has been passed on recently
 *    db_sts.sumrcd points to the new record */
static u_char
flod_worth(DCC_EMSG emsg, u_char *pflod, DB_RCD_CK *ck, DCC_CK_TYPES type)
{
      DCC_TS past;
      DCC_TGTS thresh;
      int limit;
      DB_PTR prev;

      thresh = 3*oflod_thold[type];
      if (DB_TGTS_CK(ck) < thresh) {
            *pflod = 1;
            return 1;
      }

      dcc_timeval2ts(past, &db_time, -summarize_delay_secs);
      for (limit = 20; limit >= 0; --limit) {
            prev = DB_PTR_EX(ck->prev);
            if (prev == DB_PTR_NULL)
                  break;
            ck = db_map_rcd_ck(emsg, &db_sts.rcd2, prev, type);
            if (!ck)
                  return 0;

            /* Look for a recent report for this checksum that has been
             * or will be flooded.  If we find one, and if the total
             * including it is large enough, we may not need to flood
             * the incoming report.  If the total is too small, we
             * must flood the report. */
            if (DB_TGTS_CK(ck) < thresh) {
                  *pflod = 1;
                  return 1;
            }
            if (!DB_CK_OBS(ck)
                && DCC_TS_NEWER_TS(db_sts.rcd2.d.r->ts, past))
                  return 1;
      }

      /* if we can't find a recent preceding report,
       * arrange to flood this one */
      *pflod = 1;
      return 1;
}



/* Add a record and deal with delaying its flooding.
 *    We will delay flooding it if its totals are not interesting.
 *    db_sts.sumrcd points to the new record on exit */
u_char
add_dly_rcd(DCC_EMSG emsg, DB_RCD *new_rcd)
{
      DB_PTR rcd_pos;
      int num_cks;
      DB_RCD_CK *new_ck;
      DCC_CK_TYPES type;
      DCC_TGTS rpt_tgts, ck_tgts;
      u_char flod, useful, summarize;

      /* put the record in the database */
      rcd_pos = db_add_rcd(emsg, new_rcd);
      if (!rcd_pos) {
            db_broken(dcc_emsg);
            return 0;
      }
      if (!db_map_rcd(emsg, &db_sts.sumrcd, rcd_pos, 0)) {
            db_broken(dcc_emsg);
            return 0;
      }
      new_rcd = db_sts.sumrcd.d.r;

      /* delete requests should not be delayed */
      rpt_tgts = DB_TGTS_RCD_RAW(new_rcd);
      if (rpt_tgts == DCC_TGTS_DEL)
            return 1;

      flod = (my_srvr_id == DB_RCD_ID(new_rcd));
      useful = 0;
      summarize = 0;
      for (num_cks = DB_NUM_CKS(new_rcd), new_ck = new_rcd->cks;
           num_cks > 0;
           ++new_ck, --num_cks) {
            if (DB_CK_OBS(new_ck))
                  continue;

            type = DB_CK_TYPE(new_ck);
            if (DB_TEST_NOKEEP(db_nokeep_cks, type))
                  continue;

            /* Reports of spam of either our own or others' are
             * either immediately flooded or marked obsolete as they
             * are linked in the database */
            ck_tgts = DB_TGTS_CK(new_ck);
            if (ck_tgts == DCC_TGTS_TOO_MANY) {
                  flod = 1;
                  continue;
            }

            /* This report has some potential value */
            useful = 1;

            /* Summarize all of our records for the checksums now
             * if we just passed the threshold for one of them. */
            if (!summarize
                && summarize_thold(type, rpt_tgts, ck_tgts))
                  summarize = 1;

            /* If this is an incoming flooded checksum,
             * then pass it on if it is novel (has a low total)
             * or if we have not passed it on recently. */
            if (!flod
                && !flod_worth(emsg, &flod, new_ck, type))
                  return 0;
      }

      /* Forget reports that are either useless noise or reports of spam.
       * They will be flooded or skipped by the flooder */
      if (!useful)
            return 1;

      if (DB_RCD_ID(new_rcd) == my_srvr_id) {
            /* Delay and sooner or later summarize our own reports */
            new_rcd->fgs_num_cks |= DB_RCD_FG_DELAY;

      } else if (!flod && !grey_on) {
            /* Don't delay reports that other servers that are
             * important enough to flood, because we cannot summarize them.
             * Summarizing other servers' reports would allow
             * loops in the flooding topology to inflate the totals.
             * However, another server's report might be reason
             * for us to flood a summary of our own old reports. */
            for (num_cks = DB_NUM_CKS(new_rcd), new_ck = new_rcd->cks;
                 num_cks > 0;
                 ++new_ck, --num_cks) {
                  new_ck->type_fgs |= DB_CK_FG_OBS;
            }
      }

      /* If this record pushed us past a threshold for at least one
       * checksum, then try to generate a summary of our own,
       * previously delayed reports even if this record was not our own. */
      if (summarize
          && !summarize_rcd(0)) {
            db_broken(dcc_emsg);
            return 0;
      }
      return 1;
}



static u_char
add_del(const DCC_CK *del_ck)
{
      DB_RCD del_rcd;

      memset(&del_rcd, 0, sizeof(del_rcd));
      get_ts(del_rcd.ts);
      DB_TGTS_RCD_SET(&del_rcd, DCC_TGTS_DEL);
      del_rcd.srvr_id_auth = my_srvr_id | DCC_SRVR_ID_AUTH;
      del_rcd.fgs_num_cks = 1;
      del_rcd.cks[0].type_fgs = del_ck->type;
      memcpy(del_rcd.cks[0].sum, del_ck->sum, sizeof(del_rcd.cks[0].sum));
      del_rcd.cks[0].prev = DB_PTR_CP(DB_PTR_NULL);
      if (!db_add_rcd(dcc_emsg, &del_rcd)) {
            db_broken("add delete: %s", dcc_emsg);
            return 0;
      }

      return 1;
}



static const DCC_CK *
start_work(QUEUE *q, int *was_locked)
{
      const DCC_CK *ck, *ck_lim;
      DCC_CK_TYPE prev_type;
      int num_cks;

      num_cks = q->pkt_len - (sizeof(q->pkt.r) - sizeof(q->pkt.r.cks));
      if (num_cks < 0) {
            discard_error(q, "packet length %d too small for %s",
                        q->pkt_len, dcc_req_op2str(&q->pkt.a));
            return 0;
      }
      if (num_cks > ISZ(q->pkt.r.cks)) {
            discard_error(q, "packet length %d too large for %s",
                        q->pkt_len, dcc_req_op2str(&q->pkt.a));
            return 0;
      }
      if (num_cks % sizeof(DCC_CK) != 0) {
            discard_error(q, "odd packet length %d for %s",
                        q->pkt_len, dcc_req_op2str(&q->pkt.a));
            return 0;
      }
      num_cks /= sizeof(DCC_CK);

      /* send previous answer if this is a retransmission */
      if (ridc_get(q)) {
            repeat_resp(q);
            return 0;
      }

      /* check each checksum */
      ck_lim = &q->pkt.r.cks[num_cks];
      prev_type = DCC_CK_INVALID;
      for (ck = q->pkt.r.cks; ck < ck_lim; ++ck) {
            if (ck->len != sizeof(*ck)) {
                  /* relax this someday if necessary */
                  discard_error(q, "unknown checksum length %d", ck->len);
                  return 0;
            }
            /* requiring that the checksums be ordered makes it easy
             * to check for duplicates and for bogus long packets */
            if (prev_type >= ck->type) {
                  discard_error(q, "out of order %s checksum",
                              dcc_type2str_err(ck->type, 0, 1));
                  return 0;
            }
            prev_type = ck->type;
      }

      if ((*was_locked = db_lock(dcc_emsg)) < 0) {
            SEND_EMSG(q);
            return 0;
      }

      return ck_lim;
}



static void
fin_work(const QUEUE *q, DCC_QUERY_RESP *resp)
{
      int delay_us;

      /* send the response */
      resp->hdr.op = DCC_OP_QUERY_RESP;
      send_resp(q, &resp->hdr);

      /* update the average queue delay */
      gettimeofday(&db_time, 0);
      delay_us = ((db_time.tv_sec - q->answer.tv_sec)*DCC_USECS
                + (db_time.tv_usec - q->answer.tv_usec));
      if (delay_us > 0)
            q_delays_us += delay_us;
      ++q_ops;
}



/* use db_sts.hash and db_sts.rcd2 to look up answers
 *    while not touching db_sts.rcd */
static u_char
do_query(QUEUE *q, const DCC_CK *ck_lim, u_char report,
       DCC_QUERY_RESP *resp, DCC_TGTS* max_tgts)
{
      const DB_RCD_CK *new_ck;
      const DCC_CK *ck;
      DB_RCD_CK *found_ck;
      DCC_TGTS tgts, *resp_tgts;
      int num_cks;

      /* To answer the query, look for each checksum
       * except for checksums in a record we just added. */
      if (report) {
            new_ck = db_sts.sumrcd.d.r->cks;
            num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
      } else {
            new_ck = 0;
            num_cks = 0;
      }
      *max_tgts = tgts = 0;
      resp_tgts = &resp->body.tgts[0];
      for (ck = q->pkt.r.cks; ck < ck_lim; ++ck, ++resp_tgts) {
            if (num_cks > 0 && ck->type == DB_CK_TYPE(new_ck)) {
                  /* copy answer from the new record */
                  tgts = DB_TGTS_CK(new_ck);
                  ++new_ck;
                  --num_cks;

            } else if (!DCC_CK_OK_CLNT(ck->type, grey_on)) {
                  /* ignore unknown checksums */
                  tgts = 0;

            } else {
                  switch (db_lookup(dcc_emsg, ck->type, ck->sum,
                                0, MAX_HASH_ENTRIES,
                                &db_sts.hash, &db_sts.rcd2,
                                &found_ck)) {
                  case DB_FOUND_LATER:
                  case DB_FOUND_SYSERR:
                        db_broken(dcc_emsg);
                        SEND_EMSG(q);
                        return 0;
                  case DB_FOUND_IT:
                        tgts = DB_TGTS_CK(found_ck);
                        break;
                  case DB_FOUND_EMPTY:
                  case DB_FOUND_CHAIN:
                  case DB_FOUND_INTRUDER:
                        tgts = 0;
                        break;
                  }
            }

            *resp_tgts = htonl(tgts);
            if (*max_tgts < tgts
                && DCC_CK_IS_BODY(ck->type))
                  *max_tgts = tgts;
      }

      resp->hdr.len = (sizeof(*resp) - sizeof(resp->body.tgts)
                   + ((char *)resp_tgts - (char *)resp->body.tgts));
      return 1;
}



static u_char
do_report(QUEUE *q, const DCC_CK *ck_lim, DCC_QUERY_RESP *resp,
        DCC_TGTS* max_tgts)
{
      DCC_CK_TYPES type;
      const DCC_CK *ck;
      DB_RCD new;
      DB_RCD_CK *new_ck;
      DCC_TGTS tgts;
      char tgts_buf[DCC_XHDR_MAX_TGTS_LEN];

      /* Add the report to the database,
       * and as a side effect, find the data to answer the query.
       * Start by creating the record. */
      get_ts(new.ts);
      new.fgs_num_cks = 0;
      tgts = ntohl(q->pkt.r.tgts);

      if (tgts == 0) {
            discard_error(q, "reported zero target count");
            return 0;
      }

      if (tgts < 10) {
            ;
      } else if (tgts == DCC_TGTS_TOO_MANY) {
            ++dccd_stats.reportmany;
      } else if (tgts > DCC_TGTS_TOO_MANY) {
            discard_error(q, "bogus target count %s",
                        dcc_cnt2str(tgts_buf, sizeof(tgts_buf),
                                tgts, grey_on));
            return 0;
      } else if (tgts > 1000) {
            ++dccd_stats.report1000;
      } else if (tgts > 100) {
            ++dccd_stats.report100;
      } else if (tgts > 10) {
            ++dccd_stats.report10;
      }
      DB_TGTS_RCD_SET(&new, tgts);
      new.srvr_id_auth = my_srvr_id;
      if (q->clnt_id != DCC_ID_ANON)
            new.srvr_id_auth |= DCC_SRVR_ID_AUTH;

      /* Check reported checksums as we copy them to the new record */
      new_ck = new.cks;
      for (ck = q->pkt.r.cks; ck < ck_lim; ++ck) {
            type = ck->type;

            /* ignore unknown checksums */
            if (!DCC_CK_OK_CLNT(type, 0)) {
                  clnt_msg(q, "unknown checksum %s from %s",
                         dcc_type2str_err(type, 0, 1), Q_CIP(q));
                  continue;
            }

            /* don't record uninteresting checksums */
            if (DB_TEST_NOKEEP(db_nokeep_cks, type))
                  continue;

            new_ck->type_fgs = type;
            memcpy(new_ck->sum, ck->sum, sizeof(new_ck->sum));
            new_ck->prev = DB_PTR_CP(DB_PTR_NULL);
            ++new_ck;
            ++new.fgs_num_cks;
      }

      /* if we found no checksums to put into the record,
       * treat the operation as if was a query */
      if (new.fgs_num_cks == 0)
            return do_query(q, ck_lim, 0, resp, max_tgts);

      /* Add the record to the database.
       * That will update the totals for each checksum */
      if (!add_dly_rcd(dcc_emsg, &new)) {
            SEND_EMSG(q);
            return 0;
      }

      /* generate the response from the new record */
      return do_query(q, ck_lim, 1, resp, max_tgts);
}



/* process a single DCC_OP_REPORT or DCC_OP_QUERY */
void
do_work(QUEUE *q)
{
      const DCC_CK *ck_lim;
      DCC_QUERY_RESP resp;
      DCC_TGTS max_tgts;
      int was_locked;
      u_char result;

      ck_lim = start_work(q, &was_locked);
      if (!ck_lim)
            return;

      if (q->pkt.hdr.op == DCC_OP_REPORT) {
            if (!(q->flags & Q_FLG_RPT_OK)) {
                  ++dccd_stats.report_reject;
                  clnt_msg(q, "treat report as query from %s", Q_CIP(q));
                  result = do_query(q, ck_lim, 0, &resp, &max_tgts);
            } else {
                  /* process the report */
                  result = do_report(q, ck_lim, &resp, &max_tgts);
            }
      } else {
            result = do_query(q, ck_lim, 0, &resp, &max_tgts);
      }

      if (!result) {
            /* ensure that the clock ticks so rate limits don't stick */
            gettimeofday(&db_time, 0);
      } else {
            /* notice the size of our report */
            if (max_tgts == DCC_TGTS_OK || max_tgts == DCC_TGTS_OK2) {
                  ++dccd_stats.respwhite;
            } else if (max_tgts == DCC_TGTS_TOO_MANY) {
                  ++dccd_stats.respmany;
            } else if (max_tgts > 1000) {
                  ++dccd_stats.resp1000;
            } else if (max_tgts > 100) {
                  ++dccd_stats.resp100;
            } else if (max_tgts > 10) {
                  ++dccd_stats.resp10;
            }
            fin_work(q, &resp);
      }

      if (!was_locked)
            db_unlock(0);
}



/* return 0 for a new embargo,
 *    embargo count     for an existing embargo,
 *    DCC_TGTS_TOO_MANY   no embargo
 *    DCC_TGTS_OK     a newly expired embargo
 *    DCC_TGTS_INVALID    broken database */
static DCC_TGTS
search_grey(DCC_EMSG emsg,
          const DCC_CK *req_ck3,    /* triple checksum */
          const DCC_CK *req_ckb,    /* body seen with it */
          u_char body_known)
{
      DB_RCD_CK *ck3, *ckb;
      DB_PTR prev3;
      DCC_TS old_ts;
      DCC_TGTS result_tgts;
      int i;

      /* look for the triple checksum */
      switch (db_lookup(dcc_emsg, DCC_CK_GREY_TRIPLE, req_ck3->sum,
                    0, MAX_HASH_ENTRIES,
                    &db_sts.hash, &db_sts.rcd, &ck3)) {
      case DB_FOUND_EMPTY:
      case DB_FOUND_CHAIN:
      case DB_FOUND_INTRUDER:
            return 0;

      case DB_FOUND_IT:
            /* We found the triple checksum.
             * If it is marked ok (MANY) or deleted,
             * then we have our answer */
            result_tgts = DB_TGTS_CK(ck3);
            if (result_tgts == DCC_TGTS_TOO_MANY || result_tgts == 0)
                  return result_tgts;

            /* Otherwise look for a report of the triple with
             * the right body checksum that is old enough. */
            result_tgts = 0;
            dcc_timeval2ts(old_ts, &db_time, -grey_embargo);
            for (;;) {
                  ckb = db_sts.rcd.d.r->cks;
                  for (i = DB_NUM_CKS(db_sts.rcd.d.r);
                       i > 0;
                       --i, ++ckb) {
                        /* try the next report in the database
                         * if it has the wrong body checksum
                         *
                         * If we are weak on bodies,
                         * act as if all reports of the triple checksums
                         * are with the right body checksum. */
                        if (!grey_weak_body && req_ckb) {
                              if (DB_CK_TYPE(ckb) != DCC_CK_BODY)
                                  continue;
                              if (memcmp(req_ckb->sum, ckb->sum,
                                       sizeof(DCC_SUM)))
                                  break;
                        }

                        /* We found the right body checksum in
                         * chain of the triple checksum
                         * or we don't care.
                         *
                         * If the report is old enough, then
                         * the embargo is over. */
                        if (DCC_TS_NEWER_TS(old_ts, db_sts.rcd.d.r->ts))
                              return DCC_TGTS_OK;

                        /* If it is not old enough,
                         * then we know this is not a new embargo for
                         * this body (i.e. the reported target count
                         * will be >0) and we must keep looking for an
                         * old enough report with the body checksum. */
                        ++result_tgts;
                        break;
                  }

                  /* If we know the body checksum is not in the database,
                   * then there is no profit in looking at other reports
                   * of the triple checksum to try to find an old enough
                   * report that is with the right body checksum.
                   * We know this is a new embargo. */
                  if (!body_known)
                        return 0;

                  /* If we reach the end of the chain of the
                   * triple checksum without finding an old
                   * enough report for the right body,
                   * then the embargo is not over. */
                  prev3 = DB_PTR_EX(ck3->prev);
                  if (prev3 == DB_PTR_NULL)
                        return result_tgts;

                  /* examine the timestamp of the preceding report
                   * of the triple */
                  ck3 = db_map_rcd_ck(emsg, &db_sts.rcd,
                                  prev3, DCC_CK_GREY_TRIPLE);
                  if (!ck3)
                        return DCC_TGTS_INVALID;
            }
            break;

      case DB_FOUND_LATER:
      case DB_FOUND_SYSERR:
            db_broken(dcc_emsg);
            return DCC_TGTS_INVALID;
      }
      return DCC_TGTS_INVALID;
}



void
do_grey(QUEUE *q)
{
      DCC_OPS op;
      DB_RCD new;
      const DCC_CK *req, *req_lim;
      const DCC_CK *req_ck_ip, *req_ck_triple, *req_ck_msg, *req_ck_body;
      u_char body_known;
      DB_RCD_CK *new_ck, *found_ck;
      DCC_QUERY_RESP resp;
      DCC_TGTS tgts;
      DCC_TGTS ip_tgts;       /* existing count for DCC_CK_IP */
      DCC_TGTS triple_tgts;         /*   "   count for DCC_CK_GREY_TRIPLE */
      DCC_TGTS msg_tgts;            /*   "   count for DCC_CK_GREY_MSG */
      DCC_TGTS eff_msg_tgts;        /* effective value: 0=reported to DCC */
      DCC_TGTS new_msg_tgts;        /* value after this */
      DCC_TGTS result_tgts;         /* no embargo, ending, whitelist or # */
      int was_locked;

      TMSG3(QUERY, "received %s from %d at %s",
            dcc_req_op2str(&q->pkt.a),
            (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender), Q_CIP(q));
      if (!ck_clnt_id(q))
            return;
      if (q->clnt_id == DCC_ID_ANON) {
            anon_msg("drop anomymous %s request from %s",
                   dcc_req_op2str(&q->pkt.a), Q_CIP(q));
            return;
      }

      /* an embargo of 0 seconds means we should only collect names */
      op = q->pkt.hdr.op;
      if (op == DCC_OP_GREY_REPORT && grey_embargo == 0)
            op = DCC_OP_GREY_WHITE;

      req_lim = start_work(q, &was_locked);
      if (!req_lim)
            return;

      /* Require the checksum of the (source,sender,target) triple
       * the checksum of the (body,sender,target), and the body checksum.
       * Allow other checksums for whitelisting. */
      ip_tgts = 0;
      body_known = grey_weak_body;
      req_ck_ip = 0;
      req_ck_body = 0;
      req_ck_triple = 0;
      req_ck_msg = 0;
      msg_tgts = eff_msg_tgts = 0;
      for (req = q->pkt.r.cks; req < req_lim; ++req) {
            /* Note our main checksums of the greylist triple and
             * the message body.  Search the database for it later */
            if (req->type == DCC_CK_GREY_TRIPLE) {
                  req_ck_triple = req;
                  continue;
            }

            if (!DCC_CK_OK_CLNT(req->type, 1))
                  continue;   /* ignore unknown checksums */
            switch (req->type) {
            case DCC_CK_IP:
                  req_ck_ip = req;
                  break;
            case DCC_CK_BODY:
                  req_ck_body = req;
                  break;
            case DCC_CK_GREY_MSG:
                  req_ck_msg = req;
                  break;
            }
            /* check for whitelisting */
            switch (db_lookup(dcc_emsg, req->type, req->sum,
                          0, MAX_HASH_ENTRIES,
                          &db_sts.hash, &db_sts.rcd, &found_ck)) {
            case DB_FOUND_LATER:
            case DB_FOUND_SYSERR:
                  db_broken(dcc_emsg);
                  SEND_EMSG(q);
                  if (!was_locked)
                        db_unlock(0);
                  return;
            case DB_FOUND_IT:
                  /* ignore deleted checksums */
                  tgts = DB_TGTS_CK(found_ck);
                  if (tgts == 0)
                        continue;

                  /* honor whitelisting */
                  if (tgts == DCC_TGTS_OK2
                      && op != DCC_OP_GREY_WHITE) {
                        op = DCC_OP_GREY_WHITE;
                        ++dccd_stats.respwhite;
                  }

                  switch (req->type) {
                  case DCC_CK_BODY:
                        /* notice if the target body exists at all */
                        body_known = 1;
                        break;
                  case DCC_CK_GREY_MSG:
                        msg_tgts = tgts;
                        if (msg_tgts != DCC_TGTS_TOO_MANY) {
                              /* already reported to DCC */
                              eff_msg_tgts = 1;
                        }
                        break;
                  case DCC_CK_IP:
                        ip_tgts = tgts;
                        break;
                  default:
                        break;
                  }
                  break;
            case DB_FOUND_EMPTY:
            case DB_FOUND_CHAIN:
            case DB_FOUND_INTRUDER:
                  break;
            }
      }
      if (!req_ck_triple) {
            send_error(q, "missing DCC_CK_GREY_TRIPLE checksum for %s",
                     dcc_req_op2str(&q->pkt.a));
            if (!was_locked)
                  db_unlock(0);
            return;
      }
      if (op == DCC_OP_GREY_REPORT && !grey_weak_body) {
            if (!req_ck_body) {
                  send_error(q, "missing body checksum for %s",
                           dcc_req_op2str(&q->pkt.a));
                  if (!was_locked)
                        db_unlock(0);
                  return;
            }
            if (!req_ck_msg) {
                  send_error(q, "missing DCC_CK_GREY_MSG checksum for %s",
                           dcc_req_op2str(&q->pkt.a));
                  if (!was_locked)
                        db_unlock(0);
                  return;
            }
      }

      /* decide if the embargo should end */
      triple_tgts = search_grey(dcc_emsg,
                          req_ck_triple, req_ck_body, body_known);
      if (triple_tgts == DCC_TGTS_INVALID) {
            SEND_EMSG(q);           /* broken database */
            if (!was_locked)
                  db_unlock(0);
            return;
      }
      /* End existing embargo on a newly whitelisted sender so its
       *    messages are logged.
       * Quietly prevent future embargos of whitelisted senders that have
       *    not been greylisted.
       * Honor grey_weak_ip whitelisting even after it is turned off */
      if (triple_tgts == DCC_TGTS_TOO_MANY) {
            result_tgts = DCC_TGTS_TOO_MANY;
      } else if (op == DCC_OP_GREY_WHITE) {
            result_tgts = eff_msg_tgts ? DCC_TGTS_OK : DCC_TGTS_TOO_MANY;
      } else if (ip_tgts == DCC_TGTS_TOO_MANY) {
            result_tgts = DCC_TGTS_TOO_MANY;
      } else {
            result_tgts = triple_tgts;
      }

      if (op == DCC_OP_GREY_QUERY) {
            ++dccd_stats.queries;

      } else if (!(q->flags & Q_FLG_RPT_OK)) {
            ++dccd_stats.report_reject;
            clnt_msg(q, "treat report as query from %s", Q_CIP(q));
            ++dccd_stats.queries;

      } else {
            /* add a report for this message */
            ++dccd_stats.reports;
            new.srvr_id_auth = my_srvr_id | DCC_SRVR_ID_AUTH;
            new_ck = new.cks;
            new.fgs_num_cks = 0;
            if (result_tgts < DCC_TGTS_TOO_MANY) {
                  if (req_ck_body) {
                        new_ck->type_fgs = DCC_CK_BODY;
                        memcpy(new_ck->sum, req_ck_body->sum,
                               sizeof(new_ck->sum));
                        ++new.fgs_num_cks;
                        ++new_ck;
                  }
                  new_msg_tgts = 1;
                  DB_TGTS_RCD_SET(&new, 1);
            } else {
                  /* embargo now ending (DCC_TGTS_TOO_OK)
                   * or no embargo (DCC_TGTS_TOO_MANY) */
                  if (grey_weak_ip && req_ck_ip) {
                        new_ck->type_fgs = DCC_CK_IP;
                        memcpy(new_ck->sum, req_ck_ip->sum,
                               sizeof(new_ck->sum));
                        ++new.fgs_num_cks;
                        ++new_ck;
                  }
                  new_msg_tgts = 0;
                  DB_TGTS_RCD_SET(&new, DCC_TGTS_TOO_MANY);
            }

            /* Include the DCC_CK_GREY_MSG checksum in the database
             * record for a new embargo.
             * The message checksum lets an SMTP server report an
             * embargoed message to the DCC before the embargo is over,
             * but not report it more than once even if more than one
             * SMTP client retransmits the message.
             *
             * If the DCC_CK_GREY_MSG checksum does not exist in the
             * database, then tell the DCC client the message is new
             * and should be reported to the DCC server.  We must put the
             * the DCC_CK_GREY_MSG into the database so we will recognize
             * the message as not new when it is retransmitted.
             *
             * If the DCC_CK_GREY_MSG checksum exists and is not MANY,
             * then we may have a retransmission of the message
             * from another IP address.
             * We need to tell the DCC client to not report to the
             * DCC server. The new value for the DCC_CK_GREY_MSG checksum
             * should be whatever we are using for the triple checksum.
             *
             * If the existing count for the DCC_CK_GREY_MSG checksum is
             * MANY, and the new value for triple checksum is not MANY,
             * then we have a new copy of the message and a new embargo.
             * We have a spammer with multiple senders instead of a
             * legitimate multihomed SMTP client.  We need to tell the
             * DCC client to report to the DCC server.  To remember
             * that we told the DCC client to report to the DCC server,
             * we must first delete the existing MANY report of the
             * DCC_CK_GREY_MSG checksum. */
            if (eff_msg_tgts != new_msg_tgts
                && req_ck_msg) {
                  if (msg_tgts == DCC_TGTS_TOO_MANY
                      && !add_del(req_ck_msg)) {
                        SEND_EMSG(q);
                        if (!was_locked)
                              db_unlock(0);
                        return;
                  }
                  new_ck->type_fgs = DCC_CK_GREY_MSG;
                  memcpy(new_ck->sum, req_ck_msg->sum,
                         sizeof(new_ck->sum));
                  ++new.fgs_num_cks;
                  ++new_ck;
            }

            /* Add the triple checksum if we are not whitelisting
             * the IP address */
            if (!(grey_weak_ip && req_ck_ip)
                || result_tgts != DCC_TGTS_TOO_MANY) {
                  new_ck->type_fgs = DCC_CK_GREY_TRIPLE;
                  memcpy(new_ck->sum, req_ck_triple->sum,
                         sizeof(new_ck->sum));
                  ++new.fgs_num_cks;
            }

            get_ts(new.ts);
            if (!db_add_rcd(dcc_emsg, &new)) {
                  db_broken(dcc_emsg);
                  SEND_EMSG(q);
                  if (!was_locked)
                        db_unlock(0);
                  return;
            }
      }

      /* In the result sent to the DCC client,
       * the triple checksum is preceeded by the message checksum
       * target with a count of 1 if it makes sense to report this
       * target address to a DCC server despite a current or
       * just ended embargo */
      resp.body.tgts[0] = htonl(eff_msg_tgts);

      /* Answer SMTP DATA command greylist operations with the target
       * count of the triple checksum:
       *    DCC_TGTS_OK if the embargo is just now being removed
       *    DCC_TGTS_TOO_MANY if there is no current embargo
       *    DCC_TGTS_OK2 if whitelisted.
       *    embargo # otherwise */
      resp.body.tgts[1] = htonl(result_tgts);
      resp.hdr.len = (sizeof(resp) - sizeof(resp.body.tgts)
                  + 2*sizeof(resp.body.tgts[0]));

      fin_work(q, &resp);
      if (!was_locked)
            db_unlock(0);
}



static u_char                       /* 0=refuse the bad guy, 1=continue */
picky_admn(QUEUE *q, int date)
{
      if (q->clnt_id != my_srvr_id) {
            discard_error(q, "unauthorized or wrong server-ID");
            return 0;
      }

      /* Demand a current timestamp to guard against replay attacks.
       * This requires that administrators have clocks close to servers,
       * and that network and server delays be reasonable */
      date = ntohl(date) - wake_time.tv_sec;
      if (date < -30 || date > 30) {
            send_error(q,
                     "rejected %s request; timestamp off by %d seconds",
                     dcc_req_op2str(&q->pkt.a), date);
            return 0;
      }

      return 1;
}



static u_char                       /* 1=ok 0=error sent to client */
delete_sub(QUEUE *q, DCC_CK *del_ck,
         int *was_locked,
         u_char grey_spam)          /* 1=grey IP address, 2=triple */
{
      DB_RCD_CK *rcd_ck;
      char buf[80];
      DB_PTR prev;
      DCC_TGTS tgts;

      buf[0] = '\0';
      if (grey_spam < 2) {
            *was_locked = db_lock(dcc_emsg);
            if (*was_locked < 0) {
                  SEND_EMSG(q);
                  return 0;
            }
      }
      switch (db_lookup(dcc_emsg, del_ck->type, del_ck->sum,
                    0, MAX_HASH_ENTRIES,
                    &db_sts.hash, &db_sts.rcd, &rcd_ck)) {
      case DB_FOUND_EMPTY:
      case DB_FOUND_CHAIN:
      case DB_FOUND_INTRUDER:
            if (grey_spam) {
                  /* finished if we have not greylisted the spammer */
                  if (grey_spam != 1 && !*was_locked)
                        db_unlock(0);
                  return 1;
            } else {
                  snprintf(buf, sizeof(buf),
                         "%s %s not found to delete",
                         dcc_type2str_err(del_ck->type, 0, 1),
                         dcc_ck2str_err(del_ck->type, del_ck->sum));
            }
            break;

      case DB_FOUND_IT:
            tgts = DB_TGTS_CK(rcd_ck);
            /* handle an ordinary delete request */
            if (!grey_spam) {
                  if (tgts == 0)
                        snprintf(buf, sizeof(buf),
                               "%s %s already deleted",
                               dcc_type2str_err(del_ck->type, 0, 1),
                               dcc_ck2str_err(del_ck->type,
                                          del_ck->sum));
                  break;
            }
            /* Otherwise, we are deleting a greylist checksum.
             * If we are deleting very new greylist records,
             * we can cheat and avoid adding to the database
             * by scribbling over the records.
             * If there is an older record that might have been flooded,
             * we must add a delete request to the database
             * that will itself be flooded. */
            for (;;) {
                  /* finished if the target has already been deleted */
                  if (tgts == 0) {
                        if (grey_spam != 1 && !*was_locked)
                              db_unlock(0);
                        return 1;
                  }
                  if (db_sts.rcd.s.rptr < oflods_max_pos
                      || oflods_max_pos == 0) {
                        /* We need to add a delete request, because
                         * the record might have been flooded */
                        break;
                  }
                  prev = DB_PTR_EX(rcd_ck->prev);
                  /* try to delete the entire greylist entry
                   * starting with the target triple checksum */
                  do {
                        /* only if the embargo is not over */
                        if (DB_TGTS_CK(rcd_ck) >= DCC_TGTS_TOO_MANY)
                              goto need_rcd;
                        DB_TGTS_CK_SET(rcd_ck, 0);
                  } while (--rcd_ck >= db_sts.rcd.d.r->cks);
                  DB_TGTS_RCD_SET(db_sts.rcd.d.r, 0);

                  /* stop after the last record */
                  if (prev == DB_PTR_NULL) {
                        if (grey_spam != 1 && !*was_locked)
                              db_unlock(0);
                        return 1;
                  }
                  rcd_ck = db_map_rcd_ck(dcc_emsg, &db_sts.rcd,
                                     prev, del_ck->type);
                  if (!rcd_ck) {
                        SEND_EMSG(q);
                        if (!*was_locked)
                              db_unlock(0);
                        return 0;
                  }
                  tgts = DB_TGTS_CK(rcd_ck);
            }
need_rcd:;
            break;

      case DB_FOUND_LATER:
      case DB_FOUND_SYSERR:
            db_broken(dcc_emsg);
            if (!*was_locked)
                  db_unlock(0);
            SEND_EMSG(q);
            return 0;
      }

      /* Add the delete request to the database even if the
       * checksum seems deleted or absent so that we will
       * flood the delete request.  This is required to ensure that
       * records get deleted when they are created at one DCC server
       * and deleted at another. */
      if (!add_del(del_ck))
            BUFCPY(buf, dcc_emsg);

      if (buf[0] != '\0') {
            if (!*was_locked)
                  db_unlock(0);
            send_error(q, buf);
            return 0;
      }

      if (grey_spam != 1 && !*was_locked)
            db_unlock(0);

      TMSG3(ADMN, "deleted %s %s for %s",
            dcc_type2str_err(del_ck->type, 0, 1),
            dcc_ck2str_err(del_ck->type, del_ck->sum),
            Q_CIP(q));
      return 1;
}



void
do_delete(QUEUE *q)
{
      int was_locked;

      TMSG3(ADMN, "received %s from client-ID %d at %s",
            dcc_op2str(DCC_OP_DELETE, 0, 0),
            (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q));
      ++dccd_stats.admin;

      if (!ck_clnt_srvr_id(q))
            return;
      if (!picky_admn(q, q->pkt.d.date))
            return;
      /* if we've already answered, then just repeat ourselves */
      if (ridc_get(q)) {
            repeat_resp(q);
            return;
      }

      if (q->pkt_len != sizeof(q->pkt.d)) {
            send_error(q, "wrong packet length %d for %s",
                     q->pkt_len, dcc_op2str(DCC_OP_DELETE, 0, 0));
            return;
      }
      if (q->pkt.d.ck.len != sizeof(q->pkt.d.ck)) {
            /* relax this someday if necessary */
            send_error(q, "unknown checksum length %d", q->pkt.d.ck.len);
            return;
      }
      if (!DCC_CK_OK_DB(q->pkt.d.ck.type)) {
            send_error(q, "unknown checkksum type %d", q->pkt.d.ck.type);
            return;
      }

      if (delete_sub(q, &q->pkt.d.ck, &was_locked, 0)) {
            send_ok(q);

            /* need to clean the database after a deletion
             * to correct the totals of other checksums */
            need_del_dbclean = "checksum deleted";
      }
}



/* restore the embargo against a sender of spam */
void
do_grey_spam(QUEUE *q)
{
      int was_locked;

      TMSG3(QUERY, "received %s from %d at %s",
            dcc_req_op2str(&q->pkt.a),
            (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender), Q_CIP(q));
      if (!ck_clnt_id(q))
            return;
      if (q->clnt_id == DCC_ID_ANON) {
            anon_msg("drop anomymous %s request from %s",
                   dcc_req_op2str(&q->pkt.a), Q_CIP(q));
            return;
      }

      /* require the checksum of the (source,sender,target) triple */
      if (q->pkt_len != sizeof(q->pkt.gs)) {
            send_error(q, "wrong packet length %d for %s",
                     q->pkt_len, dcc_op2str(DCC_OP_GREY_SPAM, 0, 0));
            return;
      }
      if (q->pkt.gs.triple.type != DCC_CK_GREY_TRIPLE) {
            send_error(q, "wrong checksum %s for %s",
                     dcc_type2str_err(q->pkt.gs.triple.type, 0, 1),
                     dcc_op2str(DCC_OP_GREY_SPAM, 0, 0));
            return;
      }
      if (q->pkt.gs.triple.len != sizeof(q->pkt.gs.triple)) {
            send_error(q, "unknown triple checksum length %d",
                     q->pkt.gs.ip.len);
            return;
      }
      if (q->pkt.gs.ip.type != DCC_CK_IP) {
            send_error(q, "wrong checksum %s for %s",
                     dcc_type2str_err(q->pkt.gs.ip.type, 0, 1),
                     dcc_op2str(DCC_OP_GREY_SPAM, 0, 0));
            return;
      }
      if (q->pkt.gs.ip.len != sizeof(q->pkt.gs.ip)) {
            send_error(q, "unknown IP checksum length %d",
                     q->pkt.gs.ip.len);
            return;
      }

      if (!delete_sub(q, &q->pkt.gs.ip, &was_locked, 1))
            return;
      if (!delete_sub(q, &q->pkt.gs.triple, &was_locked, 2))
            return;
      send_ok(q);
}



static void
do_flod(QUEUE *q)
{
      DCC_ADMN_RESP check;
      int print_len;
      u_int32_t val, arg;
      DCC_AOP_FLODS fop;
      FLOD_MMAP *mp;
      OFLOD_INFO *ofp;
      u_char mapped, found_it;

      val = ntohl(q->pkt.a.val1);
      fop = val % 256;
      arg = val / 256;

      if (fop != DCC_AOP_FLOD_LIST
          && !picky_admn(q, q->pkt.a.date))
            return;

      switch (fop) {
      case DCC_AOP_FLOD_CHECK:
            if (host_id_last < db_time.tv_sec - DCC_SRVR_ID_SEC0)
                  host_id_next = 0;
            next_flods_ck = 0;
            if (!check_load_ids(dcc_emsg, my_srvr_id)) {
                  SEND_EMSG(q);
                  return;
            }
            flod_stats_printf(check.val.string, sizeof(check.val.string),
                          (flods_off || flods_st == FLODS_ST_OFF
                           || !DB_IS_LOCKED())
                          ? 0
                          : (flods_st != FLODS_ST_ON) ? 1
                          : 2,
                          oflods.total, oflods.active, iflods.active);
            check.hdr.len = (strlen(check.val.string)
                         + sizeof(check)-sizeof(check.val));
            check.hdr.op = DCC_OP_ADMN;
            send_resp(q, &check.hdr);
            flods_ck(1);
            check_blacklist_file(1);
            return;

      case DCC_AOP_FLOD_SHUTDOWN:
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            ++flods_off;
            iflods_stop("shutdown flooding", 0);
            oflods_stop(0);
            send_ok(q);
            return;

      case DCC_AOP_FLOD_HALT:
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            ++flods_off;
            iflods_stop("stop flooding", 1);
            oflods_stop(1);
            send_ok(q);
            return;

      case DCC_AOP_FLOD_RESUME:
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (!check_load_ids(dcc_emsg, my_srvr_id)) {
                  SEND_EMSG(q);
                  return;
            }
            flods_off -= flod_db_sick+1;
            flod_db_sick = 0;
            flods_restart("resume flooding");
            send_ok(q);
            flods_ck(0);
            return;

      case DCC_AOP_FLOD_REWIND:
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (!flod_mmaps) {
                  send_error(q, "flooding off or %s not available",
                           FLOD_MMAP_PATH(grey_on));
                  return;
            }
            found_it = (arg == DCC_ID_INVALID);
            for (mp = flod_mmaps->mmaps;
                 mp <= LAST(flod_mmaps->mmaps);
                 ++mp) {
                  if (arg == DCC_ID_INVALID
                      || mp->rem_id == arg) {
                        mp->flags |= OFLOD_MMAP_FG_NEED_REWIND;
                        mp->flags &= ~OFLOD_MMAP_FG_FFWD_IN;
                        dcc_trace_msg("rewind flood from server-ID %d",
                                    arg);
                        found_it = 1;
                  }
            }
            if (!found_it) {
                  send_error(q, "unknown server-ID %d for %s",
                           arg, dcc_req_op2str(&q->pkt.a));
            } else {
                  send_ok(q);
                  flods_ck(0);
            }
            return;

      case DCC_AOP_FLOD_LIST:
            print_len = flods_list(check.val.string,
                               sizeof(check.val.string),
                               q->clnt_id == DCC_ID_ANON);
            check.hdr.len = print_len + sizeof(check)-sizeof(check.val);
            check.hdr.op = DCC_OP_ADMN;
            send_resp(q, &check.hdr);
            return;

      case DCC_AOP_FLOD_STATS:
      case DCC_AOP_FLOD_STATS_CLEAR:
            print_len = flod_stats(check.val.string,
                               sizeof(check.val.string),
                               arg,
                               fop == DCC_AOP_FLOD_STATS_CLEAR);
            check.hdr.len = print_len + sizeof(check)-sizeof(check.val);
            check.hdr.op = DCC_OP_ADMN;
            send_resp(q, &check.hdr);
            flods_ck(0);
            return;

      case DCC_AOP_FLOD_FFWD_IN:
      case DCC_AOP_FLOD_FFWD_OUT:
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (flod_mmaps) {
                  mapped = 0;
            } else {
                  oflods_load();
                  mapped = 1;
            }
            ofp = oflods.infos;
            for (;;) {
                  if ((mp = ofp->mp) != 0
                      && mp->rem_id == arg) {
                        /* found the target */
                        if (fop == DCC_AOP_FLOD_FFWD_OUT) {
                              ofp->cur_pos = db_csize;
                              if (ofp->s < 0)
                                  mp->confirm_pos = db_csize;
                              dcc_trace_msg("fast forward flood to"
                                          " server-ID %d",
                                          arg);
                        } else {
                              mp->flags |= OFLOD_MMAP_FG_FFWD_IN;
                              mp->flags &= ~OFLOD_MMAP_FG_NEED_REWIND;
                        }
                        send_ok(q);
                        if (!mapped)
                              flods_ck(0);
                        break;
                  }
                  if (++ofp > LAST(oflods.infos)) {
                        send_error(q, "unknown server-ID %d for %s",
                                 arg, dcc_req_op2str(&q->pkt.a));
                        break;
                  }
            }
            if (mapped)
                  oflods_unmap();
            return;

            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (!flod_mmaps) {
                  send_error(q, "flooding off or %s not available",
                           FLOD_MMAP_PATH(grey_on));
                  return;
            }
            ofp = oflods.infos;
            for (;;) {
                  if ((mp = ofp->mp) != 0
                      && mp->rem_id == arg) {
                        ofp->cur_pos = db_csize;
                        if (ofp->s < 0)
                              mp->confirm_pos = db_csize;
                        dcc_trace_msg("fast forward flood to server-ID"
                                    " %d",
                                    arg);
                        send_ok(q);
                        flods_ck(0);
                        return;
                  }
                  if (++ofp > LAST(oflods.infos)) {
                        send_error(q, "unknown server-ID %d for %s",
                                 arg, dcc_req_op2str(&q->pkt.a));
                        return;
                  }
            }
      }

      send_error(q, "unrecognized %s value %d",
               dcc_req_op2str(&q->pkt.a), fop);
}



void
stats_clear(void)
{
      OFLOD_INFO *ofp;
      time_t secs;

      memset(&dccd_stats, 0, sizeof(dccd_stats));
      for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
            if (ofp->hostname[0] == '\0')
                  continue;

            /* The counts reported to `cdcc stats` are sums
             * of the dccd_stats and ofp->cnts values.  Bias
             * the dccd_stats values by the current ofp->cnts values
             * so the reported counts will be zero.  When the flooding
             * connection is closed, the ofp->cnts values will be added
             * to the dccd_stats values. */
            dccd_stats.iflod_total -= ofp->cnts.total;
            dccd_stats.iflod_accepted -= ofp->cnts.accepted;
            dccd_stats.iflod_stale -= ofp->cnts.stale.val;
            dccd_stats.iflod_dup -= ofp->cnts.dup.val;
            dccd_stats.iflod_ok2 -= ofp->cnts.ok2.val;
            dccd_stats.iflod_not_deleted -= ofp->cnts.not_deleted.val;
      }

      cycle_q_delay();
      secs = dccd_stats.reset.tv_sec - db_time.tv_sec;
      clients_clear(secs >= RL_AVG_DAY && secs <= RL_AVG_DAY + 60*60);

      memset(&db_stats, 0, sizeof(db_stats));
      dccd_stats.reset = db_time;
}



static u_char                       /* 1=sent 0=something wrong */
stats_send(QUEUE *q)
{
      DCC_ADMN_RESP stats;
      char tbuf[80];
      OFLOD_INFO *ofp;
      IFLOD_INFO *ifp;
      int oflods_connecting, iflods_connecting;
      u_int iflod_total, iflod_accepted, iflod_stale;
      u_int iflod_dup, iflod_ok2, iflod_not_deleted;
      char flod_buf[60];
      char reset_buf1[30], reset_buf2[6], clients_reset[40], now_buf[20];
      int clients;
      const char *client_ovf;
      struct tm tm;
      int was_locked;
      int blen, plen, len;

      /* see if the database is locked so we can report that */
      was_locked = db_lock(dcc_emsg);
      if (was_locked < 0) {
            SEND_EMSG(q);
            return 0;
      } else if (!was_locked
               && !db_unlock(dcc_emsg)) {
            SEND_EMSG(q);
            return 0;
      }

      tbuf[0] = '\0';
      if (dccd_tracemask & DCC_TRACE_ADMN_BIT)
            strcat(tbuf, "ADMN ");
      if (dccd_tracemask & DCC_TRACE_ANON_BIT)
            strcat(tbuf, "ANON ");
      if (dccd_tracemask & DCC_TRACE_CLNT_BIT)
            strcat(tbuf, "CLNT ");
      if (dccd_tracemask & DCC_TRACE_RLIM_BIT)
            strcat(tbuf, "RLIM ");
      if (dccd_tracemask & DCC_TRACE_QUERY_BIT)
            strcat(tbuf, "QUERY ");
      if (dccd_tracemask & DCC_TRACE_RIDC_BIT)
            strcat(tbuf, "RIDC ");
      if (dccd_tracemask & DCC_TRACE_FLOD_BIT)
            strcat(tbuf, "FLOOD ");
      if (dccd_tracemask & DCC_TRACE_FLOD2_BIT)
            strcat(tbuf, "FLOOD2 ");
      if (dccd_tracemask & DCC_TRACE_IDS_BIT)
            strcat(tbuf, "IDS ");
      if (dccd_tracemask & DCC_TRACE_BL_BIT)
            strcat(tbuf, "BL ");

      clients = clients_get(0, 0, 0, 0, 0);
      if (clients >= 0) {
            client_ovf = "";
      } else {
            client_ovf = ">";
            clients = -clients;
      }
      if (clients_cleared > db_time.tv_sec-CLIENTS_AGE) {
            strftime(clients_reset, sizeof(clients_reset),
                   "since %X", dcc_localtime(clients_cleared, &tm));
      } else {
            snprintf(clients_reset, sizeof(clients_reset),
                   "in %d hours", CLIENTS_AGE/(60*60));
      }

      oflods_connecting = 0;
      iflod_total = dccd_stats.iflod_total;
      iflod_accepted = dccd_stats.iflod_accepted;
      iflod_stale = dccd_stats.iflod_stale;
      iflod_dup = dccd_stats.iflod_dup;
      iflod_ok2 = dccd_stats.iflod_ok2;
      iflod_not_deleted = dccd_stats.iflod_not_deleted;
      for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
            if (ofp->s >= 0 && !(ofp->flags & OFLOD_FG_CONNECTED))
                  ++oflods_connecting;
            iflod_total += ofp->cnts.total;
            iflod_accepted += ofp->cnts.accepted;
            iflod_stale += ofp->cnts.stale.val;
            iflod_dup += ofp->cnts.dup.val;
            iflod_ok2 += ofp->cnts.ok2.val;
            iflod_not_deleted += ofp->cnts.not_deleted.val;
      }
      iflods_connecting = 0;
      for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
            if (ifp->s >= 0 && !(ifp->flags & IFLOD_FG_VERS_CK))
                  ++iflods_connecting;
      }
      strftime(now_buf, sizeof(now_buf), "%b %d %X",
             dcc_localtime(db_time.tv_sec, &tm));
      strftime(reset_buf1, sizeof(reset_buf1),"%b %d %X",
             dcc_localtime(dccd_stats.reset.tv_sec, &tm));
      strftime(reset_buf2, sizeof(reset_buf2), "%Z", &tm);

      blen = min(sizeof(stats.val.string), ntohl(q->pkt.a.val1));
      plen = snprintf(stats.val.string, blen,
          "    version "DCC_VERSION"  %s%s%stracing %s\n"
          "%7d hash entries %6d used "L_D9PAT" DB bytes\n"
          "%5d ms delay  %d NOPs  %d ADMN  %d query  %s%d clients %s\n",

          was_locked ? "" : "DB UNLOCKED  ",
          query_only ? "Q-mode  " : "",
          grey_on ? "Greylist  " : "",
          tbuf[0] ? tbuf : "nothing",

          HASH_LEN_EXT(db_hash_len), HASH_LEN_EXT(db_hash_used), db_csize,

          get_q_delay_us(q) / 1000,

          dccd_stats.nops, dccd_stats.admin, dccd_stats.queries,
          client_ovf, clients, clients_reset);
      if (plen >= blen)
            plen = blen-1;
      blen -= plen;

      if (grey_on) {
            len = snprintf(&stats.val.string[plen], blen,
          "%7d reports %2d whitelisted\n",

          dccd_stats.reports,
          dccd_stats.respwhite);

      } else {
            len = snprintf(&stats.val.string[plen], blen,
          "%7d reports %7d>10 %7d>100 %7d>1000 %7d many\n"
          "        answers %7d>10 %7d>100 %7d>1000 %7d many\n",

          dccd_stats.reports,
          (dccd_stats.report10 + dccd_stats.report100
           + dccd_stats.report1000 + dccd_stats.reportmany),
          (dccd_stats.report100 + dccd_stats.report1000
           + dccd_stats.reportmany),
          dccd_stats.report1000 + dccd_stats.reportmany,
          dccd_stats.reportmany,

          (dccd_stats.resp10 + dccd_stats.resp100
           + dccd_stats.resp1000 + dccd_stats.respmany),
          dccd_stats.resp100 + dccd_stats.resp1000 + dccd_stats.respmany,
          dccd_stats.resp1000 + dccd_stats.respmany,
          dccd_stats.respmany);
      }
      if (len >= blen)
            len = blen-1;
      blen -= len;
      plen += len;

      len = snprintf(&stats.val.string[plen], blen,
          "%5d bad IDs %4d passwds %3d error responses %5d retransmitted\n",

          dccd_stats.unknown_ids, dccd_stats.bad_passwd,
          dccd_stats.send_errors,  dccd_stats.report_retrans);
      if (len >= blen)
            len = blen-1;
      blen -= len;
      plen += len;

      if (!grey_on) {
            len = snprintf(&stats.val.string[plen], blen,
          "%5d answers rate-limited %3d anonymous"
          "       %5d rejected reports\n",

          dccd_stats.rl, dccd_stats.anon_rl,  dccd_stats.report_reject);
            if (len >= blen)
                  len = blen-1;
            blen -= len;
            plen += len;
      }

      len = snprintf(&stats.val.string[plen], blen,
          "    %s %6d total flooded in\n"
          "%5d accepted %3d stale %5d dup  %5d white    %d delete\n"

          "%5d reports added between %s.%06d %s and %s",

          flod_stats_printf(flod_buf, sizeof(flod_buf),
                        (flods_off || flods_st == FLODS_ST_OFF
                         || !DB_IS_LOCKED())
                        ? 0
                        : (flods_st != FLODS_ST_ON) ? 1
                        : 2,
                        oflods.total,
                        oflods.active - oflods_connecting,
                        iflods.active - iflods_connecting),
          iflod_total,
          iflod_accepted, iflod_stale, iflod_dup,
          iflod_ok2, iflod_not_deleted,

          dccd_stats.adds+db_stats.adds,
          reset_buf1, (int)dccd_stats.reset.tv_usec, reset_buf2,
          now_buf);
      if (len >= blen)
            len = blen-1;
      blen -= len;
      plen += len;

      stats.hdr.len = plen + sizeof(stats)-sizeof(stats.val);
      stats.hdr.op = DCC_OP_ADMN;
      send_resp(q, &stats.hdr);
      return 1;
}



void
do_nop(QUEUE *q)
{
      DCC_OK buf;
      int usecs;

      /* respond immediately to even anonymous NOPs so that clients
       * that are confused about passwords and whether they are anonymous
       * do not retransmit unnecessarily */
      TMSG3(ADMN, "received %s from client-ID %d at %s",
            dcc_req_op2str(&q->pkt.a),
            (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q));
      ++dccd_stats.nops;

      if (!ck_clnt_srvr_id(q)) {
            if (q->rl)
                  ++q->rl->nops;
            free_q(q);
            return;
      }

      ++q->rl->nops;

      if (anon_off && q->clnt_id == DCC_ID_ANON) {
            anon_msg("drop anonymous NOP from %s", Q_CIP(q));
            free_q(q);
            return;
      }

      usecs = get_q_delay_us(q);
      if (usecs >= DCC_MAX_RTT) {
            TMSG1(BL, "drop excessively delayed NOP from %s", Q_CIP(q));
            free_q(q);
            return;
      }

      buf.max_pkt_vers = DCC_PKT_VERSION_MAX;
      usecs /= 1000;
      buf.qdelay_ms = htons(usecs);
      strncpy(buf.brand, brand, sizeof(buf.brand));
      buf.hdr.op = DCC_OP_OK;
      buf.hdr.len = sizeof(buf);
      send_resp(q, &buf.hdr);
      free_q(q);
}



/* deal with an adminstative request */
void
do_admn(QUEUE *q)
{
      u_int32_t val1;
      DCC_ADMN_RESP resp;
      int len, offset, delay;

      val1 = ntohl(q->pkt.a.val1);
      TMSG6(ADMN, "received %s val1=%#x val2=%#x val3=%#x from client-ID"
            " %d at %s",
            dcc_req_op2str(&q->pkt.a), val1, q->pkt.a.val2, q->pkt.a.val3,
            (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q));
      ++dccd_stats.admin;

      if (!ck_clnt_srvr_id(q))
            return;
      if (((q->pkt.a.aop != DCC_AOP_STATS
            && q->pkt.a.aop != DCC_AOP_FLOD
            && q->pkt.a.aop != DCC_AOP_ANON_DELAY
            && q->pkt.a.aop != DCC_AOP_CLIENTS_ID)
           || (anon_off && q->clnt_id == DCC_ID_ANON))
          && !picky_admn(q, q->pkt.a.date))
            return;
      if (q->pkt_len != sizeof(DCC_ADMN_REQ)) {
            send_error(q, "%s size = %d",
                     dcc_req_op2str(&q->pkt.a), q->pkt_len);
            return;
      }

      switch (q->pkt.a.aop) {
      case DCC_AOP_STOP:            /* stop gracefully */
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (!stopint) {
                  stopint = -1;
                  next_flods_ck = 0;
            }
            send_ok(q);
            db_unlock(0);
            db_unload(0);
            return;

      case DCC_AOP_NEW_IDS:         /* load ID's and passwords */
            /* if we've already answered, then just repeat ourselves */
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            /* that the file name cannot be specified over the wire
             * is a security feature not an oversight */
            switch (check_load_ids(dcc_emsg, my_srvr_id)) {
            case 0:
                  SEND_EMSG(q);
                  return;
            case 1:
                  send_ok(q);
                  dcc_trace_msg("reloaded ID's and keys");
                  flods_restart("restart flooding with new IDs");
                  return;
            case 2:
                  send_ok(q);
                  return;
            }
            return;

      case DCC_AOP_FLOD:            /* control flooding */
            do_flod(q);
            return;

      case DCC_AOP_DB_UNLOCK:       /* start switch to new database */
            /* repeat answer to identical question */
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (!flods_off || iflods.active != 0 || oflods.active != 0) {
                  send_error(q, "flooding not stopped before %s",
                           dcc_req_op2str(&q->pkt.a));
                  return;
            }
            /* answer before working so dbclean does not time out */
            send_ok(q);
            dcc_trace_msg("database cleaning begun");
            db_unlock(0);
            db_unload(0);
            next_flods_ck = 0;
            /* don't start our own cleaning */
            del_dbclean_next = db_time.tv_sec + DEL_DBCLEAN_SECS;
            dbclean_limit = db_time.tv_sec + dbclean_limit_secs;
            /* Dbclean expects us to remove its separate hold on flooding
             * so that it will not need to talk to us after telling us
             * to close the old database.
             * On some systems with lame mmap() support including BSD/OS,
             * the daemon can stall for minutes in close(). */
            --flods_off;
            return;

      case DCC_AOP_DB_NEW:          /* finish switch to new database */
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (DB_IS_LOCKED()) {
                  send_error(q, "%s received before %s",
                           dcc_req_op2str(&q->pkt.a),
                           dcc_op2str(DCC_OP_ADMN, DCC_AOP_DB_NEW, 0));
                  return;
            }
            /* send "ok" now because we may stall waiting to reopen */
            send_ok(q);
            db_close(0, 1);
            dccd_stats.adds += db_stats.adds;
            if (!dccd_db_open(DB_OPEN_LOCK_WAIT))
                  dcc_logbad(dcc_ex_code,
                           "could not restart database %s: %s",
                           DCC_NM2PATH(db_nm), dcc_emsg);
            dcc_trace_msg(DCC_VERSION" database %s reopened with %s",
                        DCC_NM2PATH(db_nm), db_window_size);
            flods_off -= flod_db_sick;
            flod_db_sick = 0;
            flods_restart("database reopened");
            next_flods_ck = 0;      /* possibly reap dbclean child */
            return;

      case DCC_AOP_STATS:           /* return counters */
            /* we cannot just repeat ourselves for retransmissions,
             * because the answer is too big to save */
            stats_send(q);
            return;

      case DCC_AOP_STATS_CLEAR:     /* return and then zero counters */
            /* we cannot just repeat ourselves for retransmissions,
             * because the answer is too big to save */
            if (stats_send(q))
                  stats_clear();
            return;

      case DCC_AOP_TRACE_ON:
      case DCC_AOP_TRACE_OFF:
            /* non-redundant message */
            if (!(DCC_TRACE_ADMN_BIT & dccd_tracemask))
                  dcc_trace_msg("received %s %#x from client-ID %d at %s",
                              dcc_req_op2str(&q->pkt.a), val1,
                              (DCC_CLNT_ID)htonl(q->pkt.hdr.sender),
                              Q_CIP(q));
            if ((val1 & ~DCC_TRACE_OFF_BITS) != 0 || val1 == 0) {
                  send_error(q, "invalid trace bits %#x", val1);
                  return;
            }
            if (q->pkt.a.aop == DCC_AOP_TRACE_OFF)
                  dccd_tracemask &= ~val1;
            else
                  dccd_tracemask |= val1;
            send_ok(q);
            return;

      case DCC_AOP_CLIENTS:
            /* we cannot just repeat ourselves for retransmissions,
             * because the answer is too big to save */
            offset = val1 >> 16;
            val1 &= 0xffff;
            len = q->pkt.a.val2 * sizeof(DCC_ADMN_RESP_CLIENTS);
            if (len > ISZ(resp.val))
                  len = ISZ(resp.val);
            clients_get(&resp.val, &len, offset, val1, q->pkt.a.val3);
            resp.hdr.len = len + sizeof(resp)-sizeof(resp.val);
            resp.hdr.op = DCC_OP_ADMN;
            send_resp(q, &resp.hdr);
            return;

      case DCC_AOP_CLIENTS_ID:
            /* we cannot just repeat ourselves for retransmissions,
             * because the answer is too big to save */
            offset = val1 >> 16;
            val1 &= 0xffff;
            len = q->pkt.a.val2 * sizeof(DCC_ADMN_RESP_CLIENTS);
            if (len > ISZ(resp.val))
                  len = ISZ(resp.val);
            clients_get_id(&resp.val, &len, offset, val1, q->pkt.a.val3);
            resp.hdr.len = len + sizeof(resp)-sizeof(resp.val);
            resp.hdr.op = DCC_OP_ADMN;
            send_resp(q, &resp.hdr);
            return;

      case DCC_AOP_ANON_DELAY:
            /* repeat answer to identical question */
            if (ridc_get(q)) {
                  repeat_resp(q);
                  return;
            }
            if (anon_off)
                  delay = DCC_ANON_DELAY_FOREVER;
            else
                  delay = anon_delay_us/1000;
            resp.val.anon_delay.delay[0] = delay>>8;
            resp.val.anon_delay.delay[1] = delay;
            if (anon_delay_inflate== (u_int)-1) {
                  resp.val.anon_delay.inflate[0] = 0;
                  resp.val.anon_delay.inflate[1] = 0;
                  resp.val.anon_delay.inflate[2] = 0;
                  resp.val.anon_delay.inflate[3] = 0;
            } else {
                  resp.val.anon_delay.inflate[0] = anon_delay_inflate>>24;
                  resp.val.anon_delay.inflate[1] = anon_delay_inflate>>16;
                  resp.val.anon_delay.inflate[2] = anon_delay_inflate>>8;
                  resp.val.anon_delay.inflate[3] = anon_delay_inflate;
            }
            delay = (q->pkt.a.val2<<8) + q->pkt.a.val3;
            if (delay != DCC_NO_ANON_DELAY
                && q->clnt_id == my_srvr_id) {
                  if (delay == DCC_ANON_DELAY_FOREVER) {
                        anon_off = 1;
                  } else {
                        anon_off = 0;
                        if (delay > DCC_ANON_DELAY_MAX/1000)
                              delay = DCC_ANON_DELAY_MAX/1000;
                        anon_delay_us = delay*1000;
                        if (val1 == 0)
                              val1 = DCC_ANON_INFLATE_OFF;
                        anon_delay_inflate = val1;
                  }
            }
            resp.hdr.len = (sizeof(resp)-sizeof(resp.val)
                        +sizeof(resp.val.anon_delay));
            resp.hdr.op = DCC_OP_ADMN;
            send_resp(q, &resp.hdr);
            return;
      }

      send_error(q, "invalid %s", dcc_req_op2str(&q->pkt.a));
}



static void
send_resp(const QUEUE *q, DCC_HDR *hdr)
{
      u_int save_len;
      int len, i;

      len = hdr->len;
      hdr->len = htons(hdr->len);
      /* callers must have dealt with the variations due to versions */
      if (q->pkt.hdr.pkt_vers < DCC_PKT_VERSION_MIN)
            hdr->pkt_vers = DCC_PKT_VERSION_MIN;
      else if (q->pkt.hdr.pkt_vers > DCC_PKT_VERSION_MAX)
            hdr->pkt_vers = DCC_PKT_VERSION_MAX;
      else
            hdr->pkt_vers = q->pkt.hdr.pkt_vers;
      hdr->sender = htonl(my_srvr_id);
      hdr->op_nums = q->pkt.hdr.op_nums;
      if (q->passwd[0] == '\0')
            dcc_sign((char *)&q->pkt.hdr.op_nums,
                   sizeof(q->pkt.hdr.op_nums),
                   hdr, len);
      else
            dcc_sign(q->passwd, sizeof(q->passwd),
                   hdr, len);

      if (q->ridc) {
            save_len = len-sizeof(*hdr)-sizeof(DCC_SIGNATURE);
            if (save_len > ISZ(q->ridc->result)) {
                  if (hdr->op == DCC_OP_ERROR)
                        save_len = sizeof(q->ridc->result);
                  else
                        dcc_logbad(EX_SOFTWARE, "RIDC buffer overflow");
            }
            q->ridc->len = save_len;
            memcpy(&q->ridc->result, hdr+1, save_len);
            q->ridc->op = hdr->op;
      }

      i = sendto(q->sp->udp, hdr, len, 0,
               &q->clnt_su.sa, DCC_SU_LEN(&q->clnt_su));
      if (i < 0) {
            clnt_msg(q, "sendto(%s, client %d at %s: %s",
                   dcc_hdr_op2str(hdr), q->clnt_id, Q_CIP(q),
                   ERROR_STR());
      } else if (len != i) {
            clnt_msg(q, "sendto(%s, client %d at %s)=%d instead of %d",
                   dcc_hdr_op2str(hdr), q->clnt_id, Q_CIP(q), i, len);
      } else {
            TMSG4(ADMN, "sent %s to client-ID %d at %s for %s",
                  dcc_hdr_op2str(hdr),
                  (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q),
                  dcc_req_op2str(&q->pkt.a));
      }

}



/* do not send an error response to a client */
void
discard_error(const QUEUE *q, const char *p, ...)
{
      char msg[DCC_ERROR_MSG_LEN];
      va_list args;

      /* log the message */
      va_start(args, p);
      vsnprintf(msg, sizeof(msg), p, args);
      va_end(args);
      clnt_msg(q, "\"%s\" not sent to client %d at %s",
             msg, q->clnt_id, Q_CIP(q));

      if (q->ridc)
            q->ridc->op = DCC_OP_INVALID;

      ++dccd_stats.send_errors;
}



/* send an error response to a client */
static void
send_error(const QUEUE *q, const char *p, ...)
{
      DCC_ERROR buf;
      int slen;
      va_list args;


      /* build and log the message */
      va_start(args, p);
      slen = vsnprintf(buf.msg, sizeof(buf.msg), p, args);
      if (slen > ISZ(buf.msg)-1)
            slen = ISZ(buf.msg)-1;
      va_end(args);
      clnt_msg(q, "\"%s\" sent to client %d at %s",
             buf.msg, q->clnt_id, Q_CIP(q));

      /* send it */
      buf.hdr.len = sizeof(buf)-sizeof(buf.msg)+slen+1;
      buf.hdr.op = DCC_OP_ERROR;
      send_resp(q, &buf.hdr);

      ++dccd_stats.send_errors;
}



static void
send_emsg(const QUEUE *q, int linenum)
{
      if (dcc_emsg[0] == '\0') {
            dcc_error_msg("error near line %d", linenum);
            discard_error(q, "error near line %d", linenum);
      } else {
            dcc_error_msg("%s", dcc_emsg);
            discard_error(q, dcc_emsg);
      }
}



/* say an administrative request was ok */
void
send_ok(QUEUE *q)
{
      DCC_OK buf;
      int delay_ms;

      buf.unused = 0;
      buf.max_pkt_vers = DCC_PKT_VERSION_MAX;
      delay_ms = get_q_delay_us(q) / 1000;
      buf.qdelay_ms = htons(delay_ms);
      strncpy(buf.brand, brand, sizeof(buf.brand));
      buf.hdr.op = DCC_OP_OK;
      buf.hdr.len = sizeof(buf);
      send_resp(q, &buf.hdr);
}



static void
repeat_resp(QUEUE *q)
{
      struct {
          DCC_HDR hdr;
          u_char  b[sizeof(q->ridc->result)];
      } buf;

      ++dccd_stats.report_retrans;

      if (q->ridc->op == DCC_OP_INVALID) {
            TMSG2(RIDC, "repeat previous error silence for %s to %s",
                  dcc_req_op2str(&q->pkt.a),
                  Q_CIP(q));
            return;
      }

      TMSG3(RIDC, "repeat previous answer of %s for %s to %s",
            dcc_op2str(q->ridc->op, 0, 0),
            dcc_req_op2str(&q->pkt.a),
            Q_CIP(q));

      buf.hdr.len = (q->ridc->len + sizeof(buf.hdr) + sizeof(DCC_SIGNATURE));
      buf.hdr.op = q->ridc->op;
      memcpy(&buf.hdr+1, &q->ridc->result, q->ridc->len);
      send_resp(q, &buf.hdr);
}

Generated by  Doxygen 1.6.0   Back to index