/*****************************************************************************

  POPular -- A POP3 server and proxy for large mail systems

  $Id: pproxy_ctrl.c,v 1.30 2002/11/28 13:56:03 sqrt Exp $

  http://www.remote.org/jochen/mail/popular/

******************************************************************************

  Copyright (C) 1999-2002  Jochen Topf <jochen@remote.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA

*****************************************************************************/

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "popular.h"
#include "pproxy.h"
#include "ctrl.h"

extern struct capa *first_capa;

extern struct pdm_module *first_pdm_module;

extern struct pproxyconfig conf;

struct configdesc cd[] = {
  /* tag             rw  type    addr                  min          max  default value */
  { "allowsslv2",     1, ctBool, &conf.allowsslv2,       0,           1, 0, NULL },
  { "authtimeout",    1, ctTime, &conf.authtimeout,      0,       60*60, DEFAULT_TIMEOUT_AUTH,    NULL },
  { "backlog",        1, ctInt,  &conf.backlog,          5,        1024, DEFAULT_LISTEN_BACKLOG,  NULL },
  { "checkport",      1, ctInt,  &conf.checkport,        1,       65535, DEFAULT_PORT_CHECK,      NULL },
  { "checktimeout",   1, ctTime, &conf.checktimeout,     1,          60, DEFAULT_TIMEOUT_CHECK,   NULL },
  /* RFC1939 says the 'inactivity autologout timer' MUST be >= 10 mins */
  { "idletimeout",    1, ctTime, &conf.idletimeout,    600,    60*60*24, DEFAULT_TIMEOUT_IDLE,    NULL },
  { "maxlocalload",   1, ctInt,  &conf.maxlocalload,     0,        9999, 0, NULL },
  { "maxsession",     1, ctInt,  &conf.maxsession,       2, MAX_SESSION, DEFAULT_MAX_SESSION,     NULL },
  { "pid",            0, ctInt,  &conf.pid,              0,           0, 0,                       NULL },
  /* RFC1939 says the 'inactivity autologout timer' MUST be >= 10 mins */
  { "proxytimeout",   1, ctTime, &conf.proxytimeout,   600,    60*60*24, DEFAULT_TIMEOUT_PROXY,   NULL },
  { "sessionlimit",   0, ctInt,  &conf.sessionlimit,     0,           0, MAX_SESSION,             NULL },
  { "sessiontimeout", 1, ctTime, &conf.sessiontimeout,   0,    60*60*24, DEFAULT_TIMEOUT_SESSION, NULL },

  /* tag             rw  type    addr                strlength                   default value */
  { "capadir",        1, ctDir,  conf.capadir,    0, sizeof(conf.capadir),    0, PPROXY_CAPA_DIR },
  { "defaultns",      1, ctId,   conf.defaultns,  0, sizeof(conf.defaultns),  0, "" },
  { "fallback",       1, ctId,   conf.fallback,   0, sizeof(conf.fallback),   0, "" },
  { "id",             0, ctId,   conf.id,         0, sizeof(conf.id),         0, PPROXY_PRG_NAME },
  { "logfile",        1, ctFile, conf.logfile,    0, sizeof(conf.logfile),    0, PPROXY_LOG_FILE },
  { "pdmdir",         1, ctDir,  conf.pdmdir,     0, sizeof(conf.pdmdir),     0, PPROXY_PDM_DIR },
  { "rundir" ,        0, ctDir,  conf.rundir,     0, sizeof(conf.rundir),     0, RUN_DIR },
  { "sidprefix" ,     1, ctId,   conf.sidprefix,  0, sizeof(conf.sidprefix),  0, "" },
  { "tlsdir",         1, ctDir,  conf.tlsdir,     0, sizeof(conf.tlsdir),     0, PPROXY_TLS_DIR },
  { "version",        0, ctStr,  conf.version,    0, sizeof(conf.version),    0, VERSION },
  { NULL,             0, ctNone, NULL,            0, 0,                       0, NULL }
};

