/*
 * ProFTPD: mod_ctrls -- a module implementing the ftpdctl local socket
 *          server, as well as several utility functions for other Controls
 *          modules
 *
 * Copyright (c) 2000-2006 TJ Saunders
 *
 * 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.
 *
 * As a special exemption, TJ Saunders and other respective copyright holders
 * give permission to link this program with OpenSSL, and distribute the
 * resulting executable, without including the source code for OpenSSL in the
 * source distribution.
 *
 * This is mod_ctrls, contrib software for proftpd 1.2 and above.
 * For more information contact TJ Saunders <tj@castaglia.org>.
 *
 * $Id: mod_ctrls.c,v 1.33 2006/06/22 19:48:35 jwm Exp $
 */

#include "conf.h"
#include "privs.h"
#include "mod_ctrls.h"

#define MOD_CTRLS_VERSION "mod_ctrls/0.9.4"

/* Master daemon in standalone mode? (from src/main.c) */
extern unsigned char is_master;

module ctrls_module;
static ctrls_acttab_t ctrls_acttab[];

/* Hard-coded Controls timer IDs.  Need two, one for the initial timer, one
 * to identify the user-configured-interval timer
 */
#define CTRLS_TIMER_ID       24075

static unsigned int ctrls_interval = 10;

/* Controls listening socket fd */
static int ctrls_sockfd = -1;

#define MOD_CTRLS_DEFAULT_SOCK		PR_RUN_DIR "/proftpd.sock"
static char *ctrls_sock_file = MOD_CTRLS_DEFAULT_SOCK;

/* User/group ownership of the control socket */
static uid_t ctrls_sock_uid = 0;
static gid_t ctrls_sock_gid = 0;

/* Pool for this module's use */
static pool *ctrls_pool = NULL;

/* Required "freshness" of client credential sockets */
static unsigned int ctrls_cl_freshness = 10;

/* Start of the client list */
static pr_ctrls_cl_t *cl_list = NULL;
static unsigned int cl_listlen = 0;
static unsigned int cl_maxlistlen = 5;

/* Controls access control list.  This is for ACLs on the control socket
 * itself, rather than on individual actions.
 */
static ctrls_acl_t ctrls_sock_acl;

static unsigned char ctrls_engine = TRUE;

/* Logging */
static int ctrls_logfd = -1;
static char *ctrls_logname = NULL;

/* Necessary prototypes */
static int ctrls_setblock(int sockfd);
static int ctrls_setnonblock(int sockfd);

/* Support routines
 */

char *ctrls_argsep(char **arg) {
  char *ret = NULL, *dst = NULL;
  char quote_mode = 0;

  if (!arg || !*arg || !**arg)
    return NULL;

  while (**arg && isspace((int) **arg))
    (*arg)++;

  if (!**arg)
    return NULL;

  ret = dst = *arg;

  if (**arg == '\"') {
    quote_mode++;
    (*arg)++;
  }

  while (**arg && **arg != ',' &&
      (quote_mode ? (**arg != '\"') : (!isspace((int) **arg)))) {

    if (**arg == '\\' && quote_mode) {

      /* escaped char */
      if (*((*arg) + 1))
        *dst = *(++(*arg));
    }

    *dst++ = **arg;
    ++(*arg);
  }

  if (**arg)
    (*arg)++;

  *dst = '\0';
  return ret;
}

/* Returns TRUE if the given cl_gid is allowed by the group ACL, FALSE
 * otherwise. Note that the default is to deny everyone, unless an ACL has
 * been configured. 
 */
static unsigned char ctrls_check_group_acl(gid_t cl_gid,
    const ctrls_grp_acl_t *grp_acl) {
  register int i = 0;
  unsigned char res = FALSE;

  /* Note: the special condition of ngids of 1 and gids of NULL signals
   * that all groups are to be treated according to the allow member.
   */
  if (grp_acl->gids) {
    for (i = 0; i < grp_acl->ngids; i++)
      if ((grp_acl->gids)[i] == cl_gid)
        res = TRUE;

  } else if (grp_acl->ngids == 1)
    res = TRUE;

  if (!grp_acl->allow)
    res = !res;

  return res;
}

/* Returns TRUE if the given cl_uid is allowed by the user ACL, FALSE
 * otherwise. Note that the default is to deny everyone, unless an ACL has
 * been configured.
 */
static unsigned char ctrls_check_user_acl(uid_t cl_uid,
    const ctrls_usr_acl_t *usr_acl) {
  register int i = 0;
  unsigned char res = FALSE;

  /* Note: the special condition of nuids of 1 and uids of NULL signals
   * that all users are to be treated according to the allow member.
   */
  if (usr_acl->uids) {
    for (i = 0; i < usr_acl->nuids; i++)
      if ((usr_acl->uids)[i] == cl_uid)
        res = TRUE;

  } else if (usr_acl->nuids == 1)
    res = TRUE;
 
  if (!usr_acl->allow)
    res = !res;

  return res;
}

/* Returns TRUE for allowed, FALSE for denied. */
unsigned char ctrls_check_acl(const pr_ctrls_t *ctrl,
    const ctrls_acttab_t *acttab, const char *action) {
  register unsigned int i = 0;

  for (i = 0; acttab[i].act_action; i++) {
    if (strcmp(acttab[i].act_action, action) == 0) {

      if (!ctrls_check_user_acl(ctrl->ctrls_cl->cl_uid,
            &(acttab[i].act_acl->acl_usrs)) &&
          !ctrls_check_group_acl(ctrl->ctrls_cl->cl_gid,
            &(acttab[i].act_acl->acl_grps))) {

        /* Access denied */
        return FALSE;
      }
    }
  }

  return TRUE;
}

void ctrls_init_acl(ctrls_acl_t *acl) {

  /* Sanity check */
  if (!acl)
    return;

  memset(acl, '\0', sizeof(ctrls_acl_t));
  acl->acl_usrs.allow = acl->acl_grps.allow = TRUE;
}

char **ctrls_parse_acl(pool *acl_pool, char *acl_str) {
  char *name = NULL, *acl_str_dup = NULL, **acl_list = NULL;
  array_header *acl_arr = NULL;
  pool *tmp_pool = NULL;

  /* Sanity checks */
  if (!acl_pool || !acl_str)
    return NULL;

  tmp_pool = make_sub_pool(acl_pool);
  acl_str_dup = pstrdup(tmp_pool, acl_str);
 
  /* Allocate an array */
  acl_arr = make_array(acl_pool, 0, sizeof(char **));

  /* Add each name to the array */
  while ((name = ctrls_argsep(&acl_str_dup)) != NULL) {
    char *tmp = pstrdup(acl_pool, name);

    /* Push the name into the ACL array */
    *((char **) push_array(acl_arr)) = tmp;
  }

  /* Terminate the temp array with a NULL, as is proper. */
  *((char **) push_array(acl_arr)) = NULL;

  acl_list = (char **) acl_arr->elts;
  destroy_pool(tmp_pool);

  /* return the array of names */
  return acl_list;
}

