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

cdcc.c

/* Distributed Checksum Clearinghouse
 *
 * control dcc 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.134 $Revision$
 */

#include "dcc_ck.h"
#include "dcc_xhdr.h"
#include "dcc_heap_debug.h"
#include "dcc_ids.h"
#ifndef DCC_WIN32
#include <arpa/inet.h>
#endif

static DCC_EMSG dcc_emsg;

static DCC_CLNT_CTXT *ctxt;

static DCC_PATH info_map_nm = DCC_MAP_NM_DEF;
static const char *homedir;
static DCC_PASSWD passwd;
static u_char passwd_set;
static DCC_SRVR_NM srvr = DCC_SRVR_NM_DEF;
static u_char port_set;
static ID_TBL *srvr_clnt_tbl;
static DCC_CLNT_ID srvr_clnt_id;
static enum WHICH_MAP {MAP_TMP, MAP_INFO} which_map = MAP_INFO;

#if defined(NO_IPV6)
static u_char clnt_flags = 0;
#else
static u_char clnt_flags = DCC_CLNT_INFO_FG_IPV6;
#endif

static u_char grey_set;

static u_char quiet;


static u_char do_cmds(char *);
static u_char init_map(u_char);

struct cmd_tbl_entry;
/* -1=display help message, 0=command failed, 1=success */
typedef int CMD (const char *, const struct cmd_tbl_entry *);
typedef struct cmd_tbl_entry {
    const char    *cmd;
    CMD           (*fnc);
    u_char  args;             /* 0=optional, 1=required, 2=none */
    u_char  privileged;       /* 1=must have server's password */
    u_char  uid_0;                  /* 1=require set-UID privileges */
    const char    *help_str;
} CMD_TBL_ENTRY;

static CMD help_cmd;
static CMD exit_cmd;
static CMD grey_cmd;
static CMD file_cmd;
static CMD new_map_cmd;
static CMD delete_cmd;
static CMD add_cmd;
static CMD load_cmd;
static CMD host_cmd;
static CMD port_cmd;
static CMD passwd_cmd;
static CMD id_cmd;
static CMD homedir_cmd;
static CMD debug_cmd;
static CMD ipv6_cmd;
static CMD socks_cmd;
static CMD info_cmd;
static CMD rtt_cmd;
static CMD delck_cmd;
static CMD sleep_cmd;
static CMD clients_cmd;
static CMD anon_cmd;
static CMD flod_rewind;
static CMD ffwd_in;
static CMD ffwd_out;
static CMD flod_stats;
static CMD stats_cmd;
static CMD trace_def;

static const CMD_TBL_ENTRY cmd_tbl[] = {
    {"help",          help_cmd,     0, 0, 0, "help [cmd]"},
    {"?",       help_cmd,     0, 0, 0, 0},
    {"exit",          exit_cmd,     2, 0, 0, "exit"},
    {"quit",          exit_cmd,     2, 0, 0, 0},
    {"grey",          grey_cmd,     0, 0, 0, "grey [on|off]"},
    {"homedir",       homedir_cmd,  0, 0, 0, "homedir [path]"},
    {"file",          file_cmd,     0, 0, 0, "file [map]"},
    {"new map",       new_map_cmd,  0, 0, 0, "new map [map]"},
    {"delete",        delete_cmd,   1, 0, 1, "delete host[,port]"},
    {"add",     add_cmd,      1, 0, 1,
          "add host,[port|-] [RTT+/-#] [ID [passwd]]"},
    {"load",          load_cmd,     1, 0, 1, "load {info-file | -}"},
    {"host",          host_cmd,     0, 0, 0, "host [hostname]"},
    {"server",        host_cmd,       0, 0, 0, 0},
    {"port",          port_cmd,     0, 0, 0, "port #"},
    {"passwd",        passwd_cmd,   0, 0, 0, "passwd secret"},
    {"password",    passwd_cmd,   0, 0, 0, 0},
    {"id",      id_cmd,   0, 0, 0, "id [ID]"},
    {"debug",         debug_cmd,    0, 0, 0, "debug [on|off|TTL=x]"},
    {"IPv6",          ipv6_cmd,       0, 0, 0, "IPv6 [on|off]"},
    {"SOCKS",         socks_cmd,      0, 0, 0, "SOCKS [on|off]"},
    {"info",          info_cmd,     0, 0, 0, "info [-N]"},
    {"RTT",     rtt_cmd,      0, 0, 0, "RTT [-N]"},
    {"delck",         delck_cmd,    1, 1, 0, "delck type hex1..4"},
    {"sleep",         sleep_cmd,      1, 0, 0, "sleep sec.onds"},
    {"clients",       clients_cmd,  0, 0, 0, "clients [-ns] [max [thold]]"},
    {"anon delay",  anon_cmd,   0, 0, 0, "anon delay [delay[,inflate]]"},
    {"flood rewind",flod_rewind,  1, 1, 0, "flood rewind ID"},
    {"flood FFWD in",ffwd_in,   1, 1, 0, "flood FFWD in ID"},
    {"flood FFWD out",ffwd_out,       1, 1, 0, "flood FFWD out ID"},
    {"flood stats", flod_stats,       1, 1, 0, "flood stats [clear] {ID|all}"},
    {"stats",         stats_cmd,      0, 0, 0, "stats [clear|all]"},
    {"status",        stats_cmd,      0, 0, 0, 0},
    {"trace default",trace_def,       2, 1, 0, "trace default"},
};


#define PRV_MSG ";\n"         \
"   use the \"id server-ID\" command\n"   \
"   and either \"passwd secret\" or `su` to read passwords from %s"


static DCC_ADMN_RESP_VAL op_resp;
static struct timeval op_start, op_end;
static DCC_SOCKU op_resp_su;