struct ctrl_cmd_dispatch_table ccdt[] = {
  { "backend",  ctrl_cmd_backend,  1, 11 },
  { "capa",     ctrl_cmd_capa,     1,  2 },
  { "debug",    ctrl_cmd_debug,    0, 10 },
  { "pdm",      ctrl_cmd_pdm,      1, 19 },
  { "set",      ctrl_cmd_set,      2,  2 },
  { "show",     ctrl_cmd_show,     0,  1 },
  { "shutdown", ctrl_cmd_shutdown, 1,  1 },
  { "vserv",    ctrl_cmd_vserv,    1, 22 },
  { "prng",     ctrl_cmd_prng,     1,  2 },
  { NULL,       NULL,              0,  0 }
};

extern volatile int got_shutdown;


/*****************************************************************************

  ctrl_cmd_backend()

*****************************************************************************/
void
ctrl_cmd_backend(char *argv[], int argc, char *answer)
{
  struct backend *old;
  int got_conf=0;

  if (argc >= 3 && !valid_id(argv[2])) ANSWER0("13 invalid chars in id (must be [a-zA-Z0-9._-]*)");

  if (!strcasecmp(argv[1], "conf")) {
    struct backend new;
    int n, got_host, port=-1;

    if ((argc < 5) || ((argc % 2) != 1)) ANSWER0("20 wrong argument count");
    old = get_backend(argv[2], 1);
    if (!old) ANSWER1("15 unknown backend and max backend number already reached: '%s'", argv[2]);

    /* if this backend name is new, intialize this backend with default values */
    if (old->state == bstFree) {
      int len;
      memset(&new, 0, sizeof(new));
      len = strlcpy(new.id, argv[2], sizeof(new.id));
      if (len >= sizeof(new.id)) ANSWER0("13 id too long");
      new.prot                    = ptPOP3;
      new.state                   = bstOffline;
      new.backend_addr.sin_family = AF_INET;
      got_host                    = 0;
      port                        = DEFAULT_PORT_POP3;
    } else {
      memcpy(&new, old, sizeof(new));
      got_host                    = 1;
    }

    for (n=3; n<argc; n+=2) {
      if (! strcasecmp(argv[n], "host")) {			/* host */
        struct hostent *host;
        host = gethostbyname(argv[n+1]);
        if (host == NULL || (host->h_addr_list)[0] == NULL)
	  ANSWER1("13 unknown host or IP number: '%s'", argv[n+1]);
        memcpy(&(new.backend_addr.sin_addr.s_addr), host->h_addr_list[0],
        	sizeof(new.backend_addr.sin_addr.s_addr));
        got_host = 1;
      } else if (! strcasecmp(argv[n], "port")) {		/* port */
        port = get_port(argv[n+1]);
        if (port < 0) ANSWER0("13 port must be valid TCP port");
        new.backend_addr.sin_port = htons(port);
      } else if (! strcasecmp(argv[n], "prot")) {		/* prot */
        struct protocol_info *pi = find_protocol_info(-1, argv[n+1]);
        if (! pi || ! pi->support_backend_side) ANSWER1("13 not a valid protocol: '%s'", argv[n+1]);
        new.prot = pi->type;
        port = pi->port;
      } else if (! strcasecmp(argv[n], "state")) {		/* state */
        struct backend_state_info *bsi = find_backend_state_info(-1, argv[n+1]);
        if (! bsi || bsi->type == bstFree)
	  ANSWER1("13 unknown state: '%s'", argv[n+1]);
        new.state = bsi->type;
      } else {
        ANSWER1("23 unknown keyword: '%s'", argv[n]);
      }
    }
    if (!got_host) ANSWER0("16 missing host option");
    if (!new.backend_addr.sin_port) new.backend_addr.sin_port = htons(port);
    memcpy(old, &new, sizeof(new));
    got_conf=1;

  } else if (!strcasecmp(argv[1], "show")) {
    if (argc != 3) ANSWER0("20 too many arguments");
    old = get_backend(argv[2], 0);
    if (! old) ANSWER1("10 unknown backend: '%s'", argv[2]);

  } else if (!strcasecmp(argv[1], "del")) {
    if (argc != 3) ANSWER0("20 too many arguments");
    old = get_backend(argv[2], 0);
    if (! old) ANSWER1("10 unknown backend: '%s'", argv[2]);
    old->state = bstFree;
    /* XLOG-DOC:ADM:0163:config_backend_del
     * A 'backend del' command was executed. */
    xlog_printf(xlog_adm, 0x0163, "config_backend_del backend=%s", old->id);
    ANSWER1("00 backend '%s' deleted", old->id);

  } else if (!strcasecmp(argv[1], "flush")) {
    if (argc != 2) ANSWER0("20 too many arguments");
    flush_backend();
    /* XLOG-DOC:ADM:0164:config_backend_flush
     * A 'backend flush' command was executed. */
    xlog_printf(xlog_adm, 0x0164, "config_backend_flush");
    ANSWER0("00 all backends flushed");

  } else if (!strcasecmp(argv[1], "list")) {
    ANSWER1("00 backends:%s", list_backend());

  } else {
    ANSWER1("22 unknown subcomand: '%s'", argv[1]);
  }


  {
    struct protocol_info *p = find_protocol_info(old->prot, NULL);
    struct backend_state_info *s = find_backend_state_info(old->state, NULL);
    if (! p) ANSWER0("99 internal error (protocol_info)");
    if (! s) ANSWER0("99 internal error (backend_state_info)");

    snprintf(answer, MAXBUF, "00 backend conf \"%s\" host \"%s\" port \"%d\" prot \"%s\" state \"%s\"",
	old->id,				/* backend id */
	print_ip(&(old->backend_addr), NULL),	/* IP address */
	ntohs(old->backend_addr.sin_port),	/* TCP port */
	p->name,				/* protocol name */
	s->name);				/* state */

    /* XLOG-DOC:ADM:0165:config_backend_conf
     * A 'backend conf' command was executed. */
    if (got_conf) xlog_printf(xlog_adm, 0x0165, "config_backend_conf id=%s host=%s port=%d prot=%s state=%s", old->id, print_ip(&(old->backend_addr), NULL), ntohs(old->backend_addr.sin_port), p->name, s->name);
  }
}


