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

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

  $Id: ctrl.c,v 1.18 2001/05/24 17:41:07 sqrt Exp $

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

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

  Copyright (C) 1999-2001  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 "ctrl.h"


extern volatile int got_shutdown;

extern struct configdesc cd[];

extern struct ctrl_cmd_dispatch_table ccdt[];


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

  init_config_vars()

*****************************************************************************/
void
init_config_vars()
{
  struct configdesc *c;

  for (c=cd; c->tag; c++) {
    switch (c->type) {
      case ctTime:	/* fall through */
      case ctBool:	/* fall through */
      case ctInt:
	*((int *)c->ptr) = c->def;
	break;
      case ctStr:	/* fall through */
      case ctId:	/* fall through */
      case ctFile:	/* fall through */
      case ctDir:
	strlcpy(c->ptr, c->defstr, c->max);
	break;
      case ctIgn:	/* fall through */
      case ctNone:
	/* ignored */
	break;
    }
  }
} 


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

  ctrl_cmd_show()

*****************************************************************************/
void
ctrl_cmd_show(char *argv[], int argc, char *answer)
{
  struct configdesc *c;

  /* if there is no argument print list of all config variables */
  if (argc == 1) {
    (void) strlcpy(answer, "00 vars:", MAXBUF);
    for (c=cd; c->tag; c++) {
      if (c->type == ctIgn) continue;
      (void) strlcat(answer, " ", MAXBUF);
      (void) strlcat(answer, c->tag, MAXBUF);
      if (! c->readwrite) (void) strlcat(answer, "(ro)", MAXBUF);
    }
    return;
  }

  for (c=cd; c->tag; c++) {
    if (strcasecmp(argv[1], c->tag)) continue;

    switch (c->type) {
      case ctTime:
	snprintf(answer, MAXBUF, "00 set %s \"%s\"", c->tag, sec2timedesc(*((int *)c->ptr)));
	break;
      case ctBool:	/* fall through */
      case ctInt:
	snprintf(answer, MAXBUF, "00 set %s \"%d\"", c->tag, *((int *)c->ptr));
	break;
      case ctDir:	/* fall through */
      case ctFile:	/* fall through */
      case ctId:	/* fall through */
      case ctStr:
	snprintf(answer, MAXBUF, "00 set %s \"%s\"", c->tag, (char *)c->ptr);
	break;
      case ctIgn:
	snprintf(answer, MAXBUF, "11 variable not supported: '%s'", argv[1]);
	break;
      case ctNone:	/* fall through */
      default:
	snprintf(answer, MAXBUF, "10 unknown variable: '%s'", argv[1]);
	break;
    }
    return;
  }

  snprintf(answer, MAXBUF, "10 unknown variable: '%s'", argv[1]);
}


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

  ctrl_cmd_set()