static void ctrls_set_group_acl(pool *grp_acl_pool, ctrls_grp_acl_t *grp_acl,
    const char *allow, char *grouplist) {
  char *group = NULL, **groups = NULL;
  array_header *gidarr = NULL;
  gid_t gid = 0;
  pool *tmp_pool = NULL;

  if (!grp_acl_pool || !grp_acl || !allow || !grouplist)
    return;

  tmp_pool = make_sub_pool(grp_acl_pool);

  if (strcmp(allow, "allow") == 0)
    grp_acl->allow = TRUE;
  else
    grp_acl->allow = FALSE;

  /* Parse the given expression into an array, then retrieve the GID
   * for each given name.
   */
  groups = ctrls_parse_acl(grp_acl_pool, grouplist);

  /* Allocate an array of gid_t's */
  gidarr = make_array(grp_acl_pool, 0, sizeof(gid_t));

  for (group = *groups; group != NULL; group = *++groups) {

    /* Handle a group name of "*" differently. */
    if (strcmp("*", group) == 0) {
      grp_acl->ngids = 1;
      grp_acl->gids = NULL;
      destroy_pool(tmp_pool);
      return;

    } else {
      gid = pr_auth_name2gid(tmp_pool, group);
      if (gid == (gid_t) -1)
        continue;
    }

    *((gid_t *) push_array(gidarr)) = gid;
  }

  grp_acl->ngids = gidarr->nelts;
  grp_acl->gids = (gid_t *) gidarr->elts;

  destroy_pool(tmp_pool);
}

static void ctrls_set_user_acl(pool *usr_acl_pool, ctrls_usr_acl_t *usr_acl,
    const char *allow, char *userlist) {
  char *user = NULL, **users = NULL;
  array_header *uidarr = NULL;
  uid_t uid = 0;
  pool *tmp_pool = NULL;

  /* Sanity checks */
  if (!usr_acl_pool || !usr_acl || !allow || !userlist)
    return;

  tmp_pool = make_sub_pool(usr_acl_pool);

  if (strcmp(allow, "allow") == 0)
    usr_acl->allow = TRUE;
  else
    usr_acl->allow = FALSE;

  /* Parse the given expression into an array, then retrieve the UID
   * for each given name.
   */
  users = ctrls_parse_acl(usr_acl_pool, userlist);

  /* Allocate an array of uid_t's */
  uidarr = make_array(usr_acl_pool, 0, sizeof(uid_t));

  for (user = *users; user != NULL; user = *++users) {

    /* Handle a user name of "*" differently. */
    if (strcmp("*", user) == 0) {
      usr_acl->nuids = 1;
      usr_acl->uids = NULL;
      destroy_pool(tmp_pool);
      return;

    } else {
      uid = pr_auth_name2uid(tmp_pool, user);
      if (uid == (uid_t) -1)
        continue;
    }

    *((uid_t *) push_array(uidarr)) = uid;
  }

  usr_acl->nuids = uidarr->nelts;
  usr_acl->uids = (uid_t *) uidarr->elts;

  destroy_pool(tmp_pool);
}

char *ctrls_set_module_acls(ctrls_acttab_t *acttab, pool *acl_pool,
    char **actions, const char *allow, const char *type, char *list) {
  register unsigned int i = 0;
  unsigned char all_actions = FALSE;

  /* First, sanity check the given list of actions against the actions
   * in the given table.
   */
  for (i = 0; actions[i]; i++) {
    register unsigned int j = 0;
    unsigned char valid_action = FALSE;

    if (strcmp(actions[i], "all") == 0)
      continue;

    for (j = 0; acttab[j].act_action; j++) {
      if (strcmp(actions[i], acttab[j].act_action) == 0) {
        valid_action = TRUE;
        break;
      }
    }

    if (!valid_action)
      return actions[i];
  }

  for (i = 0; actions[i]; i++) {
    register unsigned int j = 0;

    if (!all_actions && strcmp(actions[i], "all") == 0)
      all_actions = TRUE;

    for (j = 0; acttab[j].act_action; j++) {
      if (all_actions || strcmp(actions[i], acttab[j].act_action) == 0) {

        /* Use the type parameter to determine whether the list is of users or
         * of groups.
         */
        if (strcmp(type, "user") == 0)
          ctrls_set_user_acl(acl_pool, &(acttab[j].act_acl->acl_usrs),
            allow, list);

        else if (strcmp(type, "group") == 0)
          ctrls_set_group_acl(acl_pool, &(acttab[j].act_acl->acl_grps),
            allow, list);
      }
    }
  }

  return NULL;
}

char *ctrls_unregister_module_actions(ctrls_acttab_t *acttab,
    char **actions, module *mod) {
  register unsigned int i = 0;

  /* First, sanity check the given actions against the actions supported by
   * this module.
   */
  for (i = 0; actions[i]; i++) {
    register unsigned int j = 0;
    unsigned char valid_action = FALSE;

    for (j = 0; acttab[j].act_action; j++) {
      if (strcmp(actions[i], acttab[j].act_action) == 0) {
        valid_action = TRUE;
        break;
      }
    }

    if (!valid_action)
      return actions[i];
  }

  /* Next, iterate through both lists again, looking for actions of the
   * module _not_ in the given list.
   */
  for (i = 0; acttab[i].act_action; i++) {
    register unsigned int j = 0;
    unsigned char have_action = FALSE;

    for (j = 0; actions[j]; j++) {
      if (strcmp(acttab[i].act_action, actions[j]) == 0) {
        have_action = TRUE;
        break;
      }
    }

    if (have_action) {
      pr_log_debug(DEBUG4, "mod_%s.c: removing '%s' control", mod->name,
        acttab[i].act_action);
      pr_ctrls_unregister(mod, acttab[i].act_action);
      destroy_pool(acttab[i].act_acl->acl_pool);
    }
  }

  return NULL;
}

/* Controls logging routines
 */

static int ctrls_closelog(void) {
  /* sanity check */
  if (ctrls_logfd != -1) {
    close(ctrls_logfd);
    ctrls_logfd = -1;

    ctrls_logname = NULL;
  }

  return 0;
}

int ctrls_log(const char *module_version, const char *fmt, ...) {
  char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
  time_t timestamp = time(NULL);
  struct tm *t = NULL;
  va_list msg;

  /* sanity check */
  if (!ctrls_logname)
    return 0;

  t = localtime(&timestamp);

  /* prepend the timestamp */ 
  strftime(buf, sizeof(buf), "%b %d %H:%M:%S ", t);
  buf[sizeof(buf) - 1] = '\0';

  /* prepend a small header */
  snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
           "%s[%u]: ", module_version, (unsigned int) getpid());

  buf[sizeof(buf) - 1] = '\0';

  /* affix the message */
  va_start(msg, fmt);
  vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, msg);
  va_end(msg);
 
  buf[strlen(buf)] = '\n'; 
  buf[sizeof(buf) - 1] = '\0';
   
  while (write(ctrls_logfd, buf, strlen(buf)) < 0) {
    if (errno == EINTR) {
      pr_signals_handle();
      continue;
    }

    return -1;
  }

  return 0;
}