/*****************************************************************************

  capa_name()

*****************************************************************************/
char *
capa_name(struct virt_serv *vs)
{
  switch (vs->capa_type) {
    case capa_error:
      return "ERROR";
    case capa_none:
      return "NONE";
    case capa_default:
      return "DEFAULT";
    case capa_user:
      return vs->capa_ptr->name;
    default:
      return "ERROR";
  }
}


/*****************************************************************************

  starttls_name()

*****************************************************************************/
char *
starttls_name(struct virt_serv *vs)
{
  switch (vs->starttls_type) {
    case starttls_off:
      return "OFF";
    case starttls_optional:
      return "OPTIONAL";
    case starttls_force:
      return "FORCE";
    default:
      return "ERROR";
  }
}


/*****************************************************************************

  ctrl_cmd_vserv()

*****************************************************************************/
void
ctrl_cmd_vserv(char *argv[], int argc, char *answer)
{
  struct virt_serv *vs, *sip;
  int got_conf=0;
  int len;

  if (argc >= 3 && !valid_id(argv[2])) ANSWER0("13 invalid chars in id (must be [a-zA-Z0-9._-]*)");

  if (!strcasecmp(argv[1], "conf")) {
    struct virt_serv new;
    int n, got_host, port=-1;

    if ((argc < 5) || ((argc % 2) != 1)) ANSWER0("20 wrong argument count");
    vs = get_virt_serv(argv[2], 1);
    if (!vs) ANSWER1("15 unknown vserv and max vservs already reached: '%s'", argv[2]);

    /* if this vserv is new, intialize it with default values */
    if (vs->state == vsstFree) {
      memset(&new, 0, sizeof(new));
      len = strlcpy(new.id, argv[2], sizeof(new.id));
      if (len >= sizeof(new.id)) ANSWER0("13 id too long");
      new.prot                  = ptPOP3;
      new.state                 = vsstOffline;
      new.local_addr.sin_family = AF_INET;
      got_host                  = 0;
      port                      = DEFAULT_PORT_POP3;
      new.capa_type             = capa_error;
      new.capa_ptr              = NULL;
      new.starttls_type		= starttls_off;
    } else {
      memcpy(&new, vs, sizeof(new));
      got_host                  = 1;
    }

    for (n=3; n<argc; n+=2) {
      if (! strcasecmp(argv[n], "iface")) {			/* iface */
        struct hostent *host;
	if (new.state != vsstOffline) ANSWER0("17 iface can only be changed when in state OFFLINE");
        if (!strcasecmp(argv[n+1], "ANY")) {
          new.local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        } else {
          host = gethostbyname(argv[n+1]);
          if (host == NULL || (host->h_addr_list)[0] == NULL)
            ANSWER1("13 unknown host or IP number: '%s'", argv[n+1]);
          memcpy(&(new.local_addr.sin_addr.s_addr), host->h_addr_list[0],
        	sizeof(new.local_addr.sin_addr.s_addr));
        }
        got_host = 1;
      } else if (! strcasecmp(argv[n], "port")) {		/* port */
	if (new.state != vsstOffline) ANSWER0("17 port can only be changed when in state OFFLINE");
        port = get_port(argv[n+1]);
        if (port < 0) ANSWER0("13 port must be valid TCP port");
        new.local_addr.sin_port = htons(port);
      } else if (! strcasecmp(argv[n], "prot")) {		/* prot */
        struct protocol_info *pi = find_protocol_info(-1, argv[n+1]);
        if (! pi || ! pi->support_client_side) ANSWER1("13 not a valid protocol: '%s'", argv[n+1]);
        if (is_tls_protocol(pi->type) && new.starttls_type != starttls_off) {
          ANSWER0("25 not allowed with STARTTLS setting");
	}
        new.prot = pi->type;
        port = pi->port;
      } else if (! strcasecmp(argv[n], "capa")) {		/* capa */
        if (!strcasecmp(argv[n+1], "error")) {
	  new.capa_type = capa_error;
	  new.capa_ptr = NULL;
	} else if (!strcasecmp(argv[n+1], "none")) {
	  new.capa_type = capa_none;
	  new.capa_ptr = NULL;
	} else if (!strcasecmp(argv[n+1], "default")) {
	  new.capa_type = capa_default;
	  new.capa_ptr = NULL;
	} else {
	  struct capa *c;
	  int found=0;
	  for (c=first_capa; c; c=c->next) {
	    if (!strcasecmp(argv[n+1], c->name)) {
	      new.capa_type = capa_user;
	      new.capa_ptr = c;
	      found = 1;
	      break;
	    }
	  }
	  if (!found) ANSWER1("13 no capa '%s' found", argv[n+1]);
	}
      } else if (! strcasecmp(argv[n], "namespace")) {		/* namespace */
        if (!valid_id(argv[n+1])) ANSWER0("13 invalid chars in namespace (must be [a-zA-Z0-9._-]*)");
        len = strlcpy(new.namespace, argv[n+1], sizeof(new.namespace));
	if (len >= sizeof(new.namespace)) ANSWER0("13 namespace too long");
      } else if (! strcasecmp(argv[n], "bannerok")) {		/* banner */
        len = strlcpy(new.banner_ok, argv[n+1], sizeof(new.banner_ok));
	if (len >= sizeof(new.banner_ok)) ANSWER0("13 banner too long");
      } else if (! strcasecmp(argv[n], "bannererr")) {		/* banner */
        len = strlcpy(new.banner_err, argv[n+1], sizeof(new.banner_err));
	if (len >= sizeof(new.banner_err)) ANSWER0("13 banner too long");
      } else if (! strcasecmp(argv[n], "starttls")) {		/* starttls */
#if USE_TLS
	if (! strcasecmp(argv[n+1], "off")) {
	  new.starttls_type = starttls_off;
	} else if (! strcasecmp(argv[n+1], "optional")) {
	  if (is_tls_protocol(new.prot)) {
	    ANSWER0("24 not allowed with TLS protocol");
	  } else {
	    new.starttls_type = starttls_optional;
	  }
	} else if (! strcasecmp(argv[n+1], "force")) {
	  if (is_tls_protocol(new.prot)) {
	    ANSWER0("24 not allowed with TLS protocol");
	  } else {
	    new.starttls_type = starttls_force;
	  }
	} else {
	  ANSWER0("23 unknown keyword");
	}
#else
	ANSWER0("26 setting not allowed because TLS not compiled in");
#endif
      } else if (! strcasecmp(argv[n], "state")) {		/* state */
        struct virt_serv_state_info *ssi = find_virt_serv_state_info(-1, argv[n+1]);
        if (! ssi || ssi->type == vsstFree) ANSWER1("13 unknown state: '%s'", argv[n+1]);
        new.state = ssi->type;
      } else {
        ANSWER1("23 unknown keyword: '%s'", argv[n]);
      }
    }
    if (!got_host) ANSWER0("16 missing iface option");
    if (!new.local_addr.sin_port) new.local_addr.sin_port = htons(port);

    sip = find_virt_serv_by_ip(&new.local_addr);
    if (sip && sip != vs) ANSWER0("13 ip/port combination already used for another vserv");

    if (vs->state != new.state) {
      struct virt_serv_state_info *o = find_virt_serv_state_info(vs->state, NULL);
      struct virt_serv_state_info *n = find_virt_serv_state_info(new.state, NULL);
      if (o->listen && !n->listen) {		/* stop listening */
	(void) close(vs->fd);
	(void) close_listen_port(&(vs->local_addr));
      } else if (!o->listen && n->listen) {	/* start listening */
        new.fd = bind_and_listen(&(new.local_addr));
        if (new.fd < 0) ANSWER0("31 bind error");
      }
    }

    /* setup or destroy SSL context if needed */
#if USE_TLS
    {
      int need_tls = is_tls_protocol(new.prot) |
			(new.starttls_type != starttls_off);
      if (need_tls && new.ssl_ctx == NULL) {
	DEBUG0(DG_TLS, "ctrl_cmd_vserv", "Create TLS context because of changed config");
	if (! io_tls_vserv_setup(conf.tlsdir, conf.allowsslv2, &new)) {
	  ANSWER0("35 TLS error, see log file");
	}
      }
      if (!need_tls && new.ssl_ctx != NULL) {
	DEBUG0(DG_TLS, "ctrl_cmd_vserv", "Destroy TLS context because of changed config");
	io_tls_vserv_shutdown(&new);
      }
    }
#endif

    memcpy(vs, &new, sizeof(new));
    got_conf=1;

  } else if (!strcasecmp(argv[1], "show")) {
    if (argc != 3) ANSWER0("20 too many or too few arguments");
    vs = get_virt_serv(argv[2], 0);
    if (! vs) ANSWER1("13 unknown vserv: '%s'", argv[2]);

  } else if (!strcasecmp(argv[1], "del")) {
    if (argc != 3) ANSWER0("20 too many or too few arguments");
    vs = get_virt_serv(argv[2], 0);
    if (! vs) ANSWER1("13 unknown vserv: '%s'", argv[2]);
    if (vs->state != vsstOffline) close(vs->fd);
    vs->state = vsstFree;
    /* XLOG-DOC:ADM:0168:config_vserv_del
     * A 'vserv del' command was executed. */
    xlog_printf(xlog_adm, 0x0168, "config_vserv_del id=%s", vs->id);
    ANSWER1("00 virt_serv '%s' deleted", vs->id);

  } else if (!strcasecmp(argv[1], "flush")) {
    if (argc != 2) ANSWER0("20 too many arguments");
    flush_virt_serv(vsstFree);
    /* XLOG-DOC:ADM:0167:config_vserv_flush
     * A 'vserv flush' command was executed. */
    xlog_printf(xlog_adm, 0x0167, "config_vserv_flush");
    ANSWER0("00 all virtual servers flushed");

  } else if (!strcasecmp(argv[1], "list")) {
    if (argc != 2) ANSWER0("20 too many arguments");
    ANSWER1("00 vservs:%s", list_virt_serv());

  } else {
    ANSWER1("22 unknown subcomand: '%s'", argv[1]);
  }

  {
    struct protocol_info *p = find_protocol_info(vs->prot, NULL);
    struct virt_serv_state_info *s = find_virt_serv_state_info(vs->state, NULL);
    if (! p) ANSWER0("99 internal error (protocol_info)");
    if (! s) ANSWER0("99 internal error (virt_serv_state_info)");

    snprintf(answer, MAXBUF, "00 vserv conf \"%s\" iface \"%s\" port \"%d\" prot \"%s\" capa \"%s\" starttls \"%s\" state \"%s\" namespace \"%s\" bannerok \"%s\" bannererr \"%s\"",
	vs->id,					/* virtual server id */
	print_ip(&(vs->local_addr), NULL),	/* IP address */
	ntohs(vs->local_addr.sin_port),		/* TCP port */
	p->name,				/* protocol name */
	capa_name(vs),				/* capability list */
	starttls_name(vs),			/* starttls option */
	s->name,				/* state */
	vs->namespace,				/* namespace name */
	vs->banner_ok,				/* banner (+OK) */
	vs->banner_err);			/* banner (-ERR) */

     /* XLOG-DOC:ADM:0166:config_vserv_conf
      * A 'vserv conf' command was executed. */
     if (got_conf) xlog_printf(xlog_adm, 0x0166, "config_vserv_conf id=%s iface=%s port=%d prot=%s capa=%s starttls=%s state=%s namespace=%s bannerok='%s' bannererr='%s'",
	vs->id,					/* virtual server id */
	print_ip(&(vs->local_addr), NULL),	/* IP address */
	ntohs(vs->local_addr.sin_port),		/* TCP port */
	p->name,				/* protocol name */
	capa_name(vs),				/* capability list */
	starttls_name(vs),			/* starttls option */
	s->name,				/* state */
	vs->namespace,				/* namespace name */
	vs->banner_ok,				/* banner (+OK) */
	vs->banner_err);			/* banner (-ERR) */

  }
}