static struct {
    const char    *op;
    const char    *help_str;
    DCC_AOPS      aop;
    u_char  privileged;
    u_int32_t     val;
} aop_tbl[] = {
    {"stop",            0,  DCC_AOP_STOP, 1, 0},
    {"new IDs",         "", DCC_AOP_NEW_IDS,    1, 0},
    {"reload IDs",      0,  DCC_AOP_NEW_IDS,    1, 0},
    {"flood check",     0,  DCC_AOP_FLOD, 1, DCC_AOP_FLOD_CHECK},
    {"flood shutdown",  0,  DCC_AOP_FLOD, 1, DCC_AOP_FLOD_SHUTDOWN},
    {"flood halt",      0,  DCC_AOP_FLOD, 1, DCC_AOP_FLOD_HALT},
    {"flood resume",    0,  DCC_AOP_FLOD, 1, DCC_AOP_FLOD_RESUME},
    {"flood list",      0,  DCC_AOP_FLOD, 0, DCC_AOP_FLOD_LIST},
    {"DB unlock", 0,  DCC_AOP_DB_UNLOCK,  1, 0},
    {"DB new",          0,  DCC_AOP_DB_NEW,     1, 0},

#define TMAC(s,b) \
    {"trace "#s" on",   "trace "#s" {on|off}",              \
                      DCC_AOP_TRACE_ON, 1, DCC_TRACE_##b},\
    {"trace "#s" off",  "", DCC_AOP_TRACE_OFF,  1, DCC_TRACE_##b}
    TMAC(admn,ADMN_BIT),
    TMAC(anon,ANON_BIT),
    TMAC(clnt,CLNT_BIT),
    TMAC(rlim,RLIM_BIT),
    TMAC(query,QUERY_BIT),
    TMAC(ridc,RIDC_BIT),
    TMAC(flood,FLOD_BIT),
    TMAC(flood2,FLOD2_BIT),
    TMAC(ids,IDS_BIT),
    TMAC(bl,BL_BIT),
    {"trace all on",    "trace all {on|off}",
                      DCC_AOP_TRACE_ON,   1, DCC_TRACE_ON_BITS},
    {"trace all off",   "", DCC_AOP_TRACE_OFF,  1, DCC_TRACE_OFF_BITS},
#undef TMAC
};


static void NRATTRIB
usage(void)
{
      dcc_logbad(EX_USAGE,
               "usage: [-Vdq] [-h homedir] [-c ids] [op1 [op2] ... ]\n");
}



int NRATTRIB
main(int argc, char **argv)
{
      char cmd_buf[500];
      int i;

      srvr.port = htons(DCC_SRVR_PORT);
      srvr_clnt_id = srvr.clnt_id;

      dcc_init_priv();
      dcc_syslog_init(0, argv[0], 0);

      while ((i = getopt(argc, argv, "Vdqh:c:")) != EOF) {
            switch (i) {
            case 'V':
                  fprintf(stderr, DCC_VERSION"\n");
                  break;

            case 'd':
                  ++dcc_clnt_debug;
                  break;

            case 'q':
                  ++quiet;
                  break;

            case 'h':
                  homedir = optarg;
                  break;

            case 'c':
                  ids_nm = optarg;
                  break;

            default:
                  usage();
            }
      }
      argc -= optind;
      argv += optind;

      if (!dcc_cdhome(dcc_emsg, homedir))
            dcc_error_msg("%s", dcc_emsg);

      dcc_clnt_unthread_init();
      dcc_wf_init(&cmn_wf, 0, 0);

      dcc_all_srvrs = 1;
      if (!init_map(!quiet))
            which_map = MAP_TMP;
      else
            dcc_ctxts_unlock();

      /* with a list of commands, act as a batch utility */
      if (argc != 0) {
            for (;;) {
                  /* a final arg of "-" says switch to interactive mode */
                  if (argc == 1 && !strcmp(*argv, "-"))
                        break;

                  if (!do_cmds(*argv)) {
                        fputs(" ?\n", stderr);
                        exit(EX_UNAVAILABLE);
                  }
                  if (!dcc_info_unlock(dcc_emsg))
                        dcc_error_msg("%s", dcc_emsg);

                  ++argv;
                  if (!--argc) {
                        exit(EX_OK);
                  }
            }
      }

      /* Without an arg list of commands, look for commands from STDIN.
       * Commands end with a semicolon or newline. */
      for (;;) {
            if (!dcc_info_unlock(dcc_emsg))
                  dcc_error_msg("%s", dcc_emsg);
            printf("cdcc %s> ",
                   which_map == MAP_INFO ? info_map_nm : "-");
            fflush(stderr);
            fflush(stdout);
            if (!fgets(cmd_buf, sizeof(cmd_buf), stdin)) {
                  fputc('\n', stdout);
                  exit(EX_OK);
            }
            if (!do_cmds(cmd_buf))
                  fputs(" ?\n", stderr);
      }
}



static u_char                       /* 0=failed, 1=ok */
get_passwd(u_char privileged)
{
      if (passwd_set) {
            strncpy(srvr.passwd, passwd, sizeof(srvr.passwd));
            return (!privileged || srvr.clnt_id != DCC_ID_ANON);
      }
      memset(srvr.passwd, 0, sizeof(srvr.passwd));

      srvr_clnt_tbl = 0;
      srvr_clnt_id = srvr.clnt_id;
      if (srvr.clnt_id == DCC_ID_ANON)
            return !privileged;

      /* Fetch the common server passwords only if we can read them without
       * set-UID.  This keeps random local users from attacking local
       * or remote servers with privileged commands, but does not slow
       * down privilege users who could use an editor to read and use
       * the cleartext passwords manually. */
      dcc_rel_priv();
      if (0 > access(ids_nm, R_OK)
          && errno == EACCES)
            return !privileged;
      if (load_ids(dcc_emsg, &srvr_clnt_tbl, srvr_clnt_id) < 0) {
            if (srvr_clnt_id != DCC_ID_ANON) {
                  if (privileged)
                        dcc_error_msg("%s", dcc_emsg);
                  srvr.clnt_id = DCC_ID_ANON;
            }
            return !privileged;
      }
      if (srvr_clnt_tbl)
            strncpy(srvr.passwd, srvr_clnt_tbl->cur_passwd,
                  sizeof(srvr.passwd));
      return 1;
}



static void
clear_conn(void)
{
      if (ctxt) {
            dcc_rel_ctxt(0, ctxt);
            ctxt = 0;
      }
}



static void
set_which_map(enum WHICH_MAP new)
{
      /* release things even if nothing seems to be changing
       * to ensure that we bind a new socket */
      if (!dcc_unmap_info(dcc_emsg))
            dcc_error_msg("%s", dcc_emsg);
      clear_conn();
      which_map = new;
      if (new == MAP_INFO)
            passwd_set = 0;
}



/* start talking to the local map file
 *    on success the contexts and mapped file are locked */
static u_char                       /* 0=failed 1=mapped and locked */
init_map(u_char complain)
{
      u_char result;

      dcc_ctxts_lock();
      if (which_map == MAP_TMP) {
            result = dcc_map_lock_tmp_info(dcc_emsg, &srvr, clnt_flags);
      } else {
            result = dcc_map_lock_info(dcc_emsg, info_map_nm, -1);
      }
      if (result) {
            clnt_flags = dcc_clnt_info->flags;
            return 1;
      }
      dcc_ctxts_unlock();
      if (complain)
            dcc_error_msg("%s", dcc_emsg);
      return 0;
}



/* start talking to a DCC server
 *    If we already had a private map file, forget it.
 *    on success the contexts are locked but the mapped file is not locked */
static u_char                       /* 0=failed, 1=ok */
init_conn(u_char no_srvr_ok)
{
      if (ctxt) {
            if (!dcc_clnt_rdy(dcc_emsg, ctxt,
                          (grey_on ? DCC_CLNT_FG_GREY : 0)
                          | (no_srvr_ok ? DCC_CLNT_FG_NO_SRVR_OK : 0)
                          | DCC_CLNT_FG_NO_FAIL)) {
                  dcc_error_msg("%s", dcc_emsg);
                  return 0;
            }
            /* check the other (greylist or not) server */
            if (!dcc_clnt_rdy(dcc_emsg, ctxt,
                          (!grey_on ? DCC_CLNT_FG_GREY : 0)
                          | DCC_CLNT_FG_NO_SRVR_OK
                          | DCC_CLNT_FG_NO_FAIL)
                && dcc_clnt_debug)
                  dcc_error_msg("%s", dcc_emsg);
            clnt_flags = dcc_clnt_info->flags;
            return 1;
      }

      if (which_map == MAP_TMP) {
            /* create a brand new temporary map */
            ctxt = dcc_tmp_clnt_init(dcc_emsg, ctxt, &srvr,
                               grey_on, clnt_flags);
            if (!ctxt) {
                  dcc_error_msg("%s", dcc_emsg);
                  return 0;
            }
            clnt_flags = dcc_clnt_info->flags;
            return 1;
      }

      if (!init_map(1))       /* lock things */
            return 0;
      if (!grey_set) {
            grey_on = (dcc_clnt_info->dcc.nms[0].hostname[0] == '\0'
                     && dcc_clnt_info->grey.nms[0].hostname[0] != '\0');
      }

      ctxt = dcc_clnt_init(dcc_emsg, ctxt, info_map_nm,
                       (grey_on ? DCC_CLNT_FG_GREY : 0)
                       | (no_srvr_ok ? DCC_CLNT_FG_NO_SRVR_OK : 0)
                       | DCC_CLNT_FG_NO_FAIL);
      if (!ctxt) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }
      /* check the other (greylist or not) server */
      if (!dcc_clnt_rdy(dcc_emsg, ctxt,
                    (!grey_on ? DCC_CLNT_FG_GREY : 0)
                    | DCC_CLNT_FG_NO_SRVR_OK
                    | DCC_CLNT_FG_NO_FAIL)
          && dcc_clnt_debug)
            dcc_error_msg("%s", dcc_emsg);

      clnt_flags = dcc_clnt_info->flags;

      return 1;
}



static void
dcc_map_changed(void)
{
      if (which_map == MAP_INFO)
            dcc_mmap_utime(dcc_info_nm, 1);
      dcc_force_measure_rtt(&dcc_clnt_info->dcc, 1);
      dcc_force_measure_rtt(&dcc_clnt_info->grey, 1);
}



/* compare ignoring case */
static const char *                 /* 0 or mismatch in str */
cmd_cmp(const char *str, const char *op)
{
      char op_c, str_c;
      int len;

      len = 0;
      for (;;) {
            op_c = *op;
            /* avoid tolower() to avoid build hassles on odd systems */
            if (op_c >= 'A' && op_c <= 'Z')
                  op_c += 'a'-'A';
            str_c = *str;
            if (str_c == '\t')
                  str_c = ' ';
            else if (str_c >= 'A' && str_c <= 'Z')
                  str_c += 'a'-'A';
            if (op_c != str_c) {
                  /* compress bursts of blanks */
                  if (str_c == ' ' && len != 0 && *(op-1) == ' ') {
                        ++str;
                        continue;
                  }
                  return str;
            }
            if (op_c == '\0')
                  return 0;
            ++op;
            ++str;
            ++len;
      }
}



/* Display our name for the server and its address,
 * while suppressing some duplicates */
static void
print_aop(int anum)                 /* -1 or server index */
{
      const DCC_SRVR_CLASS *class;
      const char *resp_addr;
      char date_buf[40];
      const char *srvr_nm;
      struct tm tm;

      resp_addr = dcc_su2str_opt(&op_resp_su, 0, '\0');
      class = DCC_GREY2CLASS(grey_on);
      /* Display the preferred server if anum is -1 */
      if (anum < 0)
            anum = class->act_inx;
      if (anum < class->num_addrs) {
            srvr_nm = class->nms[class->addrs[anum].nm_inx].hostname;
            if (srvr_nm && strcmp(srvr_nm, resp_addr)) {
                  fputs(srvr_nm, stdout);
                  putchar(' ');
            }
            printf("%s\n        server-ID %d",
                   dcc_su2str(&op_resp_su),
                   class->addrs[anum].srvr_id);
      } else {
            printf("%s\n                    ",
                   dcc_su2str(&op_resp_su));
      }
      if (srvr.clnt_id != DCC_ID_ANON)
            printf("  client-ID %d", srvr.clnt_id);
      if (which_map == MAP_INFO)
            printf("  %s", dcc_info_nm);
      strftime(date_buf, sizeof(date_buf), "  %X",
             dcc_localtime(op_start.tv_sec, &tm));
      fputs(date_buf, stdout);
      putchar('\n');
}



static u_char                       /* 0=some kind of problem, 1=done */
start_aop(DCC_AOPS aop, u_int32_t val1, int anum)
{
      DCC_OPS result;
      int result_len;

      if (!init_conn(0))
            return 0;

      memset(&op_resp, 0, sizeof(op_resp));
      gettimeofday(&op_start, 0);
      result_len = sizeof(op_resp);
      result = dcc_aop(dcc_emsg, ctxt, grey_on, anum, aop, val1, 0, 0, 0,
                   &op_resp, &result_len, &op_resp_su);
      gettimeofday(&op_end, 0);

      if (result == DCC_OP_INVALID
          || result == DCC_OP_ERROR) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }

      return 1;
}