static int ctrls_openlog(void) {
  int res = 0;

  /* Sanity check */
  if (ctrls_logname == NULL)
    return 0;

  res = pr_log_openfile(ctrls_logname, &ctrls_logfd, 0640);

  if (res == -1) {
    pr_log_pri(PR_LOG_NOTICE, MOD_CTRLS_VERSION
      ": unable to open ControlsLog '%s': %s", ctrls_logname,
      strerror(errno));

  } else if (res == PR_LOG_WRITABLE_DIR) {
    pr_log_pri(PR_LOG_NOTICE, MOD_CTRLS_VERSION
      ": unable to open ControlsLog '%s': "
      "containing directory is world writable", ctrls_logname);

  } else if (res == PR_LOG_SYMLINK) {
    pr_log_pri(PR_LOG_NOTICE, MOD_CTRLS_VERSION
      ": unable to open ControlsLog '%s': %s is a symbolic link",
      ctrls_logname, ctrls_logname);
  }

  return res;
}

/* Controls client routines
 */

static pr_ctrls_cl_t *ctrls_new_cl(void) {
  pool *cl_pool = NULL;

  if (!cl_list) {

    /* Our first client */
    cl_pool = make_sub_pool(ctrls_pool);
    pr_pool_tag(cl_pool, "Controls client pool");

    cl_list = (pr_ctrls_cl_t *) pcalloc(cl_pool, sizeof(pr_ctrls_cl_t));

    cl_list->cl_pool = cl_pool;
    cl_list->cl_fd = -1;
    cl_list->cl_uid = 0;
    cl_list->cl_user = NULL;
    cl_list->cl_gid = 0;
    cl_list->cl_group = NULL;
    cl_list->cl_pid = 0;
    cl_list->cl_ctrls = make_array(cl_pool, 0, sizeof(pr_ctrls_t *));

    cl_list->cl_next = NULL;
    cl_list->cl_prev = NULL;

    cl_listlen = 1;

  } else {
    pr_ctrls_cl_t *cl = NULL;

    /* Add another victim to the list */
    cl_pool = make_sub_pool(ctrls_pool);
    pr_pool_tag(cl_pool, "Controls client pool");

    cl = (pr_ctrls_cl_t *) pcalloc(cl_pool, sizeof(pr_ctrls_cl_t));

    cl->cl_pool = cl_pool;
    cl->cl_fd = -1;
    cl->cl_uid = 0;
    cl->cl_user = NULL;
    cl->cl_gid = 0;
    cl->cl_group = NULL;
    cl->cl_pid = 0;
    cl->cl_ctrls = make_array(cl->cl_pool, 0, sizeof(pr_ctrls_t *));

    cl->cl_next = cl_list;
    cl->cl_prev = NULL;

    cl_list->cl_prev = cl;
    cl_list = cl;

    cl_listlen++;
  }

  return cl_list;
}

/* Add a new client to the set */
static pr_ctrls_cl_t *ctrls_add_cl(int cl_fd, uid_t cl_uid, gid_t cl_gid,
    pid_t cl_pid, unsigned long cl_flags) {
  pr_ctrls_cl_t *cl = NULL;

  /* Make sure there's an empty entry available */
  cl = ctrls_new_cl();

  cl->cl_fd = cl_fd;
  cl->cl_uid = cl_uid;
  cl->cl_user = pr_auth_uid2name(cl->cl_pool, cl->cl_uid);
  cl->cl_gid = cl_gid;
  cl->cl_group = pr_auth_gid2name(cl->cl_pool, cl->cl_gid);
  cl->cl_pid = cl_pid;
  cl->cl_flags = cl_flags;

  ctrls_log(MOD_CTRLS_VERSION,
    "accepted connection from %s/%s client", cl->cl_user, cl->cl_group);
 
  return cl;
}

/* Remove a client from the set */
static void ctrls_del_cl(pr_ctrls_cl_t *cl) {

  /* Remove this ctr_cl_t from the list, and free it */
  if (cl->cl_next)
    cl->cl_next->cl_prev = cl->cl_prev;

  if (cl->cl_prev)
    cl->cl_prev->cl_next = cl->cl_next;

  else
    cl_list = cl->cl_next;

  close(cl->cl_fd);
  cl->cl_fd = -1;

  destroy_pool(cl->cl_pool);

  cl_listlen--;

  return;
}

/* Controls socket routines
 */

/* Accept a client connection */
static int ctrls_accept(int sockfd, uid_t *uid, gid_t *gid, pid_t *pid) {
  pid_t cl_pid = 0;
  int cl_fd = -1;
  size_t len = 0;
  char *tmp = NULL;
  time_t stale_time;
  struct sockaddr_un sock;
  struct stat st;

  len = sizeof(sock);

  while ((cl_fd = accept(sockfd, (struct sockaddr *) &sock, &len)) < 0) {
    if (errno == EINTR) {
      pr_signals_handle();
      continue;
    }

    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to accept on local socket: %s", strerror(errno));

    return -1;
  }

  len -= sizeof(sock.sun_family);

  /* NULL terminate the name */
  sock.sun_path[len] = '\0';

  /* Check the path -- hmmm... */
  PRIVS_ROOT
  while (stat(sock.sun_path, &st) < 0) {
    if (errno == EINTR) {
      pr_signals_handle();
      continue;
    }

    PRIVS_RELINQUISH
    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to stat %s: %s", sock.sun_path, strerror(errno));
    (void) close(cl_fd);
    return -1;
  }
  PRIVS_RELINQUISH

  /* Is it a socket? */
  if (pr_ctrls_issock_unix(st.st_mode) < 0) {
    (void) close(cl_fd);
    errno = ENOTSOCK;
    return -1;
  }

  /* Are the perms _not_ rwx------? */
  if (st.st_mode & (S_IRWXG|S_IRWXO) ||
      ((st.st_mode & S_IRWXU) != PR_CTRLS_CL_MODE)) {
    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to accept connection: incorrect mode");
    (void) close(cl_fd);
    errno = EPERM;
    return -1;
  }

  /* Is it new enough? */
  stale_time = time(NULL) - ctrls_cl_freshness;

  if (st.st_atime < stale_time ||
      st.st_ctime < stale_time ||
      st.st_mtime < stale_time) {
    char *msg = "error: stale connection";

    ctrls_log(MOD_CTRLS_VERSION,
      "unable to accept connection: stale connection");

    /* Log the times being compared, to aid in debugging this situation. */
    if (st.st_atime < stale_time) {
      time_t age = stale_time - st.st_atime;

      ctrls_log(MOD_CTRLS_VERSION,
         "last access time of '%s' is %lu seconds old (must be less than %u)",
         sock.sun_path, (unsigned long) age, ctrls_cl_freshness);
    }

    if (st.st_ctime < stale_time) {
      time_t age = stale_time - st.st_ctime;

      ctrls_log(MOD_CTRLS_VERSION,
         "last change time of '%s' is %lu seconds old (must be less than %u)",
         sock.sun_path, (unsigned long) age, ctrls_cl_freshness);
    }

    if (st.st_mtime < stale_time) {
      time_t age = stale_time - st.st_mtime;

      ctrls_log(MOD_CTRLS_VERSION,
         "last modified time of '%s' is %lu seconds old (must be less than %u)",
         sock.sun_path, (unsigned long) age, ctrls_cl_freshness);
    }
 
    if (pr_ctrls_send_msg(cl_fd, -1, 1, &msg) < 0)
      ctrls_log("error sending message: %s", strerror(errno));
    close(cl_fd);
    cl_fd = -1;

    /* Done with the path now */
    PRIVS_ROOT
    unlink(sock.sun_path);
    PRIVS_RELINQUISH

    errno = ETIMEDOUT;
    return -1;
  }

  /* Parse the PID out of the path */
  tmp = sock.sun_path;
  tmp += strlen("/tmp/ftp.cl");
  cl_pid = atol(tmp);

  /* Return the IDs of the caller */
  if (uid)
    *uid = st.st_uid;

  if (gid)
    *gid = st.st_gid;

  if (pid)
    *pid = cl_pid;

  /* Done with the path now */
  PRIVS_ROOT
  unlink(sock.sun_path);
  PRIVS_RELINQUISH

  return cl_fd;
}