*****************************************************************************/
void
ctrl_cmd_set(char *argv[], int argc, char *answer)
{
  struct configdesc *c;
  int len;

  for (c=cd; c->tag; c++) {
    if (strcasecmp(argv[1], c->tag)) continue;

    if (!c->readwrite) {
      snprintf(answer, MAXBUF, "12 variable is read-only: '%s'", argv[1]);
      return;
    }

    switch (c->type) {
      case ctBool:
        if ( (!strcmp(argv[2], "0")) ||
	     (!strcmp(argv[2], "off")) ||
	     (!strcmp(argv[2], "false")) ||
	     (!strcmp(argv[2], "no")) ) {
          *((int *)c->ptr) = 0;
	} else if ( (!strcmp(argv[2], "1")) ||
	     (!strcmp(argv[2], "on")) ||
	     (!strcmp(argv[2], "true")) ||
	     (!strcmp(argv[2], "yes")) ) {
          *((int *)c->ptr) = 1;
	} else {
          ANSWER1("13 value for '%s' must be one of 0=no=off=false or 1=yes=on=true", argv[1]);
	}
        snprintf(answer, MAXBUF, "00 set %s \"%d\"", c->tag, *((int *)c->ptr));
	/* XLOG-DOC:ADM:0160:config_set
	 * A boolean config variable was set to a new value.
	 * (0=no=off=false, 1=yes=on=true) */
	xlog_printf(xlog_adm, 0x0160, "config_set var=%s value='%d'", c->tag, *((int *)c->ptr));
        break;
      case ctTime: {
	int v = timedesc2sec(argv[2], c->min, c->max);
        if (v < 0) {
          snprintf(answer, MAXBUF, "13 value for '%s' must be between %d and %d", argv[1], c->min, c->max);
          return;
        }
        *((int *)c->ptr) = v;
	snprintf(answer, MAXBUF, "00 set %s \"%s\"", c->tag, sec2timedesc(*((int *)c->ptr)));
	/* XLOG-DOC:ADM:017a:config_set
	 * A config variable was set to a new value. */
	xlog_printf(xlog_adm, 0x017a, "config_set var=%s value='%d'", c->tag, *((int *)c->ptr));
	break;
      }
      case ctInt: {
        int v = get_int(argv[2], c->min, c->max, -1, -1, -1);
        if (v < 0) {
          snprintf(answer, MAXBUF, "13 value for '%s' must be between %d and %d", argv[1], c->min, c->max);
          return;
        }
	if (!strcasecmp("maxsession", c->tag)) {
	  if (v <= *((int *)c->ptr)) ANSWER0("13 value for 'maxsession' can only increase");
	}
        *((int *)c->ptr) = v;
        snprintf(answer, MAXBUF, "00 set %s \"%d\"", c->tag, *((int *)c->ptr));
	/* XLOG-DOC:ADM:014b:config_set
	 * A config variable was set to a new value. */
	xlog_printf(xlog_adm, 0x014b, "config_set var=%s value='%d'", c->tag, *((int *)c->ptr));
        break;
      }
      case ctDir:
	{
	  struct stat sbuf;
	  if (stat(argv[2], &sbuf) != 0) ANSWER2("13 can't stat '%s': %s", argv[2], strerror(errno));
	  if (! S_ISDIR(sbuf.st_mode)) ANSWER1("13 not a directory '%s'", argv[2]);
	}
        if (access(argv[2], R_OK|X_OK) != 0) ANSWER2("13 dir '%s' is not accessible: %s", argv[2], strerror(errno));
        len = strlcpy(c->ptr, argv[2], c->max);
	if (len >= c->max) ANSWER1("13 value too long for '%s' (was truncated!)", c->tag);
        snprintf(answer, MAXBUF, "00 set %s \"%s\"", c->tag, (char *)c->ptr);
	/* XLOG-DOC:ADM:014d:config_set
	 * A config variable was set to a new value. */
	xlog_printf(xlog_adm, 0x014d, "config_set var=%s value='%s'", c->tag, (char *)c->ptr);
        break;
      case ctId:
        if (!valid_id(argv[2])) ANSWER0("13 invalid chars in id (must be [a-zA-Z0-9._-]*)");
	/* fall through */
      case ctFile:
	/* fall through */
      case ctStr:
        len = strlcpy(c->ptr, argv[2], c->max);
	if (len >= c->max) ANSWER1("13 value too long for '%s' (was truncated!)", c->tag);
        snprintf(answer, MAXBUF, "00 set %s \"%s\"", c->tag, (char *)c->ptr);
	/* XLOG-DOC:ADM:0149:config_set
	 * A config variable was set to a new value. */
	xlog_printf(xlog_adm, 0x0149, "config_set var=%s value='%s'", c->tag, (char *)c->ptr);
        break;
      case ctIgn:
	snprintf(answer, MAXBUF, "11 variable not supported: '%s'", argv[1]);
        break;
      case ctNone:
	/* fall through */
      default:
        snprintf(answer, MAXBUF, "10 unknown variable: '%s'", argv[1]);
        break;
    }
    return;
  }

  snprintf(answer, MAXBUF, "10 unknown variable: '%s'", argv[1]);
}


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

  ctrl_cmd_debug()

*****************************************************************************/
void
ctrl_cmd_debug(char *argv[], int argc, char *answer)
{
  int i, d=0;

  if (argc == 1) ANSWER1("00 debug =%s", print_debug_list(debug));

  switch (argv[1][0]) {
    case '-': /* fallthrough */
    case '+': /* fallthrough */
    case '=':
      break;
    default:
      ANSWER0("13 first arg must be '-', '+' or '='");
  }
  if (argv[1][1] != '\0') ANSWER0("13 first arg must be '-', '+' or '='");

  for (i=2; i < argc; i++) {
    struct debug_info *di = find_debug_info(-1, argv[i]);
    if (! di) ANSWER1("13 unknown debug type: '%s'", argv[i]);
    d |= di->type;
  }

  switch (argv[1][0]) {
    case '-':
      debug &= ~d;
      break;
    case '+':
      debug |= d;
      break;
    case '=':
      debug = d;
      break;
    default:
      ANSWER0("99 internal error (+-=)");
  }

  /* XLOG-DOC:INF:005e:debug_type
   * The debug options were changed through pcontrol. These are the new
   * options that are used from now on. */
  xlog_printf(xlog_inf, 0x005e, "debug_type%s", print_debug_list(debug));
  ANSWER1("00 debug =%s", print_debug_list(debug));
}


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

  ctrl_cmd_shutdown()