static void
fin_aop(int anum,             /* -1 or index of server */
      u_char psrvr)                 /* 1=print server name */
{
      if (psrvr)
            print_aop(anum);

      /* say what the server had to say */
      fputs(op_resp.string, stdout);
      putchar('\n');

      if (dcc_clnt_debug) {
            printf("%.2f ms\n",
                   ((op_end.tv_sec-op_start.tv_sec)*1000.0
                  + (op_end.tv_usec-op_start.tv_usec)/1000.0));
      }
      putchar('\n');
}



static u_char                       /* 0=some kind of problem, 1=done */
do_aop(DCC_AOPS aop, u_int32_t val, int anum, u_char psrvr)
{
      if (!start_aop(aop, val, anum))
            return 0;
      fin_aop(anum, psrvr);
      return 1;
}



static u_char                       /* 0=bad command,  1=did it */
do_op(const char *op)
{
      int op_num;

      op_num = 0;
      for (;;) {
            if (op_num >= DIM(aop_tbl)) {
                  dcc_error_msg("unrecognized command \"%s\"", op);
                  return 0;
            }
            /* do a command */
            if (!cmd_cmp(op, aop_tbl[op_num].op))
                  break;
            op_num++;
      }

      /* send an administrative request to the server */
      if (!get_passwd(aop_tbl[op_num].privileged)) {
            dcc_error_msg("\"%s\" is a privileged operation"PRV_MSG,
                        aop_tbl[op_num].op, DCC_NM2PATH(IDS_NM_DEF));
            return 0;
      }

      /* try to send it */
      return do_aop(aop_tbl[op_num].aop, aop_tbl[op_num].val, -1, 1);
}



static u_char
ck_priv_cmd(const CMD_TBL_ENTRY *ce, u_char uid_0, u_char privileged)
{
      /* always call get_passwd() so we have always fetched a password */
      if (!get_passwd(privileged)
          || (which_map != MAP_TMP
            && uid_0
            && 0 > access(info_map_nm, R_OK)
            && errno != ENOENT)) {
            dcc_error_msg("\"%s\" is a privileged command"PRV_MSG,
                        ce->cmd, DCC_NM2PATH(IDS_NM_DEF));
            return 0;
      }
      return 1;
}



static u_char                       /* 1=ok 0=bad command */
cmd(const char *p)
{
      const char *arg;
      int cmd_num, j;
      const CMD_TBL_ENTRY *ce;

      /* look for the string as a command and execute it if we find */
      for (cmd_num = 0; cmd_num < DIM(cmd_tbl); ++cmd_num) {
            ce = &cmd_tbl[cmd_num];
            arg = cmd_cmp(p, ce->cmd);
            /* if the command table entry and the command completely
             * matched, then infer a null argument */
            if (!arg) {
                  if (!ck_priv_cmd(ce, ce->uid_0, ce->privileged))
                        return 0;
                  if (ce->args != 1) {
                        j = ce->fnc("", ce);
                        if (j >= 0)
                              return j;
                  }
                  help_cmd(p, 0);
                  return 0;
            }
            /* if the command table entry is an initial sustring of
             * the user's command, then the rest of the command must
             * start with white space.  Trim and use it as the argument */
            j = strspn(arg, DCC_WHITESPACE);
            if (j) {
                  if (ce->args == 2) {
                        help_cmd(p, 0);   /* arg not allowed */
                        return 0;
                  }
                  if (!ck_priv_cmd(ce, ce->uid_0, ce->privileged))
                        return 0;
                  j = ce->fnc(arg+j, ce);
                  if (j >= 0)
                        return j;
                  help_cmd(p, 0);
                  return 0;
            }
      }

      /* otherwise try to interpret it as a DCC administrative packet */
      return do_op(p);
}



u_char                              /* 0=bad command, 1=ok */
do_cmds(char *cmd_buf)
{
      char *next_cmd, *cur_cmd, *cmd_end;
      char c;

      next_cmd = cmd_buf;
      for (;;) {
            cur_cmd = next_cmd + strspn(next_cmd, DCC_WHITESPACE";");

            if (*cur_cmd == '#' || *cur_cmd == '\0')
                  return 1;

            next_cmd = cur_cmd + strcspn(cur_cmd, ";\n\r");
            cmd_end = next_cmd;
            next_cmd += strspn(next_cmd, ";\n\r");

            /* null terminate and trim trailing white space from
             * command or arg */
            do {
                  *cmd_end-- = '\0';
                  c = *cmd_end;
            } while (cmd_end >= cur_cmd
                   && strchr(DCC_WHITESPACE";", c));

            if (*cur_cmd == '\0')   /* ignore blank commands */
                  continue;

            if (!cmd(cur_cmd))
                  return 0;
      }
}



static int
help_cmd_print(int pos, const char *str)
{
#define HELP_COL 24
      int col;

      col = strlen(str)+1;
      col += HELP_COL - (col % HELP_COL);
      pos += col;
      if (pos > 78) {
            putchar('\n');
            pos = col;
      }
      printf("%-*s", col, str);

      return pos;
#undef HELP_COL
}



static int
help_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      int i, pos;
      const char *p;

      /* say something about one command */
      if (arg) {
            for (i = 0; i < DIM(cmd_tbl); ++i) {
                  p = cmd_cmp(arg, cmd_tbl[i].cmd);
                  if (!p || *p == ' ' || *p == '\t') {
                        if (!cmd_tbl[i].help_str)
                              break;
                        printf("usage: %s\n", cmd_tbl[i].help_str);
                        return 1;
                  }
            }
            for (i = 0; i < DIM(aop_tbl); ++i) {
                  p = cmd_cmp(arg, aop_tbl[i].op);
                  if (!p || *p == ' ' || *p == '\t') {
                        p = aop_tbl[i].help_str;
                        if (!p || !*p)
                              p = aop_tbl[i].op;
                        printf("usage: %s\n", p);
                        return 1;
                  }
            }
      }

      /* talk about all of the commands */
      printf("   version "DCC_VERSION"\n");
      pos = 0;
      for (i = 0; i < DIM(cmd_tbl); ++i) {
            p = cmd_tbl[i].help_str;
            if (!p)
                  continue;
            pos = help_cmd_print(pos, p);
      }
      for (i = 0; i < DIM(aop_tbl); ++i) {
            p = aop_tbl[i].help_str;
            if (!p) {
                  p = aop_tbl[i].op;
            } else if (!*p) {
                  continue;
            }
            pos = help_cmd_print(pos, p);
      }
      putchar('\n');

      return 1;
}