/* Iterate through any readable descriptors, reading each into appropriate
 * client objects
 */
static void ctrls_cls_read(void) {
  pr_ctrls_cl_t *cl = cl_list;

  while (cl) {
    if (pr_ctrls_recv_request(cl) < 0) {

      if (errno == EOF) {
        ;
 
      } else if (errno == EINVAL) {

        /* Unsupported action requested */
        if (!cl->cl_flags)
          cl->cl_flags = PR_CTRLS_CL_NOACTION;

        ctrls_log(MOD_CTRLS_VERSION,
          "recvd from %s/%s client: (invalid action)", cl->cl_user,
          cl->cl_group);

      } else if (errno == EAGAIN || errno == EWOULDBLOCK) {

        /* Malicious/blocked client */
        if (!cl->cl_flags)
          cl->cl_flags = PR_CTRLS_CL_BLOCKED;

      } else {
        
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to receive client request: %s", strerror(errno)); 
      }

    } else {
      pr_ctrls_t *ctrl = *((pr_ctrls_t **) cl->cl_ctrls->elts);
      char *request = (char *) ctrl->ctrls_action;

      /* Request successfully read.  Flag this client as being in such a
       * state.
       */
      if (!cl->cl_flags)
        cl->cl_flags = PR_CTRLS_CL_HAVEREQ;

      if (ctrl->ctrls_cb_args) {
        int reqargc = ctrl->ctrls_cb_args->nelts;
        char **reqargv = ctrl->ctrls_cb_args->elts;

        /* Reconstruct the original request string from the client for
         * logging.
         */
        while (reqargc--)
          request = pstrcat(cl->cl_pool, request, " ", *reqargv++, NULL);

        ctrls_log(MOD_CTRLS_VERSION,
          "recvd from %s/%s client: '%s'", cl->cl_user, cl->cl_group,
          request);
      }
    }

    cl = cl->cl_next;
  }

  return;
}

/* Iterate through any writable descriptors, writing out the responses to the
 * appropriate client objects
 */
static int ctrls_cls_write(void) {
  pr_ctrls_cl_t *cl = cl_list;

  while (cl) {
    /* Necessary to keep track of the next client in the list while
     * the list is being modified.
     */
    pr_ctrls_cl_t *tmpcl = cl->cl_next;

    /* This client has something to hear */
    if (cl->cl_flags == PR_CTRLS_CL_NOACCESS) {
      char *msg = "access denied";

      /* ACL-denied access */
      if (pr_ctrls_send_msg(cl->cl_fd, -1, 1, &msg) < 0)
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to send response to %s/%s client: %s",
          cl->cl_user, cl->cl_group, strerror(errno));

      else
        ctrls_log(MOD_CTRLS_VERSION, "sent to %s/%s client: '%s'",
          cl->cl_user, cl->cl_group, msg);

    } else if (cl->cl_flags == PR_CTRLS_CL_NOACTION) {
      char *msg = "unsupported action requested";

      /* Unsupported action -- no matching controls */
      if (pr_ctrls_send_msg(cl->cl_fd, -1, 1, &msg) < 0)
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to send response to %s/%s client: %s",
          cl->cl_user, cl->cl_group, strerror(errno));

      else
        ctrls_log(MOD_CTRLS_VERSION, "sent to %s/%s client: '%s'",
          cl->cl_user, cl->cl_group, msg);

    } else if (cl->cl_flags == PR_CTRLS_CL_BLOCKED) {
      char *msg = "blocked connection";

      if (pr_ctrls_send_msg(cl->cl_fd, -1, 1, &msg) < 0)
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to send response to %s/%s client: %s",
          cl->cl_user, cl->cl_group, strerror(errno));

      else
        ctrls_log(MOD_CTRLS_VERSION, "sent to %s/%s client: '%s'",
          cl->cl_user, cl->cl_group, msg);

    } else if (cl->cl_flags == PR_CTRLS_CL_HAVEREQ) {

      if (cl->cl_ctrls->nelts > 0) {
        register int i = 0;
        pr_ctrls_t **ctrlv = NULL;

        ctrlv = (pr_ctrls_t **) cl->cl_ctrls->elts;

        if (cl->cl_ctrls) {
          for (i = 0; i < cl->cl_ctrls->nelts; i++) {
            if ((ctrlv[i])->ctrls_cb_retval < 1) {

              /* Make sure the callback(s) added responses */
              if ((ctrlv[i])->ctrls_cb_resps) {
                if (pr_ctrls_send_msg(cl->cl_fd, (ctrlv[i])->ctrls_cb_retval,
                    (ctrlv[i])->ctrls_cb_resps->nelts,
                    (char **) (ctrlv[i])->ctrls_cb_resps->elts) < 0) {
                  ctrls_log(MOD_CTRLS_VERSION,
                    "error: unable to send response to %s/%s "
                    "client: %s", cl->cl_user, cl->cl_group, strerror(errno));

                } else {

                  /* For logging/accounting purposes */
                  register int j = 0;
                  int respval = (ctrlv[i])->ctrls_cb_retval;
                  int respargc = (ctrlv[i])->ctrls_cb_resps->nelts;
                  char **respargv = (ctrlv[i])->ctrls_cb_resps->elts;

                  ctrls_log(MOD_CTRLS_VERSION,
                    "sent to %s/%s client: return value: %d",
                    cl->cl_user, cl->cl_group, respval);

                  for (j = 0; j < respargc; j++) 
                    ctrls_log(MOD_CTRLS_VERSION,
                      "sent to %s/%s client: '%s'", cl->cl_user,
                      cl->cl_group, respargv[j]);
                }

              } else {

                /* No responses added by callbacks */
                ctrls_log(MOD_CTRLS_VERSION,
                  "notice: no responses given for %s/%s client: "
                  "check controls handlers", cl->cl_user, cl->cl_group);
              }
            }
          }
        }
      }
    }

    ctrls_log(MOD_CTRLS_VERSION,
      "closed connection to %s/%s client", cl->cl_user, cl->cl_group);

    /* Remove the client from the list */
    ctrls_del_cl(cl);
    cl = tmpcl;
  }

  return 0;
}

