/*
* ProFTPD: mod_ctrls -- a module implementing the ftpdctl local socket
* server, as well as several utility functions for other Controls
* modules
*
* Copyright (c) 2000-2005 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.30 2005/11/11 21:05:32 castaglia Exp $
*/
#include "conf.h"
#include "privs.h"
#include "mod_ctrls.h"
#define MOD_CTRLS_VERSION "mod_ctrls/0.9.3"
/* 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(×tamp);
/* 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 writeable", 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 = auth_uid2name(cl->cl_pool, cl->cl_uid);
cl->cl_gid = cl_gid;
cl->cl_group = 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'");
if ((bad_action = ctrls_set_module_acls(ctrls_acttab, ctrls_pool, actions,
cmd->argv[2], cmd->argv[3], cmd->argv[4])) != NULL)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown action: '",
bad_action, "'", NULL));
return 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 HANDLED(cmd);
}
MODRET set_ctrlsengine(cmd_rec *cmd) {
int bool = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
ctrls_engine = bool;
return 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 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-writeable directory", NULL));
}
return HANDLED(cmd);
}
/* Default: 5 max clients */
MODRET set_ctrlsmaxclients(cmd_rec *cmd) {
int nclients = 0;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if ((nclients = atoi(cmd->argv[1])) <= 0)
CONF_ERROR(cmd, "must be a positive number");
cl_maxlistlen = nclients;
return 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. */
pr_log_debug(DEBUG3, MOD_CTRLS_VERSION ": closing ctrls socket '%s'",
ctrls_sock_file);
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 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 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 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));
}
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'",
ctrls_sock_file);
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
};
Last Updated: Thu Feb 23 11:07:03 2006
HTML generated by tj's src2html script