static int
exit_cmd(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
      exit(EX_OK);
      return -1;
}


static int
grey_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      if (arg[0] == '\0') {
            printf("    Greylist mode %s%s\n",
                   grey_on ? "on" : "off",
                   grey_set ? "" : " by default");
            return 1;
      }
      if (!strcmp(arg, "off")) {
            grey_on = 0;
            grey_set = 1;
            clear_conn();
      } else if (!strcmp(arg, "on")) {
            grey_on = 1;
            grey_set = 1;
            clear_conn();
      } else {
            return -1;
      }
      if (!port_set)
            srvr.port = DCC_GREY2PORT(grey_on);
      return 1;
}



static int
homedir_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      if (arg[0] != '\0') {
            if (!dcc_cdhome(dcc_emsg, arg)) {
                  dcc_error_msg("%s", dcc_emsg);
                  return 0;
            }
            set_which_map(MAP_INFO);
      }
      printf("    homedir=%s\n", dcc_homedir);
      return 1;
}



/* set name of map file */
static int
file_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      if (arg[0] == '\0') {
            if (which_map == MAP_INFO)
                  printf("    using map file: %s\n",
                         dcc_info_nm);
            else
                  printf("    map file %s but using temporary file\n",
                         info_map_nm);
            return 1;
      }

      BUFCPY(info_map_nm, arg);
      set_which_map(MAP_INFO);
      return 1;
}



/* create a new client map or parameter file */
static int
new_map_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      if (arg[0] == '\0')
            arg = DCC_MAP_NM_DEF;

      dcc_rel_priv();
      if (!dcc_create_map(dcc_emsg, arg, 0, 0, 0, 0, 0, clnt_flags)) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }
      BUFCPY(info_map_nm, arg);
      set_which_map(MAP_INFO);
      if (!quiet) {
            printf("    created %s\n", dcc_info_nm);
            return info_cmd("", ce);
      }
      return 1;
}



/* server hostname */
static int
host_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      DCC_SRVR_NM nm;

      if (arg[0] == '\0') {
            if (which_map == MAP_INFO)
                  return info_cmd("", ce);
            printf("    %s server hostname \"%s\"\n",
                   grey_on ? "Greylist" : "DCC", srvr.hostname);
            return 1;
      }
      if (!strcmp(arg, "-")) {
            set_which_map(MAP_INFO);
            if (!init_map(1)) {
                  set_which_map(MAP_TMP);
                  return 0;
            }
            dcc_ctxts_unlock();
            return 1;
      }

      arg = dcc_parse_nm_port(0, arg, 0,
                        nm.hostname, sizeof(nm.hostname),
                        &nm.port, 0, 0,
                        0, 0);
      if (!arg)
            return 0;
      arg += strspn(arg, DCC_WHITESPACE);
      if (*arg != '\0')
            return 0;

      set_which_map(MAP_TMP);
      memcpy(srvr.hostname, nm.hostname, sizeof(srvr.hostname));
      if (nm.port != 0) {
            srvr.port = nm.port;
            port_set = 1;
      }
      return 1;
}



/* server port # */
static int
port_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      u_int port;

      if (arg[0] == '\0') {
            if (which_map == MAP_INFO)
                  return info_cmd("", ce);
            printf("    port=%d\n", ntohs(srvr.port));
            return 1;
      }

      port = dcc_get_port(0, arg, DCC_GREY2PORT(grey_on), 0, 0);
      if (port == DCC_GET_PORT_INVALID)
            return 0;

      srvr.port = port;
      port_set = 1;
      set_which_map(MAP_TMP);
      return 1;
}



static int
ipv6_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      u_char new_use_ipv6;

      if (!init_map(1))
            return 0;

      if (arg[0] == '\0') {
            printf("    IPv6 %s\n",
                   (clnt_flags & DCC_CLNT_INFO_FG_IPV6) ? "on" : "off");
            return 1;
      }

      if (!ck_priv_cmd(ce, 1, 0))
            return 0;

      if (!strcmp(arg, "off")) {
            new_use_ipv6 = 0;
      } else if (!strcmp(arg, "on")) {
            new_use_ipv6 = DCC_CLNT_INFO_FG_IPV6;
      } else {
            return -1;
      }

      dcc_ctxts_lock();
      if (!dcc_resolve_lock(dcc_emsg)) {
            dcc_error_msg("%s", dcc_emsg);
            dcc_ctxts_unlock();
            return 0;
      }
      if (dcc_clnt_info
          && (dcc_clnt_info->flags & DCC_CLNT_INFO_FG_IPV6) != new_use_ipv6) {
            dcc_clnt_info->flags ^= DCC_CLNT_INFO_FG_IPV6;
            clnt_flags = dcc_clnt_info->flags;
            dcc_map_changed();
      }

      if (!dcc_resolve_unlock(dcc_emsg)) {
            dcc_error_msg("%s", dcc_emsg);
            dcc_ctxts_unlock();
            return 0;
      }
      dcc_ctxts_unlock();

      if (init_conn(1)
          && (dcc_clnt_info->flags & DCC_CLNT_INFO_FG_IPV6) != new_use_ipv6) {
#ifdef NO_IPV6
            dcc_error_msg("IPv6 switch not changed;"
                        " No IPv6 support in this system?");
#else
            dcc_error_msg("IPv6 switch not changed.");
#endif
      }

      return 1;
}



static int
socks_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      u_char new_use_socks;

      if (!init_map(1))
            return 0;

      if (arg[0] == '\0') {
            printf("    SOCKS %s\n",
                   (clnt_flags & DCC_CLNT_INFO_FG_SOCKS) ? "on" : "off");
            return 1;
      }

      if (!ck_priv_cmd(ce, 1, 0))
            return 0;

      if (!strcmp(arg, "off")) {
            new_use_socks = 0;
      } else if (!strcmp(arg, "on")) {
            new_use_socks = DCC_CLNT_INFO_FG_SOCKS;
      } else {
            return -1;
      }

      dcc_ctxts_lock();
      if (!dcc_resolve_lock(dcc_emsg)) {
            dcc_error_msg("%s", dcc_emsg);
            dcc_ctxts_unlock();
            return 0;
      }
      if (dcc_clnt_info
          && (dcc_clnt_info->flags&DCC_CLNT_INFO_FG_SOCKS) != new_use_socks) {
            dcc_clnt_info->flags ^= DCC_CLNT_INFO_FG_SOCKS;
            clnt_flags = dcc_clnt_info->flags;
            dcc_map_changed();
      }

      if (!dcc_resolve_unlock(dcc_emsg)) {
            dcc_error_msg("%s", dcc_emsg);
            dcc_ctxts_unlock();
            return 0;
      }
      dcc_ctxts_unlock();
      return 1;
}



static int
passwd_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      DCC_PASSWD new_passwd;

      if (arg[0] == '\0') {
            if (which_map == MAP_INFO)
                  return info_cmd("", ce);
            if (passwd_set)
                  printf("    password %s\n", passwd);
            else
                  printf("    password not set\n");
            return 1;
      }

      arg = dcc_parse_word(0, new_passwd, sizeof(passwd),
                       arg, "password", 0, 0);
      if (!arg || *arg != '\0')
            return -1;
      strncpy(passwd, new_passwd, sizeof(passwd));
      passwd_set = 1;
      set_which_map(MAP_TMP);
      return 1;
}



static int
id_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_CLNT_ID id;

      if (arg[0] == '\0') {
            printf("    ID=%d\n", srvr_clnt_id);
            return 1;
      }

      id = dcc_get_id(0, arg, 0, 0);
      if (id == DCC_ID_INVALID)
            return -1;

      srvr.clnt_id = srvr_clnt_id = id;
      set_which_map(MAP_TMP);
      get_passwd(1);
      return 1;
}