/* Create a listening local socket */
static int ctrls_listen(const char *sock_file) {
  int sockfd = -1, len = 0;
  struct sockaddr_un sock;

  /* No interruptions */
  pr_signals_block();

  /* Create the Unix domain socket */
  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
  if (sockfd < 0) {
    int xerrno = errno;

    pr_signals_unblock();
    errno = xerrno;
    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to create local socket: %s", strerror(errno));
    return -1;
  }

  /* Make sure the path to which we want to bind this socket doesn't already
   * exist.
   */
  unlink(sock_file);

  /* Fill in the socket structure fields */
  memset(&sock, 0, sizeof(sock));

  sock.sun_family = AF_UNIX;
  strncpy(sock.sun_path, sock_file, strlen(sock_file));

  len = sizeof(sock);

  /* Bind the name to the descriptor */
  pr_log_debug(DEBUG3, MOD_CTRLS_VERSION ": binding ctrls socket to '%s'",
    sock.sun_path);
  if (bind(sockfd, (struct sockaddr *) &sock, len) < 0) {
    int xerrno = errno;

    pr_signals_unblock();
    (void) close(sockfd);
    errno = xerrno;
    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to bind to local socket: %s", strerror(errno));
    return -1;
  }

  /* Start listening to the socket */
  if (listen(sockfd, 5) < 0) {
    int xerrno = errno;

    pr_signals_unblock();
    (void) close(sockfd);
    errno = xerrno;
    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to listen on local socket: %s", strerror(errno));
    return -1;
  }

  /* Change the permissions on the socket, so that users can connect */
  if (chmod(sock.sun_path, (mode_t) PR_CTRLS_MODE) < 0) {
    int xerrno = errno;

    pr_signals_unblock();
    (void) close(sockfd);
    errno = xerrno;
    ctrls_log(MOD_CTRLS_VERSION,
      "error: unable to chmod local socket: %s", strerror(errno));
    return -1;
  }

  pr_signals_unblock();
  return sockfd;
}

static int ctrls_recv_cl_reqs(void) {
  fd_set cl_rset;
  struct timeval timeout;
  uid_t cl_uid;
  gid_t cl_gid;
  pid_t cl_pid;
  unsigned long cl_flags = 0;
  int cl_fd, max_fd;

  timeout.tv_usec = 500L;
  timeout.tv_sec = 0L;

  /* look for any pending client connections */
  while (cl_listlen < cl_maxlistlen) {
    int res = 0;

    pr_signals_handle();

    if (ctrls_sockfd < 0)
      break;

    FD_ZERO(&cl_rset);
    FD_SET(ctrls_sockfd, &cl_rset);
    max_fd = ctrls_sockfd + 1;

    res = select(max_fd + 1, &cl_rset, NULL, NULL, &timeout);
    if (res == 0) {

      /* Go through the client list */
      ctrls_cls_read();

      return 0;
    }

    if (res < 0) {
      if (errno == EINTR) {
        pr_signals_handle();
        continue;
      }

      ctrls_log(MOD_CTRLS_VERSION,
        "error: unable to select on local socket: %s", strerror(errno));
      return res;
    }
 
    if (FD_ISSET(ctrls_sockfd, &cl_rset)) {

      /* Make sure the ctrl socket is non-blocking */
      if (ctrls_setnonblock(ctrls_sockfd) < 0) {
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to set nonblocking on local socket: %s",
          strerror(errno));
        return -1;
      }

      /* Accept pending connections */
      if ((cl_fd = ctrls_accept(ctrls_sockfd, &cl_uid, &cl_gid,
          &cl_pid)) < 0) {
        if (errno != ETIMEDOUT)
          ctrls_log(MOD_CTRLS_VERSION,
            "error: unable to accept connection: %s", strerror(errno));
        continue;
      }

      /* Restore blocking mode to the ctrl socket */
      if (ctrls_setblock(ctrls_sockfd) < 0) {
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to set blocking on local socket: %s",
          strerror(errno));
      }

      /* Set this socket as non-blocking */
      if (ctrls_setnonblock(cl_fd) < 0) {
        ctrls_log(MOD_CTRLS_VERSION,
          "error: unable to set nonblocking on client socket: %s",
          strerror(errno));
        continue;
      }

      if (!ctrls_check_user_acl(cl_uid, &ctrls_sock_acl.acl_usrs) &&
          !ctrls_check_group_acl(cl_gid, &ctrls_sock_acl.acl_grps))
        cl_flags = PR_CTRLS_CL_NOACCESS;

      /* Add the client to the list */
      ctrls_add_cl(cl_fd, cl_uid, cl_gid, cl_pid, cl_flags);
    }
  }

  /* Go through the client list */
  ctrls_cls_read();

  return 0; 
}

static int ctrls_send_cl_resps(void) {

  /* Go through the client list */
  ctrls_cls_write();

  return 0;
}

static int ctrls_setblock(int sockfd) {
  int flags = 0;
  int res = -1;

  /* default error */
  errno = EBADF;

  flags = fcntl(sockfd, F_GETFL);
  res = fcntl(sockfd, F_SETFL, flags & (U32BITS ^ O_NONBLOCK));

  return res;
}

static int ctrls_setnonblock(int sockfd) {
  int flags = 0;
  int res = -1;

  /* default error */
  errno = EBADF;

  flags = fcntl(sockfd, F_GETFL);
  res = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

  return res;
}