*****************************************************************************/
void
ctrl_cmd_shutdown(char *argv[], int argc, char *answer)
{
  if (! strcasecmp(argv[1], "all")) {
    /* XLOG-DOC:INF:005f:shutdown_all_received
     * The command 'shutdown all' was received from pcontrol. The server
     * will shut down all it's children and the exit itself. */
    xlog_printf(xlog_inf, 0x005f, "shutdown_all_received");
    got_shutdown = SHUTDOWN_CHILDREN | SHUTDOWN_SERVER;
  } else if (! strcasecmp(argv[1], "children")) {
    /* XLOG-DOC:INF:012b:shutdown_children_received
     * The command 'shutdown children' was received from pcontrol. The server
     * will shut down all it's children. */
    xlog_printf(xlog_inf, 0x012b, "shutdown_children_received");
    got_shutdown = SHUTDOWN_CHILDREN;
  } else if (! strcasecmp(argv[1], "parent")) {
    /* XLOG-DOC:INF:0060:shutdown_parent_received
     * The command 'shutdown parent' was received from pcontrol. The server
     * will shut down itself, but not its children. */
    xlog_printf(xlog_inf, 0x0060, "shutdown_parent_received");
    got_shutdown = SHUTDOWN_SERVER;
  } else if (! strcasecmp(argv[1], "delayed")) {
    /* XLOG-DOC:INF:012f:shutdown_delayed_received
     * The command 'shutdown delayed' was received from pcontrol. The server
     * will not accept new connections and shut down itself after the last
     * child has died. */
    xlog_printf(xlog_inf, 0x012f, "shutdown_delayed_received");
    got_shutdown = SHUTDOWN_DELAYED;
  } else {
    ANSWER1("22 unknown subcommand: '%s'", argv[1]);
  }
  (void) strlcpy(answer, "00 OK", MAXBUF);
}


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

  dispatch_control_request()

*****************************************************************************/
void
dispatch_control_request(char *request, char *answer)
{
  int i;
  int argc;
  char *argv[MAXARGS];

  argc = sep_args(request, argv, MAXARGS);
  if (argc == -1) ANSWER0("29 parse error");
  if (argc == -2) ANSWER0("20 too many arguments");
  if (argc < 0)   ANSWER0("99 internal error (sep_args)");

  for (i=0; ccdt[i].cmd_name; i++) {
    if (! strcasecmp(ccdt[i].cmd_name, argv[0])) {
      if (ccdt[i].minarg > argc - 1) {
	ANSWER0("20 not enough arguments");
      } else if (argc - 1 > ccdt[i].maxarg) {
	ANSWER0("20 too many arguments");
      } else {
        ccdt[i].cmd_handler(argv, argc, answer);
      }
      return;
    }
  }
  ANSWER1("21 unknown command: '%s'", argv[0]);
}


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

  control_command()

*****************************************************************************/
void
control_command(int sock)
{
  char request[MAXBUF], answer[MAXBUF];
  struct sockaddr_un from;
  int fromlen = sizeof(from);
  int len;

  DEBUG0(DG_CTRL, "control_command", "start");

  len = recvfrom(sock, request, sizeof(request)-1, 0, (struct sockaddr *) &from, &fromlen);

  if (len < 0) {
    /* XLOG-DOC:ADM:0061:pcontrol_recvfrom_failed
     * The call to recvfrom() to get a command from pcontrol failed. */
    xlog_printf(xlog_adm, 0x0061, "pcontrol_recvfrom_failed errno=%d errmsg='%s'", errno, strerror(errno));
    return;
  }

  request[len] = '\0';

  DEBUG1(DG_CTRL, "control_command", "got request: '%s'", request);
  dispatch_control_request(request, answer);
  DEBUG1(DG_CTRL, "control_command", "sending answer: '%s'", answer);

  /* Old linux versions don't have MSG_DONTWAIT. We work around this by
   * setting it to 0. This could theoretically result in a deadlock and
   * halt the server, but as it is really improbable, we keep going. */
#ifndef MSG_DONTWAIT
#define MSG_DONTWAIT 0
#warning MSG_DONTWAIT has been set to zero. Read comment in source for details.
#endif

  len = sendto(sock, answer, strlen(answer), MSG_DONTWAIT, (struct sockaddr *) &from, fromlen);
  if (len < 0) {
    /* XLOG-DOC:ADM:0062:pcontrol_sendto_failed
     * The call to sendto() to send an answer to pcontrol failed. */
    xlog_printf(xlog_adm, 0x0062, "pcontrol_sendto_failed errno=%d errmsg='%s'", errno, strerror(errno));
    return;
  }

  DEBUG0(DG_CTRL, "control_command", "end");
}


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


syntax highlighted by Code2HTML, v. 0.9.1