/*
* ProFTPD - FTP server daemon
* Copyright (c) 2001, 2002, 2003 The ProFTPD Project team
*
* 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, The ProFTPD Project team 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.
*/
/* Controls API routines
*
* $Id: ctrls.c,v 1.13 2005/11/11 21:05:32 castaglia Exp $
*/
#include "conf.h"
#ifdef PR_USE_CTRLS
typedef struct ctrls_act_obj {
struct ctrls_act_obj *prev, *next;
pool *pool;
unsigned int id;
const char *action;
const char *desc;
const module *module;
volatile unsigned int flags;
int (*action_cb)(pr_ctrls_t *, int, char **);
} ctrls_action_t;
static unsigned char ctrls_blocked = FALSE;
static pool *ctrls_pool = NULL;
static ctrls_action_t *ctrls_action_list = NULL;
static pr_ctrls_t *ctrls_active_list = NULL;
static pr_ctrls_t *ctrls_free_list = NULL;
static int ctrls_use_isfifo = FALSE;
/* lookup/lookup_next indices */
static ctrls_action_t *action_lookup_next = NULL;
static const char *action_lookup_action = NULL;
static module *action_lookup_module = NULL;
/* necessary prototypes */
static ctrls_action_t *ctrls_action_new(void);
static pr_ctrls_t *ctrls_new(void);
static pr_ctrls_t *ctrls_lookup_action(module *, const char *, unsigned char);
static pr_ctrls_t *ctrls_lookup_next_action(module *, unsigned char);
static pr_ctrls_t *ctrls_prepare(ctrls_action_t *act) {
pr_ctrls_t *ctrl = NULL;
/* Sanity check */
if (!act)
return NULL;
pr_block_ctrls();
/* Get a blank ctrl object */
ctrl = ctrls_new();
/* Fill in the fields from the action object. */
ctrl->ctrls_id = act->id;
ctrl->ctrls_module = act->module;
ctrl->ctrls_action = act->action;
ctrl->ctrls_desc = act->desc;
ctrl->ctrls_cb = act->action_cb;
ctrl->ctrls_flags = act->flags;
/* Add this to the "in use" list */
ctrl->ctrls_next = ctrls_active_list;
ctrls_active_list = ctrl;
pr_unblock_ctrls();
return ctrl;
}
static void ctrls_free(pr_ctrls_t *ctrl) {
/* Make sure that ctrls are blocked while we're doing this */
pr_block_ctrls();
/* Remove this object from the active list */
if (ctrl->ctrls_prev)
ctrl->ctrls_prev->ctrls_next = ctrl->ctrls_next;
else
ctrls_active_list = ctrl->ctrls_next;
if (ctrl->ctrls_next)
ctrl->ctrls_next->ctrls_prev = ctrl->ctrls_prev;
/* Clear its fields, and add it to the free list */
ctrl->ctrls_next = NULL;
ctrl->ctrls_prev = NULL;
ctrl->ctrls_id = 0;
ctrl->ctrls_module = NULL;
ctrl->ctrls_action = NULL;
ctrl->ctrls_cb = NULL;
ctrl->ctrls_cb_retval = 1;
ctrl->ctrls_flags = 0;
if (ctrl->ctrls_tmp_pool) {
destroy_pool(ctrl->ctrls_tmp_pool);
ctrl->ctrls_tmp_pool = NULL;
}
ctrl->ctrls_cb_args = NULL;
ctrl->ctrls_cb_resps = NULL;
ctrl->ctrls_data = NULL;
ctrl->ctrls_next = ctrls_free_list;
ctrls_free_list = ctrl;
pr_unblock_ctrls();
return;
}
static ctrls_action_t *ctrls_action_new(void) {
ctrls_action_t *act = NULL;
pool *sub_pool = make_sub_pool(ctrls_pool);
pr_pool_tag(sub_pool, "ctrls action subpool");
act = pcalloc(sub_pool, sizeof(ctrls_action_t));
act->pool = sub_pool;
return act;
}
static pr_ctrls_t *ctrls_new(void) {
pr_ctrls_t *ctrl = NULL;
/* Check for a free ctrl first */
if (ctrls_free_list) {
/* Take one from the top */
ctrl = ctrls_free_list;
ctrls_free_list = ctrls_free_list->ctrls_next;
if (ctrls_free_list)
ctrls_free_list->ctrls_prev = NULL;
} else {
/* Have to allocate a new one. */
ctrl = (pr_ctrls_t *) pcalloc(ctrls_pool, sizeof(pr_ctrls_t));
/* It's important that a new ctrl object have the retval initialized
* to 1; this tells the Controls layer that it is "pending", not yet
* handled.
*/
ctrl->ctrls_cb_retval = 1;
}
return ctrl;
}
static char *ctrls_sep(char **str) {
char *ret = NULL, *dst = NULL;
unsigned char quoted = FALSE;
/* Sanity checks */
if (!str || !*str || !**str)
return NULL;
while (**str && isspace((int) **str))
(*str)++;
if (!**str)
return NULL;
ret = dst = *str;
if (**str == '\"') {
quoted = TRUE;
(*str)++;
}
while (**str &&
(quoted ? (**str != '\"') : !isspace((int) **str))) {
if (**str == '\\' && quoted) {
/* Escaped char */
if (*((*str) + 1))
*dst = *(++(*str));
}
*dst++ = **str;
++(*str);
}
if (**str)
(*str)++;
*dst = '\0';
return ret;
}
int pr_ctrls_register(const module *mod, const char *action,
const char *desc, int (*cb)(pr_ctrls_t *, int, char **)) {
ctrls_action_t *act = NULL, *acti = NULL;
int act_id = -1;
/* sanity checks */
if (!action || !desc || !cb) {
errno = EINVAL;
return -1;
}
/* Block ctrls while we're doing this */
pr_block_ctrls();
/* Get a ctrl action object */
act = ctrls_action_new();
/* Randomly generate a unique random ID for this object */
while (TRUE) {
unsigned char have_id = FALSE;
act_id = rand();
/* Check the list for this ID */
for (acti = ctrls_action_list; acti; acti = acti->next) {
if (acti->id == act_id) {
have_id = TRUE;
break;
}
}
if (!have_id)
break;
}
act->next = NULL;
act->id = act_id;
act->action = action;
act->desc = desc;
act->module = mod;
act->action_cb = cb;
/* Add this to the list of "registered" actions */
if (ctrls_action_list) {
act->next = ctrls_action_list;
ctrls_action_list->prev = act;
}
ctrls_action_list = act;
pr_unblock_ctrls();
return act_id;
}
int pr_ctrls_unregister(module *mod, const char *action) {
ctrls_action_t *act = NULL;
unsigned char have_action = FALSE;
/* sanity checks */
if (!action) {
errno = EINVAL;
return -1;
}
/* Make sure that ctrls are blocked while we're doing this */
pr_block_ctrls();
for (act = ctrls_action_list; act; act = act->next) {
if (strcmp(act->action, action) == 0 &&
(act->module == mod || mod == ANY_MODULE || mod == NULL)) {
have_action = TRUE;
/* Remove this object from the list of registered actions */
if (act->prev)
act->prev->next = act->next;
else
ctrls_action_list = act->next;
if (act->next)
act->next->prev = act->prev;
/* Destroy this action. */
destroy_pool(act->pool);
}
}
pr_unblock_ctrls();
if (!have_action) {
errno = ENOENT;
return -1;
}
return 0;
}
int pr_ctrls_add_arg(pr_ctrls_t *ctrl, char *ctrls_arg) {
/* Sanity checks */
if (!ctrl || !ctrls_arg) {
errno = EINVAL;
return -1;
}
/* Make sure the pr_ctrls_t has a temporary pool, from which the args will
* be allocated.
*/
if (!ctrl->ctrls_tmp_pool) {
ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool");
}
if (!ctrl->ctrls_cb_args)
ctrl->ctrls_cb_args = make_array(ctrl->ctrls_tmp_pool, 0, sizeof(char *));
/* Add the given argument */
*((char **) push_array(ctrl->ctrls_cb_args)) = pstrdup(ctrl->ctrls_tmp_pool,
ctrls_arg);
return 0;
}
int pr_ctrls_copy_args(pr_ctrls_t *src_ctrl, pr_ctrls_t *dst_ctrl) {
/* Sanity checks */
if (!src_ctrl || !dst_ctrl) {
errno = EINVAL;
return -1;
}
/* If source ctrl has no ctrls_cb_args member, there's nothing to be
* done.
*/
if (!src_ctrl->ctrls_cb_args)
return 0;
/* Make sure the pr_ctrls_t has a temporary pool, from which the args will
* be allocated.
*/
if (!dst_ctrl->ctrls_tmp_pool) {
dst_ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
pr_pool_tag(dst_ctrl->ctrls_tmp_pool, "ctrls tmp pool");
}
/* Overwrite any existing dst_ctrl->ctrls_cb_args. This is OK, as
* the ctrl will be reset (cleared) once it has been processed.
*/
dst_ctrl->ctrls_cb_args = copy_array(dst_ctrl->ctrls_tmp_pool,
src_ctrl->ctrls_cb_args);
return 0;
}
int pr_ctrls_copy_resps(pr_ctrls_t *src_ctrl, pr_ctrls_t *dst_ctrl) {
/* sanity checks */
if (!src_ctrl || !dst_ctrl) {
errno = EINVAL;
return -1;
}
/* The source ctrl must have a ctrls_cb_resps member, and the destination
* ctrl must not have a ctrls_cb_resps member.
*/
if (!src_ctrl->ctrls_cb_resps || dst_ctrl->ctrls_cb_resps) {
errno = EINVAL;
return -1;
}
dst_ctrl->ctrls_cb_resps = copy_array(dst_ctrl->ctrls_tmp_pool,
src_ctrl->ctrls_cb_resps);
return 0;
}
int pr_ctrls_add_response(pr_ctrls_t *ctrl, char *fmt, ...) {
char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
va_list resp;
/* Sanity check */
if (!ctrl || !fmt) {
errno = EINVAL;
return -1;
}
/* Make sure the pr_ctrls_t has a temporary pool, from which the responses
* will be allocated
*/
if (!ctrl->ctrls_tmp_pool) {
ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool");
}
if (!ctrl->ctrls_cb_resps)
ctrl->ctrls_cb_resps = make_array(ctrl->ctrls_tmp_pool, 0,
sizeof(char *));
/* Affix the message */
va_start(resp, fmt);
vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, resp);
va_end(resp);
buf[sizeof(buf) - 1] = '\0';
/* add the given response */
*((char **) push_array(ctrl->ctrls_cb_resps)) =
pstrdup(ctrl->ctrls_tmp_pool, buf);
return 0;
}
int pr_ctrls_flush_response(pr_ctrls_t *ctrl) {
/* Sanity check */
if (!ctrl) {
errno = EINVAL;
return -1;
}
/* Make sure the callback(s) added responses */
if (ctrl->ctrls_cb_resps) {
if (pr_ctrls_send_msg(ctrl->ctrls_cl->cl_fd, ctrl->ctrls_cb_retval,
ctrl->ctrls_cb_resps->nelts,
(char **) ctrl->ctrls_cb_resps->elts) < 0)
return -1;
}
return 0;
}
int pr_ctrls_parse_msg(pool *msg_pool, char *msg, unsigned int *msgargc,
char ***msgargv) {
char *tmp = msg, *str = NULL;
pool *tmp_pool = NULL;
array_header *msgs = NULL;
/* Sanity checks */
if (!msg_pool || !msgargc || !msgargv) {
errno = EINVAL;
return -1;
}
tmp_pool = make_sub_pool(msg_pool);
/* Allocate an array_header, and push each space-delimited string
* (respecting quotes and escapes) into the array. Once done,
* destroy the array.
*/
msgs = make_array(tmp_pool, 0, sizeof(char *));
while ((str = ctrls_sep(&tmp)) != NULL)
*((char **) push_array(msgs)) = pstrdup(msg_pool, str);
*msgargc = msgs->nelts;
*msgargv = (char **) msgs->elts;
destroy_pool(tmp_pool);
return 0;
}
int pr_ctrls_recv_request(pr_ctrls_cl_t *cl) {
pr_ctrls_t *ctrl = NULL, *next_ctrl = NULL;
char reqaction[512] = {'\0'}, *reqarg = NULL;
size_t reqargsz = 0;
unsigned int nreqargs = 0, reqarglen = 0;
int status = 0;
register int i = 0;
if (!cl) {
errno = EINVAL;
return -1;
}
if (cl->cl_fd < 0) {
errno = EBADF;
return -1;
}
/* No interruptions */
pr_signals_block();
/* Read in the incoming number of args, including the action. */
/* First, read the status (but ignore it). This is necessary because
* the same function, pr_ctrls_send_msg(), is used to send requests
* as well as responses, and the status is a necessary part of a response.
*/
if (read(cl->cl_fd, &status, sizeof(int)) < 0) {
pr_signals_unblock();
return -1;
}
/* Read in the args, length first, then string. */
if (read(cl->cl_fd, &nreqargs, sizeof(unsigned int)) < 0) {
pr_signals_unblock();
return -1;
}
/* Next, read in the requested number of arguments. The client sends
* the arguments in pairs: first the length of the argument, then the
* argument itself. The first argument is the action, so get the first
* matching pr_ctrls_t (if present), and add the remaining arguments to it.
*/
if (read(cl->cl_fd, &reqarglen, sizeof(unsigned int)) < 0) {
pr_signals_unblock();
return -1;
}
if (read(cl->cl_fd, reqaction, reqarglen) < 0) {
pr_signals_unblock();
return -1;
}
nreqargs--;
/* Find a matching action object, and use it to populate a ctrl object,
* preparing the ctrl object for dispatching to the action handlers.
*/
ctrl = ctrls_lookup_action(NULL, reqaction, TRUE);
if (ctrl == NULL) {
pr_signals_unblock();
errno = EINVAL;
return -1;
}
for (i = 0; i < nreqargs; i++) {
memset(reqarg, '\0', reqargsz);
if (read(cl->cl_fd, &reqarglen, sizeof(unsigned int)) < 0) {
pr_signals_unblock();
return -1;
}
/* Make sure reqarg is large enough to handle the given argument. If
* it is too small, allocate one of the necessary size.
*/
if (reqargsz < reqarglen) {
reqargsz = reqarglen + 1;
if (!ctrl->ctrls_tmp_pool) {
ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool");
}
reqarg = pcalloc(ctrl->ctrls_tmp_pool, reqargsz);
}
if (read(cl->cl_fd, reqarg, reqarglen) < 0) {
pr_signals_unblock();
return -1;
}
if (pr_ctrls_add_arg(ctrl, reqarg)) {
pr_signals_unblock();
return -1;
}
}
/* Add this ctrls object to the client object. */
*((pr_ctrls_t **) push_array(cl->cl_ctrls)) = ctrl;
/* Set the flag that this control is ready to go */
ctrl->ctrls_flags |= PR_CTRLS_REQUESTED;
ctrl->ctrls_cl = cl;
/* Copy the populated ctrl object args to ctrl objects for all other
* matching action objects.
*/
next_ctrl = ctrls_lookup_next_action(NULL, TRUE);
while (next_ctrl) {
if (pr_ctrls_copy_args(ctrl, next_ctrl))
return -1;
/* Add this ctrl object to the client object. */
*((pr_ctrls_t **) push_array(cl->cl_ctrls)) = next_ctrl;
/* Set the flag that this control is ready to go. */
next_ctrl->ctrls_flags |= PR_CTRLS_REQUESTED;
next_ctrl->ctrls_cl = cl;
next_ctrl = ctrls_lookup_next_action(NULL, TRUE);
}
pr_signals_unblock();
return 0;
}
int pr_ctrls_recv_response(pool *resp_pool, int ctrls_sockfd,
int *status, char ***respargv) {
register int i = 0;
array_header *resparr = NULL;
unsigned int respargc = 0, resparglen = 0;
char response[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
/* Sanity checks */
if (!resp_pool || ctrls_sockfd < 0 || !status) {
errno = EINVAL;
return -1;
}
resparr = make_array(resp_pool, 0, sizeof(char *));
/* No interruptions. */
pr_signals_block();
/* First, read the status, which is the return value of the control handler.
*/
if (read(ctrls_sockfd, status, sizeof(int)) != sizeof(int)) {
pr_signals_unblock();
return -1;
}
/* Next, read the number of responses to be received */
if (read(ctrls_sockfd, &respargc, sizeof(unsigned int)) !=
sizeof(unsigned int)) {
pr_signals_unblock();
return -1;
}
/* Read each response, and add it to the array */
for (i = 0; i < respargc; i++) {
int bread = 0, blen = 0;
if (read(ctrls_sockfd, &resparglen,
sizeof(unsigned int)) != sizeof(unsigned int)) {
pr_signals_unblock();
return -1;
}
memset(response, '\0', sizeof(response));
/* Make sure resparglen is not too big */
if (resparglen > sizeof(response)) {
pr_signals_unblock();
errno = ENOMEM;
return -1;
}
bread = read(ctrls_sockfd, response, resparglen);
while (bread != resparglen) {
if (bread < 0) {
pr_signals_unblock();
return -1;
}
blen += bread;
bread = read(ctrls_sockfd, response + blen, resparglen - blen);
}
/* Always make sure the buffer is zero-terminated */
response[sizeof(response)-1] = '\0';
*((char **) push_array(resparr)) = pstrdup(resp_pool, response);
}
if (respargv)
*respargv = ((char **) resparr->elts);
pr_signals_unblock();
return respargc;
}
int pr_ctrls_send_msg(int sockfd, int msgstatus, unsigned int msgargc,
char **msgargv) {
register int i = 0;
unsigned int msgarglen = 0;
/* Sanity checks */
if (sockfd < 0) {
errno = EINVAL;
return -1;
}
if (msgargc < 1)
return 0;
if (msgargv == NULL)
return 0;
/* No interruptions */
pr_signals_block();
/* Send the message status first */
if (write(sockfd, &msgstatus, sizeof(int)) != sizeof(int)) {
pr_signals_unblock();
return -1;
}
/* Send the strings, one argument at a time. First, send the number
* of arguments to be sent; then send, for each argument, first the
* length of the argument string, then the argument itself.
*/
if (write(sockfd, &msgargc, sizeof(unsigned int)) !=
sizeof(unsigned int)) {
pr_signals_unblock();
return -1;
}
for (i = 0; i < msgargc; i++) {
int res = 0;
msgarglen = strlen(msgargv[i]);
while (TRUE) {
res = write(sockfd, &msgarglen, sizeof(unsigned int));
if (res != sizeof(unsigned int)) {
if (errno == EAGAIN)
continue;
pr_signals_unblock();
return -1;
} else
break;
}
while (TRUE) {
res = write(sockfd, msgargv[i], msgarglen);
if (res != msgarglen) {
if (errno == EAGAIN)
continue;
pr_signals_unblock();
return -1;
} else
break;
}
}
pr_signals_unblock();
return 0;
}
static pr_ctrls_t *ctrls_lookup_action(module *mod, const char *action,
unsigned char skip_disabled) {
/* (Re)set the current indices */
action_lookup_next = ctrls_action_list;
action_lookup_action = action;
action_lookup_module = mod;
/* Wrapper around ctrls_lookup_next_action() */
return ctrls_lookup_next_action(mod, skip_disabled);
}
static pr_ctrls_t *ctrls_lookup_next_action(module *mod,
unsigned char skip_disabled) {
ctrls_action_t *act = NULL;
/* Sanity check */
if (!action_lookup_action) {
errno = EINVAL;
return NULL;
}
if (mod != action_lookup_module)
return ctrls_lookup_action(mod, action_lookup_action, skip_disabled);
for (act = action_lookup_next; act; act = act->next) {
if (skip_disabled && (act->flags & PR_CTRLS_ACT_DISABLED))
continue;
if (strcmp(act->action, action_lookup_action) == 0 &&
(act->module == mod || mod == ANY_MODULE || mod == NULL)) {
action_lookup_next = act->next;
/* Use this action object to prepare a ctrl object. */
return ctrls_prepare(act);
}
}
return NULL;
}
int pr_get_registered_actions(pr_ctrls_t *ctrl, int flags) {
ctrls_action_t *act = NULL;
/* Are ctrls blocked? */
if (ctrls_blocked) {
errno = EPERM;
return -1;
}
for (act = ctrls_action_list; act; act = act->next) {
switch (flags) {
case CTRLS_GET_ACTION_ALL:
pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", act->action,
act->module->name);
break;
case CTRLS_GET_ACTION_ENABLED:
if (act->flags & PR_CTRLS_ACT_DISABLED)
break;
pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", act->action,
act->module->name);
break;
case CTRLS_GET_DESC:
pr_ctrls_add_response(ctrl, "%s: %s", act->action,
act->desc);
break;
}
}
return 0;
}
int pr_set_registered_actions(module *mod, const char *action,
unsigned char skip_disabled, unsigned int flags) {
ctrls_action_t *act = NULL;
unsigned char have_action = FALSE;
/* Is flags a valid combination of settable flags? */
if (flags && flags != PR_CTRLS_ACT_DISABLED) {
errno = EINVAL;
return -1;
}
/* Are ctrls blocked? */
if (ctrls_blocked) {
errno = EPERM;
return -1;
}
for (act = ctrls_action_list; act; act = act->next) {
if (skip_disabled && (act->flags & PR_CTRLS_ACT_DISABLED))
continue;
if ((!action ||
strcmp(action, "all") == 0 ||
strcmp(act->action, action) == 0) &&
(act->module == mod || mod == ANY_MODULE || mod == NULL)) {
have_action = TRUE;
act->flags = flags;
}
}
if (!have_action) {
errno = ENOENT;
return -1;
}
return 0;
}
int pr_ctrls_connect(const char *socket_file) {
int sockfd = -1, len = 0;
struct sockaddr_un cl_sock, ctrl_sock;
/* No interruptions */
pr_signals_block();
/* Create a Unix domain socket */
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
pr_signals_unblock();
return -1;
}
/* Fill in the socket address */
memset(&cl_sock, 0, sizeof(cl_sock));
/* This first part is clever. First, this process creates a socket in
* the file system. It _then_ connect()s to the server. Upon accept()ing
* the connection, the server examines the created socket to see that it
* is indeed a socket, with the proper mode and time. Clever, but not
* ideal.
*/
cl_sock.sun_family = AF_UNIX;
sprintf(cl_sock.sun_path, "%s%05u", "/tmp/ftp.cl", (unsigned int) getpid());
len = sizeof(cl_sock);
/* Make sure the file doesn't already exist */
unlink(cl_sock.sun_path);
/* Make it a socket */
if (bind(sockfd, (struct sockaddr *) &cl_sock, len) < 0) {
unlink(cl_sock.sun_path);
pr_signals_unblock();
return -1;
}
/* Set the proper mode */
if (chmod(cl_sock.sun_path, PR_CTRLS_CL_MODE) < 0) {
unlink(cl_sock.sun_path);
pr_signals_unblock();
return -1;
}
/* Now connect to the real server */
memset(&ctrl_sock, 0, sizeof(ctrl_sock));
ctrl_sock.sun_family = AF_UNIX;
strncpy(ctrl_sock.sun_path, socket_file, strlen(socket_file));
len = sizeof(ctrl_sock);
if (connect(sockfd, (struct sockaddr *) &ctrl_sock, len) < 0) {
unlink(cl_sock.sun_path);
pr_signals_unblock();
return -1;
}
pr_signals_unblock();
return sockfd;
}
int pr_ctrls_issock_unix(mode_t sock_mode) {
if (ctrls_use_isfifo) {
#ifdef S_ISFIFO
if (S_ISFIFO(sock_mode)) {
return 0;
}
#endif /* S_ISFIFO */
} else {
#ifdef S_ISSOCK
if (S_ISSOCK(sock_mode)) {
return 0;
}
#endif /* S_ISSOCK */
}
errno = ENOSYS;
return -1;
}
void pr_block_ctrls(void) {
ctrls_blocked = TRUE;
}
void pr_unblock_ctrls(void) {
ctrls_blocked = FALSE;
}
int pr_check_actions(void) {
ctrls_action_t *act = NULL;
for (act = ctrls_action_list; act; act = act->next) {
if (act->flags & PR_CTRLS_ACT_SOLITARY) {
/* This is a territorial action -- only one instance allowed */
if (ctrls_lookup_action(NULL, act->action, FALSE)) {
pr_log_pri(PR_LOG_NOTICE,
"duplicate controls for '%s' action not allowed",
act->action);
return -1;
}
}
}
return 0;
}
int pr_run_ctrls(module *mod, const char *action) {
pr_ctrls_t *ctrl = NULL;
/* Are ctrls blocked? */
if (ctrls_blocked) {
errno = EPERM;
return -1;
}
for (ctrl = ctrls_active_list; ctrl; ctrl = ctrl->ctrls_next) {
/* Be watchful of the various client-side flags. Note: if
* ctrl->ctrls_cl is ever NULL, it means there's a bug in the code.
*/
if (ctrl->ctrls_cl->cl_flags != PR_CTRLS_CL_HAVEREQ)
continue;
/* Has this control been disabled? */
if (ctrl->ctrls_flags & PR_CTRLS_ACT_DISABLED)
continue;
/* Is it time to trigger this ctrl? */
if (!(ctrl->ctrls_flags & PR_CTRLS_REQUESTED))
continue;
if (ctrl->ctrls_when > time(NULL)) {
ctrl->ctrls_flags |= PR_CTRLS_PENDING;
pr_ctrls_add_response(ctrl, "request pending");
continue;
}
if (action &&
strcmp(ctrl->ctrls_action, action) == 0) {
pr_log_debug(DEBUG7, "calling '%s' control handler", ctrl->ctrls_action);
/* Invoke the callback, if the ctrl's action matches. Unblock
* ctrls before invoking the callback, then re-block them after the
* callback returns. This will allow the action handlers to use some
* of the Controls API functions correctly.
*/
pr_unblock_ctrls();
ctrl->ctrls_cb_retval = ctrl->ctrls_cb(ctrl,
(ctrl->ctrls_cb_args ? ctrl->ctrls_cb_args->nelts : 0),
(ctrl->ctrls_cb_args ? (char **) ctrl->ctrls_cb_args->elts : NULL));
pr_block_ctrls();
if (ctrl->ctrls_cb_retval < 1) {
ctrl->ctrls_flags &= ~PR_CTRLS_REQUESTED;
ctrl->ctrls_flags &= ~PR_CTRLS_PENDING;
ctrl->ctrls_flags |= PR_CTRLS_HANDLED;
}
} else if (!action) {
pr_log_debug(DEBUG5, "calling '%s' control handler", ctrl->ctrls_action);
/* If no action was given, invoke every callback */
pr_unblock_ctrls();
ctrl->ctrls_cb_retval = ctrl->ctrls_cb(ctrl,
(ctrl->ctrls_cb_args ? ctrl->ctrls_cb_args->nelts : 0),
(ctrl->ctrls_cb_args ? (char **) ctrl->ctrls_cb_args->elts : NULL));
pr_block_ctrls();
if (ctrl->ctrls_cb_retval < 1) {
ctrl->ctrls_flags &= ~PR_CTRLS_REQUESTED;
ctrl->ctrls_flags &= ~PR_CTRLS_PENDING;
ctrl->ctrls_flags |= PR_CTRLS_HANDLED;
}
}
}
return 0;
}
int pr_reset_ctrls(void) {
pr_ctrls_t *ctrl = NULL;
/* NOTE: need a clean_ctrls() or somesuch that will, after sending any
* responses, iterate through the list and "free" any ctrls whose
* ctrls_cb_retval is zero. This feature is used to handle things like
* shutdown requests in the future -- the request is only considered
* "processed" when the callback returns zero. Any non-zero requests are
* not cleared, and are considered "pending". However, this brings up the
* complication of an additional request for that action being issued by the
* client before the request is processed. Simplest solution: remove the
* old request args, and replace them with the new ones.
*
* This requires that the return value of the ctrl callback be explicitly
* documented.
*
* How about: ctrls_cb_retval = 1 pending
* 0 processed, OK (reset)
* -1 processed, error (reset)
*/
for (ctrl = ctrls_active_list; ctrl; ctrl = ctrl->ctrls_next) {
if (ctrl->ctrls_cb_retval < 1)
ctrls_free(ctrl);
}
return 0;
}
void init_ctrls(void) {
struct stat st;
int sockfd;
struct sockaddr_un sockun;
size_t socklen;
char *sockpath = PR_RUN_DIR "/test.sock";
if (ctrls_pool) {
destroy_pool(ctrls_pool);
}
ctrls_pool = make_sub_pool(permanent_pool);
pr_pool_tag(ctrls_pool, "Controls Pool");
/* Make sure all of the lists are zero'd out. */
ctrls_action_list = NULL;
ctrls_active_list = NULL;
ctrls_free_list = NULL;
/* And that the lookup indices are (re)set as well... */
action_lookup_next = NULL;
action_lookup_action = NULL;
action_lookup_module = NULL;
/* Run-time check to find out whether this platform identifies a
* Unix domain socket file descriptor via the S_ISFIFO macro, or
* the S_ISSOCK macro.
*/
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
pr_log_pri(PR_LOG_NOTICE, "notice: unable to create Unix domain socket: %s",
strerror(errno));
return;
}
memset(&sockun, 0, sizeof(sockun));
sockun.sun_family = AF_UNIX;
sstrncpy(sockun.sun_path, sockpath, strlen(sockpath) + 1);
socklen = sizeof(struct sockaddr_un);
if (bind(sockfd, (struct sockaddr *) &sockun, socklen) < 0) {
pr_log_pri(PR_LOG_NOTICE,
"notice: unable to bind to Unix domain socket at '%s': %s",
sockpath, strerror(errno));
(void) close(sockfd);
(void) unlink(sockpath);
return;
}
if (fstat(sockfd, &st) < 0) {
pr_log_pri(PR_LOG_NOTICE,
"notice: unable to stat Unix domain socket at '%s': %s",
sockpath, strerror(errno));
(void) close(sockfd);
(void) unlink(sockpath);
return;
}
#ifdef S_ISFIFO
pr_log_debug(DEBUG10, "Controls: testing Unix domain socket using S_ISFIFO");
if (S_ISFIFO(st.st_mode)) {
ctrls_use_isfifo = TRUE;
}
#else
pr_log_debug(DEBUG10, "Controls: cannot test Unix domain socket using "
"S_ISFIFO: macro undefined");
#endif
#ifdef S_ISSOCK
pr_log_debug(DEBUG10, "Controls: testing Unix domain socket using S_ISSOCK");
if (S_ISSOCK(st.st_mode)) {
ctrls_use_isfifo = FALSE;
}
#else
pr_log_debug(DEBUG10, "Controls: cannot test Unix domain socket using "
"S_ISSOCK: macro undefined");
#endif
pr_log_debug(DEBUG10, "Controls: using %s macro for Unix domain socket "
"detection", ctrls_use_isfifo ? "S_ISFIFO" : "S_ISSOCK");
(void) close(sockfd);
(void) unlink(sockpath);
return;
}
#endif /* PR_USE_CTRLS */
Last Updated: Thu Feb 23 11:07:11 2006
HTML generated by tj's src2html script