static int ctrls_timer_cb(CALLBACK_FRAME) {
  static unsigned char first = TRUE;

  /* If the ControlsEngine is not to run, do nothing from here on out */
  if (!ctrls_engine) {
    close(ctrls_sockfd);
    ctrls_sockfd = -1;

    if (is_master)
      /* Remove the local socket path as well */
      unlink(ctrls_sock_file);

    return 0;
  }

  if (first) {
    /* Change the ownership on the socket to that configured by the admin */
    PRIVS_ROOT
    if (chown(ctrls_sock_file, ctrls_sock_uid, ctrls_sock_gid) < 0)
      pr_log_pri(PR_LOG_INFO, "mod_ctrls: unable to chown local socket: %s",
        strerror(errno));
    PRIVS_RELINQUISH

    first = FALSE;
  }

  /* Please no alarms while doing this. */
  pr_alarms_block();

  /* Process pending requests. */
  ctrls_recv_cl_reqs();

  /* Run through the controls */
  pr_run_ctrls(NULL, NULL);

  /* Process pending responses */
  ctrls_send_cl_resps();

  /* Reset controls */
  pr_reset_ctrls();

  pr_alarms_unblock();
  return 1;
}

/* Controls handlers
 */

static int respcmp(const void *a, const void *b) {
  return strcmp(*((char **) a), *((char **) b));
}

static int ctrls_handle_help(pr_ctrls_t *ctrl, int reqargc,
    char **reqargv) {

  /* Run through the list of registered controls, and add them to the
   * response, including the module in which they appear.
   */

  if (reqargc != 0) {
    pr_ctrls_add_response(ctrl, "wrong number of parameters");
    return -1;
  }

  if (pr_get_registered_actions(ctrl, CTRLS_GET_DESC) < 0)
    pr_ctrls_add_response(ctrl, "unable to get actions: %s", strerror(errno));

  else {

    /* Be nice, and sort the directives lexicographically */
    qsort(ctrl->ctrls_cb_resps->elts, ctrl->ctrls_cb_resps->nelts,
      sizeof(char *), respcmp);
  }

  return 0;
}

static int ctrls_handle_insctrl(pr_ctrls_t *ctrl, int reqargc,
    char **reqargv) {
  module *m = ANY_MODULE;

  /* Enable a control into the registered controls list. This requires the
   * action and, optionally, the module of the control to be enabled.
   */

  /* Check the insctrl ACL */
  if (!ctrls_check_acl(ctrl, ctrls_acttab, "insctrl")) {

    /* Access denied */
    pr_ctrls_add_response(ctrl, "access denied");
    return -1;
  }

  if (reqargc < 1 || reqargc > 2) {
    pr_ctrls_add_response(ctrl, "wrong number of parameters");
    return -1;
  }

  /* If the optional second parameter, a module name, is used, lookup
   * the module pointer matching the name.
   */
  if (reqargc == 2)
    m = pr_module_get(reqargv[1]);

  if (pr_set_registered_actions(m, reqargv[0], FALSE, 0) < 0) {

    if (errno == ENOENT)
      pr_ctrls_add_response(ctrl, "no such control: '%s'", reqargv[0]);
    else
      pr_ctrls_add_response(ctrl, "unable to enable '%s': %s", reqargv[0],
        strerror(errno));

  } else
    pr_ctrls_add_response(ctrl, "'%s' control enabled", reqargv[0]);

  return 0;
}

static int ctrls_handle_lsctrl(pr_ctrls_t *ctrl, int reqargc,
    char **reqargv) {

  /* Run through the list of registered controls, and add them to the
   * response, including the module in which they appear.
   */

  /* Check the lsctrl ACL */
  if (!ctrls_check_acl(ctrl, ctrls_acttab, "lsctrl")) {

    /* Access denied */
    pr_ctrls_add_response(ctrl, "access denied");
    return -1;
  }

  if (reqargc != 0) {
    pr_ctrls_add_response(ctrl, "wrong number of parameters");
    return -1;
  }

  if (pr_get_registered_actions(ctrl, CTRLS_GET_ACTION_ENABLED) < 0)
    pr_ctrls_add_response(ctrl, "unable to get actions: %s", strerror(errno));

  else {

    /* Be nice, and sort the actions lexicographically */
    qsort(ctrl->ctrls_cb_resps->elts, ctrl->ctrls_cb_resps->nelts,
      sizeof(char *), respcmp);

  }

  return 0;
}

static int ctrls_handle_rmctrl(pr_ctrls_t *ctrl, int reqargc,
    char **reqargv) {
  module *m = ANY_MODULE;
  
  /* Disable a control from the registered controls list. This requires the
   * action and, optionally, the module of the control to be removed.
   */

  /* Check the rmctrl ACL */
  if (!ctrls_check_acl(ctrl, ctrls_acttab, "rmctrl")) {

    /* Access denied */
    pr_ctrls_add_response(ctrl, "access denied");
    return -1;
  }

  if (reqargc < 1 || reqargc > 2) {
    pr_ctrls_add_response(ctrl, "wrong number of parameters");
    return -1;
  }

  /* The three controls added by this module _cannot_ be removed (at least
   * not via this control handler).
   */
  if (strcmp(reqargv[0], "insctrl") == 0 ||
      strcmp(reqargv[0], "lsctrl") == 0 ||
      strcmp(reqargv[0], "rmctrl") == 0) {
    pr_ctrls_add_response(ctrl, "'%s' control cannot be removed", reqargv[0]);
    return -1;
  }

  /* If the optional second parameter, a module name, is used, lookup
   * the module pointer matching the name.
   */
  if (reqargc == 2)
    m = pr_module_get(reqargv[1]);

  if (pr_set_registered_actions(m, reqargv[0], FALSE,
      PR_CTRLS_ACT_DISABLED) < 0) {

    if (errno == ENOENT)
      pr_ctrls_add_response(ctrl, "no such control: '%s'", reqargv[0]);
    else
      pr_ctrls_add_response(ctrl, "unable to disable '%s': %s", reqargv[0],
        strerror(errno));

  } else {
    if (strcmp(reqargv[0], "all") != 0) 
      pr_ctrls_add_response(ctrl, "'%s' control disabled", reqargv[0]);

    else {

      /* If all actions have been disabled, stop listening on the local
       * socket, and turn off this module's engine.
       */
      pr_ctrls_add_response(ctrl, "all controls disabled");
      pr_ctrls_add_response(ctrl, "restart the daemon to re-enable controls");

      close(ctrls_sockfd);
      ctrls_sockfd = -1;

      ctrls_engine = FALSE;
    }
  }

  return 0;
}

/* Configuration handlers
 */