static int
debug_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      char debug_str[24];
      char ttl_str[24];
      int new_ttl, new_debug;
      char *p;

      if (arg[0] == '\0') {
            if (!dcc_clnt_debug)
                  snprintf(debug_str, sizeof(debug_str),
                         "debug off");
            else if (dcc_clnt_debug == 1)
                  snprintf(debug_str, sizeof(debug_str),
                         "debug on");
            else
                  snprintf(debug_str, sizeof(debug_str),
                         "debug on+%d\n", dcc_clnt_debug-1);
            if (dcc_debug_ttl != 0)
                  snprintf(ttl_str, sizeof(ttl_str),
                         "    TTL=%d", dcc_debug_ttl);
            else
                  ttl_str[0] = '\0';
            printf("    %s%s\n", debug_str, ttl_str);
            return 1;
      }

      new_ttl = dcc_debug_ttl;
      new_debug = dcc_clnt_debug;
      for (;;) {
            if (!CSTRCMP(arg, "off")) {
                  new_debug = 0;
                  arg += STRZ("off");
            } else if (!CSTRCMP(arg, "on")) {
                  ++new_debug;
                  arg += STRZ("on");
            } else if (!CSTRCMP(arg, "ttl=")) {
                  new_ttl = strtoul(arg+STRZ("ttl="), &p, 0);
#if defined(IPPROTO_IP) && defined(IP_TTL)
                  if (new_ttl < 256)
                        arg = p;
#else
                  printf("    TTL setting not supported\n");
#endif
            }

            if (*arg == ' ' || *arg == '\t') {
                  arg += strspn(arg, DCC_WHITESPACE);
            } else if (*arg == '\0') {
                  break;
            } else {
                  return -1;
            }
      }
      dcc_debug_ttl = new_ttl;
      if (dcc_debug_ttl != 0)
            set_which_map(MAP_TMP);
      dcc_clnt_debug = new_debug;
      if (dcc_clnt_debug > 1)
            printf("    debug on+%d\n", dcc_clnt_debug-1);
      return 1;
}



static int
delete_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_SRVR_CLASS *class;
      DCC_SRVR_NM nm, *nmp;
      DCC_SRVR_ADDR *addr;
      u_char del_grey;

      del_grey = grey_on;
      if (!dcc_parse_srvr_nm(dcc_emsg, &nm, &del_grey, arg, 0, 0)) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }

      /* map and lock */
      set_which_map(MAP_INFO);
      if (!init_map(1))
            return 0;
      if (!dcc_resolve_lock(dcc_emsg)) {
            dcc_ctxts_unlock();
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }

      class = DCC_GREY2CLASS(del_grey);
      for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) {
            if (strcasecmp(nmp->hostname, nm.hostname))
                  continue;
            if (nmp->port == nm.port) {
                  /* Found it.  Delete everything we've learned about
                   * its IP addresses so their values won't be saved
                   * during the re-measuring of RTT's */
                  for (addr = class->addrs;
                       addr <= LAST(class->addrs);
                       ++addr) {
                        if (addr->nm_inx == nmp - class->nms)
                              memset(addr, 0, sizeof(*addr));
                  }
                  if (nmp != LAST(class->nms))
                        memmove(nmp, nmp+1,
                              (LAST(class->nms) - nmp)*sizeof(*nmp));
                  memset(LAST(class->nms), 0, sizeof(*nmp));
                  dcc_force_measure_rtt(class, 1);
                  if (!dcc_resolve_unlock(dcc_emsg))
                        dcc_error_msg("%s", dcc_emsg);
                  dcc_ctxts_unlock();
                  return 1;
            }
      }

      if (!dcc_resolve_unlock(dcc_emsg))
            dcc_error_msg("%s", dcc_emsg);
      dcc_ctxts_unlock();
      dcc_error_msg("entry not found");
      return 0;
}



static int
add_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_SRVR_CLASS *class;
      DCC_SRVR_NM nm, *nmp, *tgt_nmp;
      u_char add_grey;

      add_grey = grey_set && grey_on;

      if (0 >= dcc_parse_srvr_nm(dcc_emsg, &nm, &add_grey, arg, 0, 0)) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }
      if (nm.clnt_id == DCC_ID_ANON && add_grey) {
            dcc_error_msg("anonymous client-ID invalid"
                        " for Greylist server %s",
                        nm.hostname);
            return 0;
      }

      /* map and lock the information */
      set_which_map(MAP_INFO);
      if (!init_map(1))
            return 0;
      /* lock the right to resolve hostnames */
      if (!dcc_resolve_lock(dcc_emsg)) {
            dcc_ctxts_unlock();
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }

      /* look for the old entry or a new, free entry */
      class = DCC_GREY2CLASS(add_grey);
      tgt_nmp = 0;
      for (nmp = class->nms; nmp <= LAST(class->nms); ++nmp) {
            if (nmp->hostname[0] == '\0') {
                  if (!tgt_nmp)
                        tgt_nmp = nmp;
                  continue;
            }
            if (!strcmp(nmp->hostname, nm.hostname)
                && nmp->port == nm.port) {
                  printf("    overwriting existing entry\n");
                  tgt_nmp = nmp;
                  break;
            }
      }

      if (tgt_nmp) {
            memcpy(tgt_nmp, &nm, sizeof(*tgt_nmp));
            dcc_force_measure_rtt(class, 1);
            if (!dcc_resolve_unlock(dcc_emsg))
                  dcc_error_msg("%s", dcc_emsg);
            dcc_ctxts_unlock();
            return 1;
      }

      if (!dcc_resolve_unlock(dcc_emsg))
            dcc_error_msg("%s", dcc_emsg);
      dcc_ctxts_unlock();
      if (add_grey)
            dcc_error_msg("too many Greylist server names");
      else
            dcc_error_msg("too many DCC server names");
      return 0;
}



static void
add_new_nms(const DCC_SRVR_NM new_nms[DCC_MAX_SRVR_NMS],
          DCC_SRVR_NM old_nms[DCC_MAX_SRVR_NMS])
{
      const DCC_SRVR_NM *new_nmp;
      DCC_SRVR_NM *old_nmp;

      for (new_nmp = new_nms;
           new_nmp < &new_nms[DCC_MAX_SRVR_NMS]
           && new_nmp->hostname[0] != '\0';
           ++new_nmp) {
            for (old_nmp = old_nms;
                 old_nmp <= &old_nms[DCC_MAX_SRVR_NMS];
                 ++old_nmp) {
                  if (old_nmp->hostname[0] == '\0'
                      || (!strcmp(old_nmp->hostname, new_nmp->hostname)
                        && old_nmp->port == new_nmp->port)) {
                        memcpy(old_nmp, new_nmp, sizeof(*old_nmp));
                        break;
                  }
            }
      }
}