/*****************************************************************************

  ctrl_cmd_capa()

*****************************************************************************/
void
ctrl_cmd_capa(char *argv[], int argc, char *answer)
{
  if (! strcasecmp(argv[1], "list")) {
    struct capa *c;
    if (argc != 2) ANSWER0("20 too many arguments");
    strlcpy(answer, "00 capas: ", MAXBUF);
    for (c=first_capa; c; c=c->next) {
      strlcat(answer, c->name, MAXBUF);
      if (c->next) strlcat(answer, ", ", MAXBUF);
    }

  } else if (! strcasecmp(argv[1], "load")) {
    struct capa *c;
    int new = 0;

    if (argc != 3) ANSWER0("20 second argument (capa id) is missing");
    if (!valid_id(argv[2])) ANSWER0("13 invalid chars in id (must be [a-zA-Z0-9._-]*)");

    for (c=first_capa; c; c=c->next) {
      if (!strcmp(c->name, argv[2])) goto addtext;
    }

    c = malloc(sizeof(struct capa));
    if (! c) ANSWER0("32 out of memory");
    c->next = first_capa;
    first_capa = c;
    strlcpy(c->name, argv[2], MAXLEN_ID);
    new = 1;

addtext:
    {
      FILE *f;
      char buf[MAXBUF];
      char readbuf[MAXLEN_CAPA+1];
      int offset = 0;

      snprintf(buf, sizeof(buf), "%s/%s", conf.capadir, argv[2]);
      f = fopen(buf, "r");
      if (! f) {
	if (new) { /* if we created a new capa entry, del it after an error */
	  first_capa = c->next;
	  free(c);
	}
	ANSWER1("33 error opening '%s'", buf);
      }
      /* read in capa file converting LF to CRLF as we go */
      while (fgets(readbuf + offset, sizeof(readbuf) - offset - 2, f)) {
	int len = strlen(readbuf + offset);
	readbuf[offset+len-1] = '\r';
	readbuf[offset+len]   = '\n';
	readbuf[offset+len+1] = '\0';
	offset += len+1;
	if (offset >= sizeof(readbuf) - 2) {
	  if (new) { /* if we created a new capa entry, del it */
	    first_capa = c->next;
  	    free(c);
  	  }
          ANSWER1("13 capa '%s' too long to be loaded", argv[2]);
	}
      }
      fclose(f);
      strlcpy(c->text, readbuf, sizeof(c->text));
    }
    /* XLOG-DOC:ADM:0169:config_capa_load
     * A 'capa load' command was executed. */
    xlog_printf(xlog_adm, 0x0169, "config_capa_load id=%s", argv[2]);
    ANSWER1("00 capa '%s' loaded", argv[2]);

  } else if (! strcasecmp(argv[1], "del")) {
    struct capa *c, **d;
    if (argc != 3) ANSWER0("20 second argument (capa id) is missing");
    if (!valid_id(argv[2])) ANSWER0("13 invalid chars in id (must be [a-zA-Z0-9._-]*)");
    for (c=first_capa, d=&first_capa; c; d=&(c->next), c=c->next) {
      if (!strcmp(c->name, argv[2])) {
	if (find_virt_serv_by_capa(c)) ANSWER1("18 capa '%s' is in use", argv[2]);
	*d = c->next;
	free(c);
	/* XLOG-DOC:ADM:016a:config_capa_del
	 * A 'capa del' command was executed. */
	xlog_printf(xlog_adm, 0x016a, "config_capa_del id=%s", argv[2]);
	ANSWER1("00 capa '%s' deleted", argv[2]);
      }
    }
    ANSWER1("00 no capa '%s' found", argv[2]);

  } else if (! strcasecmp(argv[1], "show")) {
    struct capa *c;
    if (argc != 3) ANSWER0("20 second argument (capa id) is missing");
    if (!valid_id(argv[2])) ANSWER0("13 invalid chars in id (must be [a-zA-Z0-9._-]*)");
    for (c=first_capa; c; c=c->next) {
      if (!strcmp(c->name, argv[2])) {
	char *x;
	snprintf(answer, MAXBUF, "00 capa %s: %s", argv[2], c->text);
        for (x=answer; *x; x++) {
	  if (*x == '\r') *x=',';
	  if (*x == '\n') *x=' ';
	}
	answer[strlen(answer)-2] = '\0';
	return;
      }
    }
    ANSWER1("00 no capa '%s' found", argv[2]);

  } else {
    ANSWER1("22 unknown subcomand: '%s'", argv[1]);
  }
}