/* Default behavior is to deny everyone unless an ACL has been configured */
MODRET set_ctrlsacls(cmd_rec *cmd) {
  char *bad_action = NULL, **actions = NULL;

  CHECK_ARGS(cmd, 4);
  CHECK_CONF(cmd, CONF_ROOT);

  /* Parse the given string of actions into a char **.  Then iterate
   * through the acttab, checking to see if a given control is _not_ in
   * the list.  If not in the list, unregister that control.
   */

  /* We can cheat here, and use the ctrls_parse_acl() routine to
   * separate the given string...
   */
  actions = ctrls_parse_acl(cmd->tmp_pool, cmd->argv[1]);

  /* Check the second parameter to make sure it is "allow" or "deny" */
  if (strcmp(cmd->argv[2], "allow") != 0 &&
      strcmp(cmd->argv[2], "deny") != 0)
    CONF_ERROR(cmd, "second parameter must be 'allow' or 'deny'");

  /* Check the third parameter to make sure it is "user" or "group" */
  if (strcmp(cmd->argv[3], "user") != 0 &&
      strcmp(cmd->argv[3], "group") != 0)
    CONF_ERROR(cmd, "third parameter must be 'user' or 'group'");

  bad_action = ctrls_set_module_acls(ctrls_acttab, ctrls_pool, actions,
    cmd->argv[2], cmd->argv[3], cmd->argv[4]);
  if (bad_action != NULL)
    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown action: '",
      bad_action, "'", NULL));

  return PR_HANDLED(cmd);
}

/* default: 10 secs */
MODRET set_ctrlsauthfreshness(cmd_rec *cmd) {
  int freshness = 0;

  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT);

  freshness = atoi(cmd->argv[1]);
  if (freshness <= 0)
    CONF_ERROR(cmd, "must be a positive number");

  ctrls_cl_freshness = freshness;

  return PR_HANDLED(cmd);
}

MODRET set_ctrlsengine(cmd_rec *cmd) {
  int bool = -1;

  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT);

  bool = get_boolean(cmd, 1);
  if (bool == -1)
    CONF_ERROR(cmd, "expected Boolean parameter");

  ctrls_engine = bool;
  return PR_HANDLED(cmd);
}

/* default: 10 secs */
MODRET set_ctrlsinterval(cmd_rec *cmd) {
  int nsecs = 0;

  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT);

  if ((nsecs = atoi(cmd->argv[1])) <= 0)
    CONF_ERROR(cmd, "must be a positive number");

  /* Remove the existing timer, and re-install it with this new interval. */
  ctrls_interval = nsecs;

  pr_timer_remove(CTRLS_TIMER_ID, &ctrls_module);
  pr_timer_add(ctrls_interval, CTRLS_TIMER_ID, &ctrls_module, ctrls_timer_cb);

  return PR_HANDLED(cmd);
}

MODRET set_ctrlslog(cmd_rec *cmd) {
  int res = 0;

  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT);

  ctrls_logname = pstrdup(ctrls_pool, cmd->argv[1]);

  res = ctrls_openlog();
  if (res < 0) {
    if (res == -1)
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to open '",
        cmd->argv[1], "': ", strerror(errno), NULL));

    if (res == -2)
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
        "unable to log to a world-writable directory", NULL));
  }

  return PR_HANDLED(cmd);
}

/* Default: 5 max clients */
MODRET set_ctrlsmaxclients(cmd_rec *cmd) {
  int nclients = 0;

  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT);

  nclients = atoi(cmd->argv[1]);
  if (nclients <= 0)
    CONF_ERROR(cmd, "must be a positive number");

  cl_maxlistlen = nclients;
  return PR_HANDLED(cmd);
}

/* Default: var/run/proftpd.sock */
MODRET set_ctrlssocket(cmd_rec *cmd) {
  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT);

  if (*cmd->argv[1] != '/')
    CONF_ERROR(cmd, "must be an absolute path");

  /* Close the socket. */
  if (ctrls_sockfd >= 0) {
    pr_log_debug(DEBUG3, MOD_CTRLS_VERSION ": closing ctrls socket '%s' (%d)",
      ctrls_sock_file, ctrls_sockfd);
    close(ctrls_sockfd);
    ctrls_sockfd = -1;
  }

  /* Change the path. */
  if (strcmp(cmd->argv[1], ctrls_sock_file) != 0)
    ctrls_sock_file = pstrdup(ctrls_pool, cmd->argv[1]);

  return PR_HANDLED(cmd);
}

/* Default behavior is to deny everyone unless an ACL has been configured */
MODRET set_ctrlssocketacl(cmd_rec *cmd) {
  CHECK_ARGS(cmd, 3);
  CHECK_CONF(cmd, CONF_ROOT);

  ctrls_init_acl(&ctrls_sock_acl);

  /* Check the first argument to make sure it either "allow" or "deny" */
  if (strcmp(cmd->argv[1], "allow") != 0 &&
      strcmp(cmd->argv[1], "deny") != 0)
    CONF_ERROR(cmd, "first parameter must be either 'allow' or 'deny'");

  /* Check the second argument to see how to handle the directive */
  if (strcmp(cmd->argv[2], "user") == 0)
    ctrls_set_user_acl(ctrls_pool, &ctrls_sock_acl.acl_usrs, cmd->argv[1],
      cmd->argv[3]);

  else if (strcmp(cmd->argv[2], "group") == 0)
    ctrls_set_group_acl(ctrls_pool, &ctrls_sock_acl.acl_grps, cmd->argv[1],
      cmd->argv[3]);

  else
    CONF_ERROR(cmd, "second parameter must be either 'user' or 'group'");

  return PR_HANDLED(cmd);
}

/* Default: root root */
MODRET set_ctrlssocketowner(cmd_rec *cmd) {
  gid_t gid = 0;
  uid_t uid = 0;

  CHECK_ARGS(cmd, 2);
  CHECK_CONF(cmd, CONF_ROOT);

  uid = pr_auth_name2uid(cmd->tmp_pool, cmd->argv[1]);
  if (uid == (uid_t) -1) {
    if (errno != EINVAL)
      pr_log_debug(DEBUG0, "%s: %s has UID of -1", cmd->argv[0],
        cmd->argv[1]);

    else
      pr_log_debug(DEBUG0, "%s: no such user '%s'", cmd->argv[0],
        cmd->argv[1]);

  } else
    ctrls_sock_uid = uid;

  gid = pr_auth_name2gid(cmd->tmp_pool, cmd->argv[2]);
  if (gid == (gid_t) -1) {
    if (errno != EINVAL)
      pr_log_debug(DEBUG0, "%s: %s has GID of -1", cmd->argv[0],
        cmd->argv[2]);

    else
      pr_log_debug(DEBUG0, "%s: no such group '%s'", cmd->argv[0],
        cmd->argv[2]);

  } else
    ctrls_sock_gid = gid;

  return PR_HANDLED(cmd);
}

/* Event handlers
 */

static void ctrls_exit_ev(const void *event_data, void *user_data) {
  if (!is_master || !ctrls_engine)
    return;

  /* Close any connected clients */
  if (cl_list) {
    pr_ctrls_cl_t *cl = NULL;

    for (cl = cl_list; cl; cl = cl->cl_next) {
      close(cl->cl_fd);
      cl->cl_fd = -1;
    }
  }

  return;
}