static int
load_cmd(const char *lfile, const CMD_TBL_ENTRY *ce UATTRIB)
{
      u_char new_clnt_flags, load_grey;
      int flags_set;
      u_char created;
      DCC_SRVR_NM new_nm;
      DCC_SRVR_NM dcc_nms[DCC_MAX_SRVR_NMS];
      int num_dcc_nms;
      DCC_SRVR_NM grey_nms[DCC_MAX_SRVR_NMS];
      int num_grey_nms;
      char buf[sizeof(DCC_SRVR_NM)*3];
      const char *bufp, *cp;
      FILE *f;
      int fd, lineno;

      if (*lfile == '\0')
            return -1;

      dcc_rel_priv();
      if (!strcmp(lfile,"-")) {
            lfile = 0;
            fd = dup(fileno(stdin));
            if (fd < 0) {
                  dcc_error_msg("dup(stdin): %s", ERROR_STR());
                  return 0;
            }
            f = fdopen(fd, "r");
            if (!f) {
                  dcc_error_msg("fdopen(): %s", ERROR_STR());
                  return 0;
            }
      } else {
            f = dcc_open_srvr_nm(dcc_emsg, lfile);
            if (!f) {
                  dcc_error_msg("%s", dcc_emsg);
                  return 0;
            }
      }

      /* parse the text file to create a pair of lists of server names */
      flags_set = 0;
      new_clnt_flags = clnt_flags;
      num_dcc_nms = 0;
      memset(dcc_nms, 0, sizeof(dcc_nms));
      num_grey_nms = 0;
      memset(grey_nms, 0, sizeof(grey_nms));
      lineno = 0;
      for (;;) {
            bufp = fgets(buf, sizeof(buf), f);
            if (!bufp) {
                  if (ferror(f)) {
                        dcc_error_msg("fgets(%s): %s",
                                    !lfile
                                    ? "STDIN" : DCC_NM2PATH(lfile),
                                    ERROR_STR());
                        fclose(f);
                        return 0;
                  }
                  break;
            }

            ++lineno;

            /* skip blank lines and comments */
            bufp += strspn(bufp, DCC_WHITESPACE);
            if (*bufp == '\0' || *bufp == '#')
                  continue;

            /* look for flags in the first non-comment line */
            if (!flags_set++) {
                  cp = bufp;
                  if (!strncmp(DCC_INFO_USE_IPV4, cp,
                             sizeof(DCC_INFO_USE_IPV4)-1)) {
                        cp += sizeof(DCC_INFO_USE_IPV4)-1;
                        new_clnt_flags = 0;
                  } else if (!strncmp(DCC_INFO_USE_IPV6, cp,
                                  sizeof(DCC_INFO_USE_IPV6)-1)) {
                        cp += sizeof(DCC_INFO_USE_IPV6)-1;
                        new_clnt_flags = DCC_CLNT_INFO_FG_IPV6;
                  } else {
                        ++flags_set;
                  }
                  if (flags_set == 1) {
                        /* We found "IPv6 on" or "off".
                         * Loof for "use SOCKS" */
                        cp += strspn(cp, DCC_WHITESPACE);
                        if (*cp == '\0')
                              continue;
                        if (!strcasecmp(DCC_INFO_USE_SOCKS"\n", cp)) {
                              new_clnt_flags|=DCC_CLNT_INFO_FG_SOCKS;
                              continue;
                        }
                  }
                  /* the first non-comment line must be a server name */
            }

            load_grey = 0;
            if (0 >= dcc_parse_srvr_nm(dcc_emsg, &new_nm, &load_grey,
                                 bufp, lfile, lineno)) {
                  dcc_error_msg("%s", dcc_emsg);
                  fclose(f);
                  return 0;
            }
            if (load_grey) {
                  if (new_nm.clnt_id == DCC_ID_ANON) {
                        dcc_error_msg("anonymous client-ID invalid"
                                    " for Greylist server %s%s",
                                    new_nm.hostname,
                                    fnm_lineno(lfile, lineno));
                        fclose(f);
                        return 0;
                  }
                  if (num_grey_nms >= DIM(grey_nms)) {
                        dcc_error_msg("too many Greylist server names"
                                    "%s",
                                    fnm_lineno(lfile, lineno));
                        fclose(f);
                        return 0;
                  }
                  grey_nms[num_grey_nms++] = new_nm;
            } else {
                  if (num_dcc_nms >= DIM(dcc_nms)) {
                        dcc_error_msg("too many DCC server names%s",
                                    fnm_lineno(lfile, lineno));
                        fclose(f);
                        return 0;
                  }
                  dcc_nms[num_dcc_nms++] = new_nm;
            }
      }
      fclose(f);
      if (num_grey_nms == 0 && num_dcc_nms == 0) {
            dcc_error_msg("no DCC server names%s",
                        fnm_lineno(lfile, lineno));
            return 0;
      }


      /* create the map and lock, install, and unlock the information */
      dcc_rel_priv();
      dcc_ctxts_lock();
      created = 0;
      if (0 < dcc_map_info(dcc_emsg, info_map_nm, -1)) {
            /* copy into an existing map */
            if (!dcc_info_lock(dcc_emsg)) {
                  dcc_error_msg("%s", dcc_emsg);
                  dcc_ctxts_unlock();
                  return 0;
            }
      } else {
            /* create a new map */
            dcc_ctxts_unlock();
            if (!dcc_create_map(dcc_emsg, info_map_nm, 0,
                            0, 0, 0, 0, new_clnt_flags)) {
                  dcc_error_msg("%s", dcc_emsg);
                  return 0;
            }
            created = 1;
            printf("    created %s\n", dcc_info_nm);
            set_which_map(MAP_INFO);
            if (!init_map(1))
                  return 0;
      }
      if (!dcc_resolve_lock(dcc_emsg)) {
            if (created)
                  unlink(dcc_info_nm);
            dcc_error_msg("%s", dcc_emsg);
            dcc_ctxts_unlock();
            return 0;
      }

      /* merge the old and new entries */
      add_new_nms(grey_nms, dcc_clnt_info->grey.nms);
      add_new_nms(dcc_nms, dcc_clnt_info->dcc.nms);
      dcc_clnt_info->flags = clnt_flags = new_clnt_flags;

      dcc_map_changed();
      if (!dcc_resolve_unlock(dcc_emsg))
            dcc_error_msg("%s", dcc_emsg);
      dcc_ctxts_unlock();
      if (!lfile)
            printf("##################\n\n");

      set_which_map(MAP_INFO);
      if (!quiet)
            return info_cmd("", ce);
      return 1;
}



static int
info_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_CLNT_INFO info;
      u_char dcc, names;

      if (*arg == '\0') {
            names = 0;
      } else if (!strcmp(arg, "-N")) {
            names = 1;
      } else {
            return -1;
      }

      /* map, copy, and unlock the information
       * prefer to talk to the server, but don't insist */
      if (which_map == MAP_TMP) {
            if (!init_conn(1))
                  return 0;
      } else {
            if (!init_conn(1)) {
                  set_which_map(MAP_TMP);
                  return 0;
            }
      }

      /* snapshot the data and then release it while we print it */
      memcpy(&info, dcc_clnt_info, sizeof(info));
      dcc_ctxts_unlock();
      if (!dcc_info_unlock(dcc_emsg)) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }

      dcc_rel_priv();
      if (which_map == MAP_INFO) {
            if (info.dcc.nms[0].hostname[0] != '\0'
                || !grey_on) {
                  dcc_print_info(dcc_info_nm, &info, 0, names,
                               0 <= access(dcc_info_nm, R_OK));
                  dcc = 1;
            } else {
                  dcc = 0;
            }
            if (info.grey.nms[0].hostname[0] != '\0'
                || grey_on) {
                  if (dcc)
                        fputs("\n################\n", stdout);
                  dcc_print_info(dcc_info_nm, &info, 1, names,
                               0 <= access(dcc_info_nm, R_OK));
            }
      } else {
            dcc_print_info(0, &info, 0, names, 1);
      }
      putchar('\n');
      return 1;
}



static int
rtt_cmd(const char *arg, const CMD_TBL_ENTRY *ce)
{
      if (!init_map(1))
            return 0;
      if (!dcc_resolve_lock(dcc_emsg)) {
            dcc_error_msg("%s", dcc_emsg);
      } else {
            dcc_force_measure_rtt(&dcc_clnt_info->dcc, 0);
            dcc_force_measure_rtt(&dcc_clnt_info->grey, 0);
            if (!dcc_resolve_unlock(dcc_emsg))
                  dcc_error_msg("%s", dcc_emsg);
      }
      dcc_ctxts_unlock();

      return info_cmd(arg, ce);
}



/* delete a checksum */
static int                    /* 1=ok, 0=bad checksum, -1=fatal */
delck_sub(DCC_EMSG emsg, DCC_WF *wf UATTRIB,
        const char *fnm UATTRIB, int lineno UATTRIB,
        DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts UATTRIB)
{
      struct timeval cmd_start, cmd_end;
      char type_buf[DCC_XHDR_MAX_TYPE_LEN];
      char ck_buf[sizeof(DCC_SUM)*3+2];
      DCC_DELETE del;
      union {
          DCC_HDR hdr;
          DCC_ERROR     error;
      } resp;
      u_char result;

      printf(" deleting %s  %s\n",
             dcc_type2str(type_buf, sizeof(type_buf), type, 0, 1),
             dcc_ck2str(ck_buf, sizeof(ck_buf), type, sum));

      memset(&del, 0, sizeof(del));
      gettimeofday(&cmd_start, 0);
      del.date = htonl(cmd_start.tv_sec);
      del.ck.type = type;
      del.ck.len = sizeof(del.ck);
      memcpy(&del.ck.sum, sum, sizeof(DCC_SUM));
      result = dcc_clnt_op(emsg, ctxt, DCC_CLNT_FG_NO_FAIL,
                       0, 0, &del.hdr, sizeof(del),
                       DCC_OP_DELETE, &resp.hdr, sizeof(resp), 0);
      gettimeofday(&cmd_end, 0);
      if (!result) {
            dcc_error_msg("%s", dcc_emsg);
      } else {
            switch (resp.hdr.op) {
            case DCC_OP_OK:
                  break;

            case DCC_OP_ERROR:
                  dcc_error_msg("   %.*s",
                              (ntohs(resp.hdr.len)
                               -(int)(sizeof(resp.error)
                                    - sizeof(resp.error.msg))),
                              resp.error.msg);
                  result = 0;
                  break;

            default:
                  dcc_error_msg("unexpected response: %s",
                              dcc_hdr_op2str(&resp.hdr));
                  result = 0;
                  break;
            }
      }

      if (dcc_clnt_debug) {
            printf("%.2f ms\n",
                   ((cmd_end.tv_sec-cmd_start.tv_sec)*1000.0
                  + (cmd_end.tv_usec-cmd_start.tv_usec)/1000.0));
      }
      return result;
}