/*****************************************************************************

  ctrl_cmd_prng()

*****************************************************************************/
void
ctrl_cmd_prng(char *argv[], int argc, char *answer)
{
#ifndef USE_TLS
  ANSWER0("26 prng command only allowed when TLS is compiled in");
#else
  int bytes = ((argc == 3) ? atoi(argv[2]) : DEFAULT_RANDOM_BYTES);
  DEBUG2(DG_CTRL, "ctrl_cmd_prng", "seed prng filename=%s bytes=%d", argv[1], bytes);
  if (!io_tls_seed_prng(argv[1], bytes)) {
    /* XLOG-DOC:SOS:0203:tls_seed_failed
     * Initialization of the TLS pseudo random number generator failed.
     * This is serious because it could potentially mean that there is
     * no security at all. */
    xlog_printf(xlog_sos, 0x0203, "tls_seed_failed");
    ANSWER0("33 tls seeding failed");
  }
  ANSWER0("00 ok");
#endif
}


/*****************************************************************************

  pdm_find_module()

*****************************************************************************/
struct pdm_module *
pdm_find_module(char *id)
{
  struct pdm_module *m;
  for (m=first_pdm_module; m; m=m->next) {
    if (!strcmp(id, m->id)) return m;
  }
  return NULL;
}


/*****************************************************************************

  ctrl_cmd_pdm()

*****************************************************************************/
void
ctrl_cmd_pdm(char *argv[], int argc, char *answer)
{
  if (! strcasecmp(argv[1], "list")) {
    struct pdm_module *m;
    if (argc != 2) ANSWER0("20 too many arguments");
    strlcpy(answer, "00 modules: ", MAXBUF);
    for (m=first_pdm_module; m; m=m->next) {
      strlcat(answer, m->id, MAXBUF);
      if (m->next) strlcat(answer, ", ", MAXBUF);
    }
  } else if (! strcasecmp(argv[1], "add")) {
    struct pdm_module *m, *mo;
    if (argc < 4) ANSWER0("20 not enough arguments");
    m = pdm_find_module(argv[2]);
    if (m) ANSWER1("13 module '%s' already exists", argv[2]);
    m = pdm_attach(conf.pdmdir, argc-2, argv+2);
    if (! m) ANSWER1("40 loading of module libpdm_%s.so failed", argv[3]);
    if (first_pdm_module) {
      for (mo = first_pdm_module; mo->next; mo=mo->next);
      m->next = NULL;
      mo->next = m;
    } else {
      first_pdm_module = m;
    }
    /* XLOG-DOC:ADM:016b:config_pdm_add
     * A 'pdm add' command was executed. */
    xlog_printf(xlog_adm, 0x016b, "config_pdm_add id=%s", argv[2]);
    ANSWER0("00 module loaded");
  } else if (! strcasecmp(argv[1], "del")) {
    struct pdm_module **mp, *m;
    if (argc != 3) ANSWER0("20 wrong number of arguments");
    if (! first_pdm_module) {
      ANSWER1("15 module '%s' doesn't exist", argv[2]);
    } else if (! strcmp(first_pdm_module->id, argv[2])) {
      m = first_pdm_module;
      first_pdm_module = first_pdm_module->next;
    } else {
      for (mp = &first_pdm_module; (*mp)->next; mp=&((*mp)->next)) {
	DEBUG2(DG_CTRL, "ctrl_cmd_pdm", "list: %s %s", (*mp)->id, (*mp)->next->id);
	if (! strcmp((*mp)->next->id, argv[2])) {
	  m = (*mp)->next;
	  DEBUG2(DG_CTRL, "ctrl_cmd_pdm", "found '%s' (next is '%s')", m->id, m->next ? m->next->id : "NULL");
	  (*mp)->next = m->next;
	  pdm_detach(m);
	  ANSWER0("00 module deleted");
	}
      }
      ANSWER1("15 module '%s' doesn't exist", argv[2]);
    }
    pdm_detach(m);
    /* XLOG-DOC:ADM:016c:config_pdm_del
     * A 'pdm del' command was executed. */
    xlog_printf(xlog_adm, 0x016c, "config_pdm_del id=%s", argv[2]);
    ANSWER0("00 module deleted");
  } else if (! strcasecmp(argv[1], "reload")) {
    struct pdm_module *m;
    if (argc != 3) ANSWER0("20 wrong number of arguments");
    m = pdm_find_module(argv[2]);
    if (! m) ANSWER1("15 module '%s' doesn't exist", argv[2]);
    if (pdm_call_reload(m)) {
      /* XLOG-DOC:ADM:016d:config_pdm_reload
       * A 'pdm reload' command was executed. */
      xlog_printf(xlog_adm, 0x016d, "config_pdm_reload id=%s", argv[2]);
      ANSWER1("00 module '%s' reloaded", argv[2]);
    }
    ANSWER1("34 module '%s' reload failed", argv[2]);
  } else if (! strcasecmp(argv[1], "flush")) {
    struct pdm_module *m, *mf;
    if (argc != 2) ANSWER0("20 too many arguments");
    for (m=first_pdm_module; m; m=mf) {
      mf = m->next;
      pdm_detach(m);
    }
    first_pdm_module = NULL;
    /* XLOG-DOC:ADM:016e:config_pdm_flush
     * A 'pdm flush' command was executed. */
    xlog_printf(xlog_adm, 0x016e, "config_pdm_flush");
    ANSWER0("00 all modules unloaded");
  } else if (! strcasecmp(argv[1], "show")) {
    struct pdm_module *m;
    if (argc != 3) ANSWER0("20 wrong number of arguments");
    m = pdm_find_module(argv[2]);
    if (! m) ANSWER1("15 module '%s' doesn't exist", argv[2]);
    ANSWER2("00 module add %s %s", argv[2], pdm_get_args(m));
  } else{
    ANSWER1("22 unknown subcomand: '%s'", argv[1]);
  }
}


/** THE END *****************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1