static void ctrls_postparse_ev(const void *event_data, void *user_data) {

  /* Start listening on the ctrl socket */
  PRIVS_ROOT
  ctrls_sockfd = ctrls_listen(ctrls_sock_file);
  PRIVS_RELINQUISH
  if (ctrls_sockfd < 0) {
    pr_log_pri(PR_LOG_NOTICE, "notice: unable to listen to local socket: %s",
      strerror(errno));

  } else {
    /* Ensure that the listen socket used is not one of the major three
     * (stdin, stdout, or stderr).
     */
    if (ctrls_sockfd < 3) {
      if (dup2(ctrls_sockfd, 3) < 0) {
        pr_log_pri(PR_LOG_NOTICE, MOD_CTRLS_VERSION
          ": error duplicating listen socket: %s", strerror(errno));
        (void) close(ctrls_sockfd);
        ctrls_sockfd = -1;

      } else {
        (void) close(ctrls_sockfd);
        ctrls_sockfd = 3;
      }
    }
  }

}

static void ctrls_restart_ev(const void *event_data, void *user_data) {
  register unsigned int i;

  /* Block alarms while we're preparing for the restart. */
  pr_alarms_block();

  /* Close any connected clients */
  if (cl_list) {
    pr_ctrls_cl_t *cl = NULL;

    for (cl = cl_list; cl; cl = cl->cl_next) {
      close(cl->cl_fd);
      cl->cl_fd = -1;
    }
  }

  /* Reset the client list */
  cl_list = NULL;
  cl_listlen = 0;

  pr_log_debug(DEBUG3, MOD_CTRLS_VERSION ": closing ctrls socket '%s' (%d)",
    ctrls_sock_file, ctrls_sockfd);
  close(ctrls_sockfd);
  ctrls_sockfd = -1;

  ctrls_closelog();

  /* Clear the existing pool */
  if (ctrls_pool) {
    destroy_pool(ctrls_pool);

    ctrls_logname = NULL;
    ctrls_sock_file = MOD_CTRLS_DEFAULT_SOCK;
  }

  /* Allocate the pool for this module's use */
  ctrls_pool = make_sub_pool(permanent_pool);
  pr_pool_tag(ctrls_pool, MOD_CTRLS_VERSION);

  /* Register the control handlers */
  for (i = 0; ctrls_acttab[i].act_action; i++) {

    /* Allocate and initialize the ACL for this control. */
    ctrls_acttab[i].act_acl = pcalloc(ctrls_pool, sizeof(ctrls_acl_t));
    ctrls_init_acl(ctrls_acttab[i].act_acl);
  }

  pr_alarms_unblock();
  return;
}

static void ctrls_startup_ev(const void *event_data, void *user_data) {

  /* Start a timer for the checking/processing of the ctrl socket.  */
  pr_timer_remove(CTRLS_TIMER_ID, &ctrls_module);
  pr_timer_add(ctrls_interval, CTRLS_TIMER_ID, &ctrls_module, ctrls_timer_cb);
}

/* Initialization routines
 */

static int ctrls_init(void) {
  register unsigned int i = 0; 

  /* Allocate the pool for this module's use */
  ctrls_pool = make_sub_pool(permanent_pool);
  pr_pool_tag(ctrls_pool, MOD_CTRLS_VERSION);

  /* Register the control handlers */
  for (i = 0; ctrls_acttab[i].act_action; i++) {

    /* Allocate and initialize the ACL for this control. */
    ctrls_acttab[i].act_acl = pcalloc(ctrls_pool, sizeof(ctrls_acl_t));
    ctrls_init_acl(ctrls_acttab[i].act_acl);

    if (pr_ctrls_register(&ctrls_module, ctrls_acttab[i].act_action,
        ctrls_acttab[i].act_desc, ctrls_acttab[i].act_cb) < 0)
      pr_log_pri(PR_LOG_INFO, MOD_CTRLS_VERSION
        ": error registering '%s' control: %s",
        ctrls_acttab[i].act_action, strerror(errno));
  }

  /* Make certain the socket ACL is initialized. */
  memset(&ctrls_sock_acl, '\0', sizeof(ctrls_acl_t));
  ctrls_sock_acl.acl_usrs.allow = ctrls_sock_acl.acl_grps.allow = FALSE;

  /* Start listening on the ctrl socket */
  ctrls_sockfd = ctrls_listen(ctrls_sock_file);
  if (ctrls_sockfd < 0)
    pr_log_pri(PR_LOG_NOTICE, "notice: unable to listen to local socket: %s",
      strerror(errno));

  pr_event_register(&ctrls_module, "core.exit", ctrls_exit_ev, NULL);
  pr_event_register(&ctrls_module, "core.postparse", ctrls_postparse_ev, NULL);
  pr_event_register(&ctrls_module, "core.restart", ctrls_restart_ev, NULL);
  pr_event_register(&ctrls_module, "core.startup", ctrls_startup_ev, NULL);

  return 0;
}

static int ctrls_sess_init(void) {

  /* Children are not to listen for or handle control requests */
  ctrls_engine = FALSE;
  pr_timer_remove(CTRLS_TIMER_ID, &ctrls_module);

  pr_event_unregister(&ctrls_module, "core.exit", ctrls_exit_ev);
  pr_event_unregister(&ctrls_module, "core.restart", ctrls_restart_ev);

  /* Close the inherited socket */
  close(ctrls_sockfd);
  ctrls_sockfd = -1;
 
  return 0;
}

static ctrls_acttab_t ctrls_acttab[] = {
  { "help",	"describe all registered controls", NULL,
    ctrls_handle_help },
  { "insctrl",	"enable a disabled control", NULL, 
    ctrls_handle_insctrl },
  { "lsctrl",	"list all registered controls", NULL, 
    ctrls_handle_lsctrl },
  { "rmctrl",	"disable a registered control", NULL,
    ctrls_handle_rmctrl },
  { NULL, NULL, NULL, NULL }
};

/* Module API tables
 */

static conftable ctrls_conftab[] = {
  { "ControlsACLs",		set_ctrlsacls,		NULL },
  { "ControlsAuthFreshness",	set_ctrlsauthfreshness,	NULL },
  { "ControlsEngine",		set_ctrlsengine,	NULL },
  { "ControlsInterval",		set_ctrlsinterval,	NULL },
  { "ControlsLog",		set_ctrlslog,		NULL },
  { "ControlsMaxClients",	set_ctrlsmaxclients,	NULL },
  { "ControlsSocket",		set_ctrlssocket,	NULL },
  { "ControlsSocketACL",	set_ctrlssocketacl,	NULL },
  { "ControlsSocketOwner",	set_ctrlssocketowner,	NULL },
  { NULL }
};

module ctrls_module = {
  NULL, NULL,

  /* Module API version 2.0 */
  0x20,

  /* Module name */
  "ctrls",

  /* Module configuration handler table */
  ctrls_conftab,

  /* Module command handler table */
  NULL,

  /* Module authentication handler table */
  NULL,

  /* Module initialization function */
  ctrls_init,

  /* Session initialization function */
  ctrls_sess_init,

  /* Module version */
  MOD_CTRLS_VERSION
};