/* delete a simple checksum */
static int
delck_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      char type_str[DCC_XHDR_MAX_TYPE_LEN+1];

      if (*arg == '\0')
            return -1;
      arg = dcc_parse_word(dcc_emsg, type_str, sizeof(type_str),
                       arg, 0, 0, 0);
      if (!arg)
            return -1;

      if (!init_conn(0))
            return 0;
      return 0 < dcc_parse_hex_ck(dcc_emsg, &cmn_wf, 0, 0, type_str,
                            arg, 0, delck_sub);
}



static int
sleep_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      double s;
      char *p;

      s = strtod(arg, &p);
      if (*p != '\0' || s < 0.001 || s > 1000)
            return -1;
      usleep((u_int)(s*1000000.0));
      return 1;
}



/* get the server's list of recent clients */
static int
clients_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
#define CPY2(s) ((s[0]<<8) | s[1])
#define CPY3(s) ((s[0]<<16) | (s[1]<<8) | s[2])
#define CPY4(s) ((s[0]<<24) | (s[1]<<16) | (s[2]<<8) | s[3])
      u_char nonames, sort, ids, avg, have_max_clients, have_thold;
      u_int max_clients, thold;
      int total, subtotal;
      u_int num_clients, ct_size;
      struct ct {
          struct ct *lt, *gt, *up;
          time_t  last_used;
          u_int32_t     requests;
          u_int   rank;
          u_int16_t     nops;
          u_char  flags;
          DCC_CLNT_ID   id;
          DCC_SOCKU     su;
      } *ctree, **ctptr, *ctup, *ct, *ctnew;
      DCC_OPS result;
      DCC_ADMN_RESP_CLIENTS *cl;
      int cl_len, result_len;
      char name[MAXHOSTNAMELEN];
      char date_buf[40];
      struct tm tm;
      char *p;
      int i;

      max_clients = 10000;
      have_max_clients = 0;
      thold = 0;
      have_thold = 0;

      /* look for "-n", "-ns", "-n -s", etc. */
      nonames = 0;
      sort = 0;
      ids = 0;
      avg = 0;
      while (*arg != 0) {
            arg += strspn(arg, " \t");
            if (*arg == '-') {
                  ++arg;
                  do {
                        if (*arg == 'n') {
                              nonames = 1;
                        } else if (*arg == 's') {
                              sort = 1;
                        } else if (*arg == 'i') {
                              ids = 1;
                        } else if (*arg == 'a') {
                              avg = 1;
                        } else {
                              return -1;
                        }
                  } while (*++arg != ' ' && *arg != '\t' && *arg != '\0');
                  continue;
            }
            if (!have_max_clients
                && (i = strtoul(arg, &p, 10)) != 0
                && (*p == ' ' || *p == '\t' || *p == '\0')) {
                  have_max_clients = 1;
                  max_clients = i;
                  arg = p;
                  continue;
            }
            if (!have_thold
                && (i = strtoul(arg, &p, 10)) > 0
                && i < (1<<16)
                && (*p == ' ' || *p == '\t' || *p == '\0')) {
                  have_thold = 1;
                  thold = i;
                  arg = p;
                  continue;
            }
            return -1;
      }

      if (!ids
          && !ck_priv_cmd(ce, 0, 1))
            return 0;

      if (!init_conn(0))
            return 0;

      /* Collect all of the information before printing it to minimize
       * the changes in the position of hosts and so deleted or missing
       * entries. */
      total = 0;
      subtotal = 0;
      ct_size = 0;
      num_clients = 0;
      ctree = 0;
      for (;;) {
            memset(&op_resp, 0, sizeof(op_resp));
            gettimeofday(&op_start, 0);
            result_len = sizeof(op_resp);
            result = dcc_aop(dcc_emsg, ctxt, grey_on, -1,
                         ids ? DCC_AOP_CLIENTS_ID : DCC_AOP_CLIENTS,
                         (num_clients << 16) + thold,
                         sizeof(DCC_ADMN_RESP_VAL)
                         / sizeof(DCC_ADMN_RESP_CLIENTS),
                         avg, 0,
                         &op_resp, &result_len, &op_resp_su);
            if (result == DCC_OP_INVALID
                || result == DCC_OP_ERROR) {
                  dcc_error_msg("%s", dcc_emsg);
                  break;
            }

            if (!num_clients)
                  print_aop(-1);

            if (result_len == 1)
                  break;

            for (cl = op_resp.clients;
                 (char *)cl < result_len+(char *)op_resp.clients;
                 cl = (DCC_ADMN_RESP_CLIENTS *)((char *)cl + cl_len)) {
                  if (cl->flags & DCC_ADMN_RESP_CLIENTS_SKIP) {
                        num_clients += CPY3(cl->requests);
                        cl_len = (sizeof(*cl) - sizeof(cl->addr)
                                + sizeof(cl->addr.ipv4));
                        continue;
                  }

                  ++num_clients;
                  if (++ct_size > 10000) {
                        dcc_error_msg("too many clients");
                        goto stop;
                  }
                  ctnew = dcc_malloc(sizeof(*ctnew));
                  memset(ctnew, 0, sizeof(*ctnew));
                  ctnew->flags = cl->flags;
                  ctnew->id = CPY4(cl->id);
                  ctnew->last_used = CPY4(cl->last_used);
                  ctnew->requests = CPY3(cl->requests);
                  total += ctnew->requests;
                  ctnew->nops = CPY2(cl->nops);
                  if (cl->flags & DCC_ADMN_RESP_CLIENTS_IPV6) {
                        memcpy(&ctnew->su.ipv6.sin6_addr,
                               cl->addr.ipv6,
                               sizeof(ctnew->su.ipv6.sin6_addr));
                        ctnew->su.sa.sa_family = AF_INET6;
                        cl_len = (sizeof(*cl) - sizeof(cl->addr)
                                + sizeof(cl->addr.ipv6));
                  } else {
                        memcpy(&ctnew->su.ipv4.sin_addr,
                               cl->addr.ipv4,
                               sizeof(ctnew->su.ipv4.sin_addr));
                        ctnew->su.sa.sa_family = AF_INET;
                        cl_len = (sizeof(*cl) - sizeof(cl->addr)
                                + sizeof(cl->addr.ipv4));
                  }

                  /* add the new entry to the tree */
                  ctptr = &ctree;
                  ctup = 0;
                  for (;;) {
                        ct = *ctptr;
                        if (!ct) {
                              ctnew->up = ctup;
                              *ctptr = ctnew;
                              break;
                        }
                        i = !sort;
                        if (!i) {
                              i = (ct->flags
                                   & DCC_ADMN_RESP_CLIENTS_BL);
                              i -= (ctnew->flags
                                    & DCC_ADMN_RESP_CLIENTS_BL);
                        }
                        if (!i) {
                              i = ct->requests;
                              i -= ctnew->requests;
                        }
                        ctup = ct;
                        if (i >= 0) {
                              ctptr = &ct->lt;
                        } else {
                              /* update the threshold if sorting */
                              if (++ct->rank >= max_clients
                                  && thold < ct->requests)
                                  thold = ct->requests;
                              ctptr = &ct->gt;
                        }
                  }
            }
            if ((char *)cl != result_len+(char *)op_resp.clients) {
                  dcc_error_msg("wrong sized clients response; %d != %d",
                              result_len,
                              (int)((char *)cl
                                  - (char *)op_resp.clients));
                  break;
            }

            /* quit if we want only part of the list
             * and  we have it */
            if (!sort && ct_size >= max_clients)
                  break;
      }
