/*
* ProFTPD: mod_ctrls_admin -- a module implementing admin control handlers
*
* 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_controls, contrib software for proftpd 1.2 and above.
* For more information contact TJ Saunders <tj@castaglia.org>.
*
* $Id: mod_ctrls_admin.c,v 1.23 2005/04/30 19:59:27 castaglia Exp $
*/
#include "conf.h"
#include "privs.h"
#include "mod_ctrls.h"
#define MOD_CTRLS_ADMIN_VERSION "mod_ctrls_admin/0.9.3"
/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001021001
# error "ProFTPD 1.2.10rc1 or later required"
#endif
#ifndef PR_USE_CTRLS
# error "Controls support required (use --enable-ctrls)"
#endif
/* Values for the stop flags */
#define CTRL_STOP_DEFAULT (1 << 0)
#define CTRL_STOP_CLEAN (1 << 1)
#define CTRL_STOP_FULL (1 << 2)
#define CTRL_STOP_GRACEFUL (1 << 3)
/* For the 'shutdown' control action */
#define CTRLS_DEFAULT_SHUTDOWN_WAIT 5
/* From src/dirtree.c */
extern xaset_t *server_list;
extern int ServerUseReverseDNS;
module ctrls_admin_module;
static ctrls_acttab_t ctrls_admin_acttab[];
/* Pool for this module's use */
static pool *ctrls_admin_pool = NULL;
/* For the 'dump' action */
static pr_ctrls_t *ctrls_dump_ctrl = NULL;
/* Support routines
*/
static void ctrls_admin_printf(const char *fmt, ...) {
char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
va_list msg;
va_start(msg, fmt);
vsnprintf(buf, sizeof(buf), fmt, msg);
va_end(msg);
buf[sizeof(buf)-1] = '\0';
pr_ctrls_add_response(ctrls_dump_ctrl, "%s", buf);
}
#if 0
/* Will be used when scheduled shutdowns are supported.. */
static unsigned char isnumeric(char *str) {
while (str && isspace((int) *str))
str++;
if (!str || !*str)
return FALSE;
for (; str && *str; str++) {
if (!isdigit((int) *str))
return TRUE;
}
return 1;
}
#endif
static int respcmp(const void *a, const void *b) {
return strcmp(*((char **) a), *((char **) b));
}
/* Controls handlers
*/
static int ctrls_handle_debug(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
/* Check the debug ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "debug")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Sanity check */
if (reqargc == 0 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "debug: missing required parameters");
return -1;
}
/* Handle 'debug level' requests */
if (strcmp(reqargv[0], "level") == 0) {
int level = 0;
if (reqargc != 2) {
pr_ctrls_add_response(ctrl, "debug: missing required parameters");
return -1;
}
if ((level = atoi(reqargv[1])) < 0) {
pr_ctrls_add_response(ctrl, "debug level must not be negative");
return -1;
}
pr_log_setdebuglevel(level);
ctrls_log(MOD_CTRLS_ADMIN_VERSION, "debug: level set to %d", level);
pr_ctrls_add_response(ctrl, "debug level set to %d", level);
} else {
pr_ctrls_add_response(ctrl, "unknown debug action: '%s'", reqargv[0]);
return -1;
}
return 0;
}
static int ctrls_handle_dns(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
int bool;
/* Check the dns ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "dns")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Sanity check */
if (reqargc == 0 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "dns: missing required parameters");
return -1;
}
if (reqargc != 1) {
pr_ctrls_add_response(ctrl, "dns: wrong number of parameters");
return -1;
}
bool = pr_is_boolean(reqargv[0]);
if (bool == -1) {
pr_ctrls_add_response(ctrl, "dns: error: expected Boolean parameter: '%s'",
reqargv[0]);
return -1;
}
ServerUseReverseDNS = bool;
ctrls_log(MOD_CTRLS_ADMIN_VERSION, "dns: UseReverseDNS set to '%s'",
bool ? "on" : "off");
pr_ctrls_add_response(ctrl, "dns: UseReverseDNS set to '%s'",
bool ? "on" : "off");
return 0;
}
static int admin_addr_down(pr_ctrls_t *ctrl, pr_netaddr_t *addr,
unsigned int port) {
ctrls_log(MOD_CTRLS_ADMIN_VERSION, "down: disabling %s#%u",
pr_netaddr_get_ipstr(addr), port);
if (pr_ipbind_close(addr, port, FALSE) < 0) {
if (errno == ENOENT)
pr_ctrls_add_response(ctrl, "down: no such server: %s#%u",
pr_netaddr_get_ipstr(addr), port);
else
pr_ctrls_add_response(ctrl, "down: %s#%u already disabled",
pr_netaddr_get_ipstr(addr), port);
} else
pr_ctrls_add_response(ctrl, "down: %s#%u disabled",
pr_netaddr_get_ipstr(addr), port);
return 0;
}
static int ctrls_handle_down(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
register unsigned int i = 0;
/* Handle scheduled downs of virtual servers in the future, and
* cancellations of scheduled downs.
*/
/* Check the 'down' ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "down")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Sanity check */
if (reqargc < 1 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "down: missing required parameters");
return -1;
}
for (i = 0; i < reqargc; i++) {
unsigned int server_port = 21;
char *server_str = reqargv[i], *tmp = NULL;
pr_netaddr_t *server_addr = NULL;
array_header *addrs = NULL;
/* Check for an argument of "all" */
if (strcasecmp(server_str, "all") == 0) {
pr_ipbind_close(NULL, 0, FALSE);
pr_ctrls_add_response(ctrl, "down: all servers disabled");
return 0;
}
tmp = strchr(server_str, '#');
if (tmp != NULL) {
server_port = atoi(tmp + 1);
*tmp = '\0';
}
server_addr = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, server_str, &addrs);
if (!server_addr) {
pr_ctrls_add_response(ctrl, "down: no such server: %s#%u",
server_str, server_port);
continue;
}
admin_addr_down(ctrl, server_addr, server_port);
if (addrs) {
register unsigned int j;
pr_netaddr_t **elts = addrs->elts;
for (j = 0; j < addrs->nelts; j++)
admin_addr_down(ctrl, elts[j], server_port);
}
}
return 0;
}
static int ctrls_handle_get(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
int res = 0;
/* Sanity check */
if (reqargc == 0 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "get: missing required parameters");
return -1;
}
/* Handle 'get config' requests */
if (strcmp(reqargv[0], "config") == 0) {
if (reqargc >= 2) {
register int i = 0;
for (i = 1; i < reqargc; i++) {
config_rec *c = NULL;
/* NOTE: there are some directives that are not stored as config_recs,
* but rather as static variables or as members of other structs.
* Handle these exceptions as well? These include ServerName,
* ServerType, ServerAdmin, etc. How to handle configs that should
* be retrievable, but are Boolean values instead of strings. Hmmm.
*/
if ((c = find_config(main_server->conf, CONF_PARAM, reqargv[i],
FALSE)) != NULL) {
#if 0
/* Not yet supported */
if (c->flags & CF_GCTRL)
pr_ctrls_add_response(ctrl, "%s: %s", reqargv[i],
(char *) c->argv[0]);
else
#endif
pr_ctrls_add_response(ctrl, "%s: not retrievable", reqargv[i]);
} else
pr_ctrls_add_response(ctrl, "%s: directive not found", reqargv[i]);
}
} else {
pr_ctrls_add_response(ctrl, "%s: missing parameters", reqargv[0]);
res = -1;
}
/* Handle 'get directives' requests */
} else if (strcmp(reqargv[0], "directives") == 0) {
if (reqargc == 1) {
conftable *conftab;
int stash_idx = -1;
/* Create a list of all known configuration directives. */
conftab = pr_stash_get_symbol(PR_SYM_CONF, NULL, NULL, &stash_idx);
while (stash_idx != -1) {
pr_signals_handle();
if (conftab) {
pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", conftab->directive,
conftab->m->name);
} else
stash_idx++;
conftab = pr_stash_get_symbol(PR_SYM_CONF, NULL, conftab, &stash_idx);
}
/* Be nice, and sort the directives lexicographically */
qsort(ctrl->ctrls_cb_resps->elts, ctrl->ctrls_cb_resps->nelts,
sizeof(char *), respcmp);
} else {
pr_ctrls_add_response(ctrl, "%s: wrong number of parameters", reqargv[0]);
res = -1;
}
} else {
pr_ctrls_add_response(ctrl, "unknown get type requested: '%s'", reqargv[0]);
res = -1;
}
return res;
}
static int ctrls_handle_kick(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
int res = 0;
/* Check the kick ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "kick")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Sanity check */
if (reqargc == 0 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "missing required parameters");
return -1;
}
/* Handle 'kick user' requests. */
if (strcmp(reqargv[0], "user") == 0) {
register unsigned int i = 0;
pr_scoreboard_entry_t *score = NULL;
if (reqargc == 1) {
pr_ctrls_add_response(ctrl, "kick user: missing required user name(s)");
return -1;
}
/* Iterate through the scoreboard, and send a SIGTERM to each
* pid whose name matches the given user name(s).
*/
for (i = 1; i < reqargc; i++) {
unsigned char kicked_user = FALSE;
if (pr_rewind_scoreboard() < 0)
ctrls_log("error rewinding scoreboard: %s", strerror(errno));
while ((score = pr_scoreboard_read_entry()) != NULL) {
if (strcmp(reqargv[i], score->sce_user) == 0) {
res = 0;
PRIVS_ROOT
res = kill(score->sce_pid, SIGTERM);
PRIVS_RELINQUISH
if (res == 0)
kicked_user = TRUE;
else
ctrls_log("error kicking user '%s': %s", reqargv[i],
strerror(errno));
}
}
if (pr_restore_scoreboard() < 0)
ctrls_log("error restoring scoreboard: %s", strerror(errno));
if (kicked_user) {
pr_ctrls_add_response(ctrl, "kicked user '%s'", reqargv[i]);
ctrls_log("kicked user '%s'", reqargv[i]);
pr_log_debug(DEBUG4, MOD_CTRLS_ADMIN_VERSION ": kicked user '%s'",
reqargv[i]);
} else
pr_ctrls_add_response(ctrl, "user '%s' not connected", reqargv[i]);
}
/* Handle 'kick host' requests. */
} else if (strcmp(reqargv[0], "host") == 0) {
register unsigned int i = 0;
pr_scoreboard_entry_t *score = NULL;
if (reqargc == 1) {
pr_ctrls_add_response(ctrl, "kick host: missing required host(s)");
return -1;
}
/* Iterate through the scoreboard, and send a SIGTERM to each
* pid whose address matches the given host name (resolve to
* stringified IP address).
*/
for (i = 1; i < reqargc; i++) {
unsigned char kicked_host = FALSE;
const char *addr;
pr_netaddr_t *na;
na = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, reqargv[1], NULL);
if (!na) {
pr_ctrls_add_response(ctrl, "kick host: error resolving '%s': %s",
reqargv[1], strerror(errno));
continue;
}
addr = pr_netaddr_get_ipstr(na);
if (pr_rewind_scoreboard() < 0)
ctrls_log("error rewinding scoreboard: %s", strerror(errno));
while ((score = pr_scoreboard_read_entry()) != NULL) {
if (strcmp(score->sce_client_addr, addr) == 0) {
PRIVS_ROOT
if (kill(score->sce_pid, SIGTERM) == 0)
kicked_host = TRUE;
PRIVS_RELINQUISH
}
}
pr_restore_scoreboard();
if (kicked_host) {
pr_ctrls_add_response(ctrl, "kicked host '%s'", addr);
ctrls_log("kicked host '%s'", addr);
pr_log_debug(DEBUG4, MOD_CTRLS_ADMIN_VERSION ": kicked host '%s'",
addr);
} else
pr_ctrls_add_response(ctrl, "host '%s' not connected", addr);
}
/* Handle 'kick class' requests. */
} else if (strcmp(reqargv[0], "class") == 0) {
register unsigned int i = 0;
pr_scoreboard_entry_t *score = NULL;
if (reqargc == 1) {
pr_ctrls_add_response(ctrl, "kick class: missing required class name(s)");
return -1;
}
/* Iterate through the scoreboard, and send a SIGTERM to each
* pid whose name matches the given class name(s).
*/
for (i = 1; i < reqargc; i++) {
unsigned char kicked_class = FALSE;
if (pr_rewind_scoreboard() < 0)
ctrls_log("error rewinding scoreboard: %s", strerror(errno));
while ((score = pr_scoreboard_read_entry()) != NULL) {
if (strcmp(reqargv[i], score->sce_class) == 0) {
res = 0;
PRIVS_ROOT
res = kill(score->sce_pid, SIGTERM);
PRIVS_RELINQUISH
if (res == 0)
kicked_class = TRUE;
else
ctrls_log("error kicking class '%s': %s", reqargv[i],
strerror(errno));
}
}
if (pr_restore_scoreboard() < 0)
ctrls_log("error restoring scoreboard: %s", strerror(errno));
if (kicked_class) {
pr_ctrls_add_response(ctrl, "kicked class '%s'", reqargv[i]);
ctrls_log("kicked class '%s'", reqargv[i]);
pr_log_debug(DEBUG4, MOD_CTRLS_ADMIN_VERSION ": kicked class '%s'",
reqargv[i]);
} else
pr_ctrls_add_response(ctrl, "class '%s' not connected", reqargv[i]);
}
} else {
pr_ctrls_add_response(ctrl, "unknown kick type requested: '%s'",
reqargv[0]);
res = -1;
}
return res;
}
static int ctrls_handle_restart(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
/* Check the restart ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "restart")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Be pedantic */
if (reqargc != 0) {
pr_ctrls_add_response(ctrl, "bad number of arguments");
return -1;
}
PRIVS_ROOT
raise(SIGHUP);
PRIVS_RELINQUISH
pr_ctrls_add_response(ctrl, "restarted server");
return 0;
}
static int ctrls_handle_shutdown(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
register unsigned int i = 0;
int respargc = 0;
char **respargv = NULL;
/* Check the shutdown ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "shutdown")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Add a response */
pr_ctrls_add_response(ctrl, "shutting down");
if (reqargc >= 1 &&
strcmp(reqargv[0], "graceful") == 0) {
unsigned long nkids = 0;
unsigned int waiting = CTRLS_DEFAULT_SHUTDOWN_WAIT;
unsigned int timeout = 0;
time_t now;
if (reqargc == 2) {
timeout = atoi(reqargv[1]);
time(&now);
ctrls_log(MOD_CTRLS_ADMIN_VERSION,
"shutdown: waiting %u seconds before shutting down", timeout);
/* If the timeout is less than the waiting period, reduce the
* waiting period by half.
*/
if (timeout < waiting)
waiting /= 2;
}
/* Now, simply wait for all sessions to be done. For bonus points,
* the admin should be able to specify a timeout, after which any
* sessions will be summarily terminated. And, even better, have a
* way to indicate to the sessions that the daemon wants to shut down,
* and the session, if it is not involved in a data transfer, should
* end itself.
*/
nkids = child_count();
while (nkids > 0) {
if (timeout &&
time(NULL) - now > timeout) {
ctrls_log(MOD_CTRLS_ADMIN_VERSION,
"shutdown: %u seconds elapsed, ending remaining sessions",
timeout);
/* End all remaining sessions at this point. */
PRIVS_ROOT
child_signal(SIGTERM);
PRIVS_RELINQUISH
break;
}
ctrls_log(MOD_CTRLS_ADMIN_VERSION,
"shutdown: waiting for %lu sessions to end", nkids);
sleep(waiting);
child_update();
nkids = child_count();
/* Always check for sent signals in a while() loop. */
pr_signals_handle();
}
}
/* This is one of the rare cases where the control handler needs to
* flush the responses out to the client manually, rather than waiting
* for the normal controls cycle to handle it, as this handler is
* not going to exit the function normally.
*/
respargc = ctrl->ctrls_cb_resps->nelts;
respargv = ctrl->ctrls_cb_resps->elts;
/* Manually tweak the return value, for the benefit of the client */
ctrl->ctrls_cb_retval = 0;
if (pr_ctrls_flush_response(ctrl) < 0)
ctrls_log(MOD_CTRLS_ADMIN_VERSION,
"shutdown: error flushing response: %s", strerror(errno));
/* For logging/accounting purposes */
ctrls_log(MOD_CTRLS_ADMIN_VERSION,
"shutdown: flushed to %s/%s client: return value: 0",
ctrl->ctrls_cl->cl_user, ctrl->ctrls_cl->cl_group);
for (i = 0; i < respargc; i++)
ctrls_log(MOD_CTRLS_ADMIN_VERSION,
"shutdown: flushed to %s/%s client: '%s'",
ctrl->ctrls_cl->cl_user, ctrl->ctrls_cl->cl_group, respargv[i]);
/* Shutdown by raising SIGTERM. Easy. */
raise(SIGTERM);
return 0;
}
static int admin_addr_status(pr_ctrls_t *ctrl, pr_netaddr_t *addr,
unsigned int port) {
pr_ipbind_t *ipbind = NULL;
ctrls_log(MOD_CTRLS_ADMIN_VERSION, "status: checking %s#%u",
pr_netaddr_get_ipstr(addr), port);
/* Fetch the ipbind associated with this address/port. */
ipbind = pr_ipbind_find(addr, port, FALSE);
if (ipbind == NULL) {
pr_ctrls_add_response(ctrl,
"status: no server associated with %s#%u", pr_netaddr_get_ipstr(addr),
port);
return -1;
}
pr_ctrls_add_response(ctrl, "status: %s#%u %s", pr_netaddr_get_ipstr(addr),
port, ipbind->ib_isactive ? "UP" : "DOWN");
return 0;
}
static int ctrls_handle_status(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
register unsigned int i = 0;
/* Check the status ACL. */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "status")) {
/* Access denied. */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Sanity check */
if (reqargc < 1 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "status: missing required parameters");
return -1;
}
for (i = 0; i < reqargc; i++) {
unsigned int server_port = 21;
char *server_str = reqargv[i], *tmp = NULL;
pr_netaddr_t *server_addr = NULL;
array_header *addrs = NULL;
/* Check for an argument of "all" */
if (strcasecmp(server_str, "all") == 0) {
pr_ipbind_t *ipbind = NULL;
ctrls_log(MOD_CTRLS_ADMIN_VERSION, "status: checking all servers");
while ((ipbind = pr_ipbind_get(ipbind)) != NULL) {
const char *ipbind_str = pr_netaddr_get_ipstr(ipbind->ib_addr);
pr_ctrls_add_response(ctrl, "status: %s#%u %s", ipbind_str,
ipbind->ib_port, ipbind->ib_isactive ? "UP" : "DOWN");
}
return 0;
}
tmp = strchr(server_str, '#');
if (tmp != NULL) {
server_port = atoi(tmp + 1);
*tmp = '\0';
}
server_addr = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, server_str, &addrs);
if (!server_addr) {
pr_ctrls_add_response(ctrl, "status: no such server: %s#%u",
server_str, server_port);
continue;
}
if (admin_addr_status(ctrl, server_addr, server_port) < 0)
continue;
if (addrs) {
register unsigned int j;
pr_netaddr_t **elts = addrs->elts;
for (j = 0; j < addrs->nelts; j++)
admin_addr_status(ctrl, elts[j], server_port);
}
}
return 0;
}
static int admin_addr_up(pr_ctrls_t *ctrl, pr_netaddr_t *addr,
unsigned int port) {
pr_ipbind_t *ipbind = NULL;
int res = 0;
/* Fetch the ipbind associated with this address/port. */
ipbind = pr_ipbind_find(addr, port, FALSE);
if (ipbind == NULL) {
pr_ctrls_add_response(ctrl,
"up: no server associated with %s#%u", pr_netaddr_get_ipstr(addr),
port);
return -1;
}
/* If this ipbind is already active, abort now. */
if (ipbind->ib_isactive) {
pr_ctrls_add_response(ctrl, "up: %s#%u already enabled",
pr_netaddr_get_ipstr(addr), port);
return 0;
}
/* Determine whether this server_rec needs a listening connection
* created. A ServerType of SERVER_STANDALONE combined with a
* SocketBindTight means each server_rec will have its own listen
* connection; any other combination means that all the server_recs
* share the same listen connection.
*/
if (ipbind->ib_server->ServerPort && !ipbind->ib_server->listen) {
ipbind->ib_server->listen =
pr_inet_create_connection(ipbind->ib_server->pool, server_list, -1,
(SocketBindTight ? ipbind->ib_server->addr : NULL),
ipbind->ib_server->ServerPort, FALSE);
}
ctrls_log(MOD_CTRLS_ADMIN_VERSION, "up: attempting to enable %s#%u",
pr_netaddr_get_ipstr(addr), port);
PR_OPEN_IPBIND(ipbind->ib_server->addr, ipbind->ib_server->ServerPort,
ipbind->ib_server->listen, FALSE, FALSE, TRUE);
if (res < 0)
pr_ctrls_add_response(ctrl, "up: no server listening on %s#%u",
pr_netaddr_get_ipstr(addr), port);
else
pr_ctrls_add_response(ctrl, "up: %s#%u enabled",
pr_netaddr_get_ipstr(addr), port);
PR_ADD_IPBINDS(ipbind->ib_server);
return 0;
}
static int ctrls_handle_up(pr_ctrls_t *ctrl, int reqargc,
char **reqargv) {
register unsigned int i = 0;
/* Handle scheduled ups of virtual servers in the future, and
* cancellations of scheduled ups.
*/
/* Check the 'up' ACL */
if (!ctrls_check_acl(ctrl, ctrls_admin_acttab, "up")) {
/* Access denied */
pr_ctrls_add_response(ctrl, "access denied");
return -1;
}
/* Sanity check */
if (reqargc < 1 || reqargv == NULL) {
pr_ctrls_add_response(ctrl, "up: missing required parameters");
return -1;
}
for (i = 0; i < reqargc; i++) {
unsigned int server_port = 21;
char *server_str = reqargv[i], *tmp = NULL;
pr_netaddr_t *server_addr = NULL;
array_header *addrs = NULL;
tmp = strchr(server_str, '#');
if (tmp != NULL) {
server_port = atoi(tmp + 1);
*tmp = '\0';
}
server_addr = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, server_str, &addrs);
if (!server_addr) {
pr_ctrls_add_response(ctrl, "up: unable to resolve address for '%s'",
server_str);
return -1;
}
if (admin_addr_up(ctrl, server_addr, server_port) < 0)
return -1;
if (addrs) {
register unsigned int j;
pr_netaddr_t **elts = addrs->elts;
for (j = 0; j < addrs->nelts; j++)
if (admin_addr_up(ctrl, elts[j], server_port) < 0)
return -1;
}
}
return 0;
}
/* Configuration handlers
*/
/* usage: AdminControlsACLs actions|all allow|deny user|group list */
MODRET set_adminctrlsacls(cmd_rec *cmd) {
char *bad_action = NULL, **actions = NULL;
CHECK_ARGS(cmd, 4);
CHECK_CONF(cmd, CONF_ROOT);
/* 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_admin_acttab,
ctrls_admin_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);
}
/* usage: AdminControlsEngine on|off|actions */
MODRET set_adminctrlsengine(cmd_rec *cmd) {
int bool = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if ((bool = get_boolean(cmd, 1)) != -1) {
/* If bool is TRUE, there's no need to do anything. If FALSE,
* then unregister all the controls of this module.
*/
if (!bool) {
register unsigned int i = 0;
for (i = 0; ctrls_admin_acttab[i].act_action; i++) {
pr_ctrls_unregister(&ctrls_admin_module,
ctrls_admin_acttab[i].act_action);
destroy_pool(ctrls_admin_acttab[i].act_acl->acl_pool);
}
}
} else {
char *bad_action = NULL;
/* 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...
*/
char **actions = ctrls_parse_acl(cmd->tmp_pool, cmd->argv[1]);
if ((bad_action = ctrls_unregister_module_actions(ctrls_admin_acttab,
actions, &ctrls_admin_module)) != NULL)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown action: '",
bad_action, "'", NULL));
}
return HANDLED(cmd);
}
/* Event handlers
*/
#if defined(PR_SHARED_MODULE)
static void ctrls_admin_mod_unload_ev(const void *event_data, void *user_data) {
if (strcmp("mod_ctrls_admin.c", (const char *) event_data) == 0) {
register unsigned int i;
pr_event_unregister(&ctrls_admin_module, NULL, NULL);
for (i = 0; ctrls_admin_acttab[i].act_action; i++) {
pr_ctrls_unregister(&ctrls_admin_module,
ctrls_admin_acttab[i].act_action);
}
if (ctrls_admin_pool) {
destroy_pool(ctrls_admin_pool);
ctrls_admin_pool = NULL;
}
}
}
#endif /* PR_SHARED_MODULE */
static void ctrls_admin_restart_ev(const void *event_data, void *user_data) {
register unsigned int i;
if (ctrls_admin_pool)
destroy_pool(ctrls_admin_pool);
/* Allocate the pool for this module's use */
ctrls_admin_pool = make_sub_pool(permanent_pool);
pr_pool_tag(ctrls_admin_pool, MOD_CTRLS_ADMIN_VERSION);
/* Register the control handlers */
for (i = 0; ctrls_admin_acttab[i].act_action; i++) {
/* Allocate and initialize the ACL for this control. */
ctrls_admin_acttab[i].act_acl = pcalloc(ctrls_admin_pool,
sizeof(ctrls_acl_t));
ctrls_init_acl(ctrls_admin_acttab[i].act_acl);
}
return;
}
static void ctrls_admin_startup_ev(const void *event_data, void *user_data) {
int res;
/* Make sure the process has an fd to the scoreboard. */
PRIVS_ROOT
res = pr_open_scoreboard(O_RDWR);
PRIVS_RELINQUISH
if (res < 0) {
switch (res) {
case PR_SCORE_ERR_BAD_MAGIC:
pr_log_debug(DEBUG0, "error opening scoreboard: bad/corrupted file");
break;
case PR_SCORE_ERR_OLDER_VERSION:
pr_log_debug(DEBUG0, "error opening scoreboard: bad version (too old)");
break;
case PR_SCORE_ERR_NEWER_VERSION:
pr_log_debug(DEBUG0, "error opening scoreboard: bad version (too new)");
break;
default:
pr_log_debug(DEBUG0, "error opening scoreboard: %s", strerror(errno));
break;
}
}
return;
}
/* Initialization routines
*/
static int ctrls_admin_init(void) {
register unsigned int i = 0;
/* Allocate the pool for this module's use */
ctrls_admin_pool = make_sub_pool(permanent_pool);
pr_pool_tag(ctrls_admin_pool, MOD_CTRLS_ADMIN_VERSION);
/* Register the control handlers */
for (i = 0; ctrls_admin_acttab[i].act_action; i++) {
/* Allocate and initialize the ACL for this control. */
ctrls_admin_acttab[i].act_acl = pcalloc(ctrls_admin_pool,
sizeof(ctrls_acl_t));
ctrls_init_acl(ctrls_admin_acttab[i].act_acl);
if (pr_ctrls_register(&ctrls_admin_module,
ctrls_admin_acttab[i].act_action, ctrls_admin_acttab[i].act_desc,
ctrls_admin_acttab[i].act_cb) < 0)
pr_log_pri(PR_LOG_INFO, MOD_CTRLS_ADMIN_VERSION
": error registering '%s' control: %s",
ctrls_admin_acttab[i].act_action, strerror(errno));
}
#if defined(PR_SHARED_MODULE)
pr_event_register(&ctrls_admin_module, "core.module-unload",
ctrls_admin_mod_unload_ev, NULL);
#endif /* PR_SHARED_MODULE */
pr_event_register(&ctrls_admin_module, "core.restart",
ctrls_admin_restart_ev, NULL);
pr_event_register(&ctrls_admin_module, "core.startup",
ctrls_admin_startup_ev, NULL);
return 0;
}
static ctrls_acttab_t ctrls_admin_acttab[] = {
{ "debug", "set debugging level", NULL,
ctrls_handle_debug },
{ "dns", "set UseReverseDNS configuration", NULL,
ctrls_handle_dns },
{ "down", "disable an individual virtual server", NULL,
ctrls_handle_down },
{ "get", "list configuration data", NULL,
ctrls_handle_get },
{ "kick", "disconnect a class, host, or user", NULL,
ctrls_handle_kick },
{ "restart", "restart the daemon (similar to using HUP)", NULL,
ctrls_handle_restart },
{ "shutdown", "shutdown the daemon", NULL,
ctrls_handle_shutdown },
{ "status", "display status of servers", NULL,
ctrls_handle_status },
{ "up", "enable a downed virtual server", NULL,
ctrls_handle_up },
{ NULL, NULL, NULL, NULL }
};
/* Module API tables
*/
static conftable ctrls_admin_conftab[] = {
{ "AdminControlsACLs", set_adminctrlsacls, NULL },
{ "AdminControlsEngine", set_adminctrlsengine, NULL },
{ NULL }
};
module ctrls_admin_module = {
NULL, NULL,
/* Module API version 2.0 */
0x20,
/* Module name */
"ctrls_admin",
/* Module configuration handler table */
ctrls_admin_conftab,
/* Module command handler table */
NULL,
/* Module authentication handler table */
NULL,
/* Module initialization function */
ctrls_admin_init,
/* Session initialization function */
NULL
};
Last Updated: Thu Feb 23 11:06:25 2006
HTML generated by tj's src2html script