stop:
      if (!total)
            total = 1;

      /* print the list in the tree */
      name[0] = '\0';
      num_clients = 0;
      for (ct = ctree; ct; ct = ctnew) {
            ctnew = ct->gt;
            if (ctnew) {
                  ct->gt = 0;
                  continue;
            }

            if (num_clients == 0) {
                  if (sort) {
                        fputs("              ops  nops ", stdout);
                        if (ids)
                              fputs("   ID ", stdout);
                        fputs("   last\n", stdout);
                  } else {
                        fputs("    ops  nops    last           ID\n",
                              stdout);
                  }
            }
            if (++num_clients <= max_clients) {
                  if (sort) {
                        subtotal += ct->requests;
                        printf("%3d%% %3d%% ",
                               ct->requests*100/total,
                               subtotal*100/total);
                  }
                  printf("%7d %5d ", ct->requests, ct->nops);
                  if (sort && ids)
                        printf("%5d ", ct->id);
                  strftime(date_buf, sizeof(date_buf), "%m/%d %X",
                         dcc_localtime(ct->last_used, &tm));
                  printf("%s", date_buf);
                  if (ct->flags & DCC_ADMN_RESP_CLIENTS_BL)
                        fputs(" BLACKLIST", stdout);
                  if (!sort)
                        printf(" %5d", ct->id);
                  if (!ids) {
                        if (!nonames)
                              dcc_su2name(name, sizeof(name),
                                        &ct->su);
                        printf(" %-16s %s",
                               dcc_su2str_opt(&ct->su, 0, '\0'), name);
                  }
                  putchar('\n');

                  ctnew = ct->lt;
                  if (!ctnew) {
                        ctnew = ct->up;
                  } else {
                        ctnew->up = ct->up;
                  }
            }

            memset(ct, 0, sizeof(*ct));
            dcc_free(ct);
      }
      putchar('\n');
      return 1;
#undef CPY2
#undef CPY3
#undef CPY4
}



/* get and set the server's anonymous client delay */
static int
anon_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      int new_delay, old_delay, inflate;
      DCC_OPS result;
      int result_len;
      char *inflate_str, *p;

      inflate = 0;
      if (*arg == '\0') {
            new_delay = DCC_NO_ANON_DELAY;
      } else {
            if (!ck_priv_cmd(ce, 0, 1))
                  return 0;
            if (!strcasecmp(arg, "forever")) {
                  new_delay = DCC_ANON_DELAY_FOREVER;
            } else {
                  new_delay = strtoul(arg, &inflate_str, 10);
                  if (new_delay > DCC_ANON_DELAY_MAX
                      || (*inflate_str != '\0' && *inflate_str != ',')) {
                        dcc_error_msg("invalid delay: \"%s\"", arg);
                        return 0;
                  }
                  if (*inflate_str == ',')
                        inflate_str += strspn(inflate_str,
                                          DCC_WHITESPACE);
                  if (*inflate_str != '\0'
                      && strcasecmp(inflate_str, "none")) {
                        inflate = strtoul(++inflate_str, &p, 10);
                        if (*p != '\0') {
                              dcc_error_msg("invalid delay inflation:"
                                          " \"%s\"", inflate_str);
                              return 0;
                        }
                  }
            }
      }

      if (!init_conn(0))
            return 0;

      memset(&op_resp, 0, sizeof(op_resp));
      gettimeofday(&op_start, 0);
      result_len = sizeof(op_resp);
      result = dcc_aop(dcc_emsg, ctxt, grey_on, -1, DCC_AOP_ANON_DELAY,
                   inflate, new_delay>>8, new_delay, 0,
                   &op_resp, &result_len, &op_resp_su);
      if (result == DCC_OP_INVALID
          || result == DCC_OP_ERROR) {
            dcc_error_msg("%s", dcc_emsg);
            return 0;
      }

      old_delay = ((op_resp.anon_delay.delay[0]<<8)
                 + op_resp.anon_delay.delay[1]);
      if (old_delay == DCC_ANON_DELAY_FOREVER) {
            printf("    anon delay %s FOREVER\n",
                   new_delay != DCC_NO_ANON_DELAY ? "was" : "is");
      } else {
            printf("    anon delay %s %d",
                   new_delay != DCC_NO_ANON_DELAY ? "was" : "is",
                   old_delay);
            inflate = ((op_resp.anon_delay.inflate[0]<<24)
                     +(op_resp.anon_delay.inflate[1]<<16)
                     +(op_resp.anon_delay.inflate[2]<<8)
                     +op_resp.anon_delay.inflate[3]);
            if (inflate != 0)
                  printf(",%d", inflate);
            putchar('\n');
      }
      return 1;
}



/* rewind the flood from a single server */
static int
flod_rewind(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_CLNT_ID id;

      if (!arg)
            return -1;
      id = dcc_get_id(0, arg, 0, 0);
      if (id == DCC_ID_INVALID)
            return -1;

      return do_aop(DCC_AOP_FLOD, id*256 + DCC_AOP_FLOD_REWIND, -1, 1);
}



/* fast forward the flood to a single server */
static int
ffwd_out(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_CLNT_ID id;

      if (!arg)
            return -1;
      id = dcc_get_id(0, arg, 0, 0);
      if (id == DCC_ID_INVALID)
            return -1;

      return do_aop(DCC_AOP_FLOD, id*256 + DCC_AOP_FLOD_FFWD_OUT, -1, 1);
}



/* fast forward the flood to a single server */
static int
ffwd_in(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_CLNT_ID id;

      if (!arg)
            return -1;
      id = dcc_get_id(0, arg, 0, 0);
      if (id == DCC_ID_INVALID)
            return -1;

      return do_aop(DCC_AOP_FLOD, id*256 + DCC_AOP_FLOD_FFWD_IN, -1, 1);
}



/* get the flood counts for a server */
static int
flod_stats(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
      u_int32_t id, next_id;
      DCC_AOP_FLODS op;
      u_char heading;
      int sresult;

      if (!arg)
            return -1;
      if (!CSTRCMP(arg, "clear")) {
            arg += STRZ("clear");
            arg += strspn(arg, DCC_WHITESPACE);
            op = DCC_AOP_FLOD_STATS_CLEAR;
      } else {
            op = DCC_AOP_FLOD_STATS;
      }

      heading = 1;
      if (!strcasecmp(arg, "all")) {
            id = DCC_SRVR_ID_MAX+1;
            for (;;) {
                  if (!start_aop(DCC_AOP_FLOD, id*256 + op, -1))
                        return 0;
                  sresult = sscanf(op_resp.string,
                               DCC_AOP_FLOD_STATS_ID, &next_id);
                  if (1 == sresult
                      && id == next_id)
                        return 1;
                  fin_aop(-1, heading);
                  heading = 0;
                  if (1 != sresult)
                        return 0;
                  id = next_id+DCC_SRVR_ID_MAX+1;
            }
      }

      id = dcc_get_id(0, arg, 0, 0);
      if (id == DCC_ID_INVALID)
            return -1;
      return do_aop(DCC_AOP_FLOD, id*256 + op, -1, heading);
}



/* get the statistics from all known servers */
static int
stats_cmd(const char *arg, const CMD_TBL_ENTRY *ce UATTRIB)
{
      DCC_SRVR_CLASS *class;
      int anum, srvrs_gen;
      DCC_AOPS aop;

      /* look for "clear" or "all" */
      anum = -1;
      aop = DCC_AOP_STATS;
      while (*arg != 0) {
            arg += strspn(arg, " \t");
            if (anum == -1
                && !CSTRCMP(arg, "clear")) {
                  arg += STRZ("clear");
                  aop = DCC_AOP_STATS_CLEAR;
                  if (!get_passwd(aop_tbl[aop].privileged)) {
                        dcc_error_msg("\"stats clear\""
                                    " is a privileged operation"
                                    PRV_MSG,
                                    DCC_NM2PATH(IDS_NM_DEF));
                        return 0;
                  }
            } else if (aop == DCC_AOP_STATS
                     && !CSTRCMP(arg, "all")) {
                  arg += STRZ("all");
                  anum = 0;
            }
            if (*arg != '\0' && *arg != ' ' && *arg != '\t')
                  return -1;
      }

      if (!init_conn(0))
            return 0;
      class = DCC_GREY2CLASS(grey_on);
      srvrs_gen = class->gen;
      do {
            if (srvrs_gen != class->gen) {
                  dcc_error_msg("list of servers changed");
                  return 0;
            }
            /* skip dead servers */
            if (class->addrs[anum].srvr_id == DCC_ID_INVALID
                && anum >= 0)
                  continue;

            do_aop(aop, sizeof(op_resp), anum, 1);
            fflush(stderr);
            fflush(stdout);
      } while (anum >= 0 && ++anum < class->num_addrs);

      return 1;
}



/* restore tracing to default */
static int
trace_def(const char *arg UATTRIB, const CMD_TBL_ENTRY *ce UATTRIB)
{
      if (!init_conn(0))
            return 0;

      return (do_aop(DCC_AOP_TRACE_ON, DCC_TRACE_ON_DEF_BITS, -1, 1)
            && do_aop(DCC_AOP_TRACE_OFF, DCC_TRACE_OFF_DEF_BITS, -1, 1));

}

Generated by  Doxygen 1.6.0   Back to index