/*
* ProFTPD - FTP server daemon
* Copyright (c) 1997, 1998 Public Flood Software
* 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, Public Flood Software/MacGyver aka Habeeb J. Dihu
* 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.
*/
/*
* Module handling routines
* $Id: modules.c,v 1.49 2004/10/26 23:25:11 castaglia Exp $
*/
#include "conf.h"
/* This local structure vastly speeds up symbol lookups. */
struct stash {
struct stash *next,*prev;
pool *sym_pool;
const char *sym_name;
pr_stash_type_t sym_type;
module *sym_module;
union {
conftable *sym_conf;
cmdtable *sym_cmd;
authtable *sym_auth;
cmdtable *sym_hook;
void *sym_generic;
} ptr;
};
extern module *static_modules[];
extern module *loaded_modules;
/* Symbol hashes for each type */
static xaset_t *symbol_table[PR_TUNABLE_HASH_TABLE_SIZE];
static pool *symbol_pool = NULL;
static struct stash *curr_sym = NULL;
/* Currently running module */
module *curr_module = NULL;
/* Used to track the priority for loaded modules. */
static unsigned int curr_module_pri = 0;
typedef struct mod_cb {
struct mod_cb *next, *prev;
int (*module_cb)(void);
} module_cb_t;
/* Symbol stash lookup code and management */
/* This wrapper will be used in the future to track when to rehash through
* the symbol memory, to prevent symbol_pool from growing too large.
*/
static struct stash *sym_alloc(void) {
static unsigned int count = 0;
pool *sub_pool = make_sub_pool(symbol_pool);
struct stash *sym = pcalloc(sub_pool, sizeof(struct stash));
sym->sym_pool = sub_pool;
pr_pool_tag(sub_pool, "symbol subpool");
count++;
return sym;
}
static int sym_cmp(struct stash *s1, struct stash *s2) {
int ret;
ret = strcmp(s1->sym_name, s2->sym_name);
/* Higher priority modules must go BEFORE lower priority in the
* hash tables.
*/
if (!ret) {
if (s1->sym_module->priority > s2->sym_module->priority)
ret = -1;
else if (s1->sym_module->priority < s2->sym_module->priority)
ret = 1;
}
return ret;
}
static int symtab_hash(const char *name) {
unsigned char *cp = NULL;
int total = 0;
if (!name)
return 0;
for (cp = (unsigned char *)name; *cp; cp++)
total += (int)*cp;
return (total < PR_TUNABLE_HASH_TABLE_SIZE ? total :
(total % PR_TUNABLE_HASH_TABLE_SIZE));
}
int pr_stash_add_symbol(pr_stash_type_t sym_type, void *data) {
struct stash *sym = NULL;
int idx = 0;
if (!data) {
errno = EINVAL;
return -1;
}
switch (sym_type) {
case PR_SYM_CONF:
sym = sym_alloc();
sym->sym_type = PR_SYM_CONF;
sym->sym_name = ((conftable *) data)->directive;
sym->sym_module = ((conftable *) data)->m;
sym->ptr.sym_conf = data;
break;
case PR_SYM_CMD:
sym = sym_alloc();
sym->sym_type = PR_SYM_CMD;
sym->sym_name = ((cmdtable *) data)->command;
sym->sym_module = ((cmdtable *) data)->m;
sym->ptr.sym_cmd = data;
break;
case PR_SYM_AUTH:
sym = sym_alloc();
sym->sym_type = PR_SYM_AUTH;
sym->sym_name = ((authtable *) data)->name;
sym->sym_module = ((authtable *) data)->m;
sym->ptr.sym_auth = data;
break;
case PR_SYM_HOOK:
sym = sym_alloc();
sym->sym_type = PR_SYM_HOOK;
sym->sym_name = ((cmdtable *) data)->command;
sym->sym_module = ((cmdtable *) data)->m;
sym->ptr.sym_hook = data;
break;
default:
errno = EINVAL;
return -1;
}
/* XXX Ugly hack to support mixed cases of directives in config files. */
if (sym_type != PR_SYM_CONF)
idx = symtab_hash(sym->sym_name);
else {
register unsigned int i;
char buf[1024];
memset(buf, '\0', sizeof(buf));
sstrncpy(buf, sym->sym_name, sizeof(buf)-1);
for (i = 0; i < strlen(buf); i++)
buf[i] = tolower((int) buf[i]);
idx = symtab_hash(buf);
}
if (!symbol_table[idx])
symbol_table[idx] = xaset_create(symbol_pool, (XASET_COMPARE) sym_cmp);
xaset_insert_sort(symbol_table[idx], (xasetmember_t *) sym, TRUE);
return idx;
}
static struct stash *stash_lookup(pr_stash_type_t sym_type,
const char *name, int idx) {
struct stash *sym = NULL;
if (symbol_table[idx]) {
for (sym = (struct stash *) symbol_table[idx]->xas_list; sym;
sym = sym->next)
if (sym->sym_type == sym_type &&
(!name || strcasecmp(sym->sym_name, name) == 0))
break;
}
return sym;
}
static struct stash *stash_lookup_next(pr_stash_type_t sym_type,
const char *name, int idx, void *prev) {
struct stash *sym = NULL;
int last_hit = 0;
if (symbol_table[idx]) {
for (sym = (struct stash *) symbol_table[idx]->xas_list; sym;
sym = sym->next) {
if (last_hit && sym->sym_type == sym_type &&
(!name || strcasecmp(sym->sym_name, name) == 0))
break;
if (sym->ptr.sym_generic == prev)
last_hit++;
}
}
return sym;
}
void *pr_stash_get_symbol(pr_stash_type_t sym_type, const char *name,
void *prev, int *idx_cache) {
int idx;
struct stash *sym = NULL;
if (idx_cache && *idx_cache != -1)
idx = *idx_cache;
else {
/* XXX Ugly hack to support mixed cases of directives in config files. */
if (sym_type != PR_SYM_CONF)
idx = symtab_hash(name);
else {
register unsigned int i;
char buf[1024];
memset(buf, '\0', sizeof(buf));
sstrncpy(buf, name, sizeof(buf)-1);
for (i = 0; i < strlen(buf); i++)
buf[i] = tolower((int) buf[i]);
idx = symtab_hash(buf);
}
if (idx_cache)
*idx_cache = idx;
}
if (idx >= PR_TUNABLE_HASH_TABLE_SIZE) {
if (*idx_cache)
*idx_cache = -1;
return NULL;
}
if (prev)
curr_sym = sym = stash_lookup_next(sym_type, name, idx, prev);
else
curr_sym = sym = stash_lookup(sym_type, name, idx);
switch (sym_type) {
case PR_SYM_CONF:
return sym ? sym->ptr.sym_conf : NULL;
case PR_SYM_CMD:
return sym ? sym->ptr.sym_cmd : NULL;
case PR_SYM_AUTH:
return sym ? sym->ptr.sym_auth : NULL;
case PR_SYM_HOOK:
return sym ? sym->ptr.sym_hook : NULL;
}
/* In case the compiler complains */
return NULL;
}
int pr_stash_remove_symbol(pr_stash_type_t sym_type, const char *sym_name,
module *sym_module) {
int count = 0, symtab_idx = 0;
if (!sym_name) {
errno = EINVAL;
return -1;
}
/* XXX Ugly hack to support mixed cases of directives in config files. */
if (sym_type != PR_SYM_CONF)
symtab_idx = symtab_hash(sym_name);
else {
register unsigned int i;
char buf[1024];
memset(buf, '\0', sizeof(buf));
sstrncpy(buf, sym_name, sizeof(buf)-1);
for (i = 0; i < strlen(buf); i++)
buf[i] = tolower((int) buf[i]);
symtab_idx = symtab_hash(buf);
}
switch (sym_type) {
case PR_SYM_CONF: {
int idx = -1;
conftable *tab;
tab = pr_stash_get_symbol(PR_SYM_CONF, sym_name, NULL, &idx);
while (tab) {
pr_signals_handle();
/* Note: this works because of a hack: the symbol look functions
* set a static pointer, curr_sym, to point to the struct stash
* just looked up. curr_sym will not be NULL if pr_stash_get_symbol()
* returns non-NULL.
*/
if (!sym_module ||
curr_sym->sym_module == sym_module) {
xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
destroy_pool(curr_sym->sym_pool);
tab = NULL;
}
tab = pr_stash_get_symbol(PR_SYM_CONF, sym_name, tab, &idx);
}
break;
}
case PR_SYM_CMD: {
int idx = -1;
cmdtable *tab;
tab = pr_stash_get_symbol(PR_SYM_CMD, sym_name, NULL, &idx);
while (tab) {
pr_signals_handle();
/* Note: this works because of a hack: the symbol look functions
* set a static pointer, curr_sym, to point to the struct stash
* just looked up.
*/
if (!sym_module ||
curr_sym->sym_module == sym_module) {
xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
destroy_pool(curr_sym->sym_pool);
tab = NULL;
}
tab = pr_stash_get_symbol(PR_SYM_CMD, sym_name, tab, &idx);
}
break;
}
case PR_SYM_AUTH: {
int idx = -1;
authtable *tab;
tab = pr_stash_get_symbol(PR_SYM_AUTH, sym_name, NULL, &idx);
while (tab) {
pr_signals_handle();
/* Note: this works because of a hack: the symbol look functions
* set a static pointer, curr_sym, to point to the struct stash
* just looked up.
*/
if (!sym_module ||
curr_sym->sym_module == sym_module) {
xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
destroy_pool(curr_sym->sym_pool);
tab = NULL;
}
tab = pr_stash_get_symbol(PR_SYM_AUTH, sym_name, tab, &idx);
}
break;
}
case PR_SYM_HOOK: {
int idx = -1;
cmdtable *tab;
tab = pr_stash_get_symbol(PR_SYM_HOOK, sym_name, NULL, &idx);
while (tab) {
pr_signals_handle();
if (!sym_module ||
curr_sym->sym_module == sym_module) {
xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
destroy_pool(curr_sym->sym_pool);
tab = NULL;
}
tab = pr_stash_get_symbol(PR_SYM_HOOK, sym_name, tab, &idx);
}
break;
}
default:
errno = EINVAL;
return -1;
}
return count;
}
modret_t *call_module(module *m, modret_t *(*func)(cmd_rec *), cmd_rec *cmd) {
modret_t *res;
module *prev_module = curr_module;
if (!cmd->tmp_pool) {
cmd->tmp_pool = make_sub_pool(cmd->pool);
pr_pool_tag(cmd->tmp_pool, "call_module() cmd tmp_pool");
}
curr_module = m;
res = func(cmd);
curr_module = prev_module;
/* Note that we don't clear the pool here because the function may
* return data which resides in this pool.
*/
return res;
}
modret_t *mod_create_data(cmd_rec *cmd,void *d) {
modret_t *ret;
ret = pcalloc(cmd->tmp_pool, sizeof(modret_t));
ret->data = d;
return ret;
}
modret_t *mod_create_ret(cmd_rec *cmd, unsigned char err, char *n, char *m) {
modret_t *ret;
ret = pcalloc(cmd->tmp_pool, sizeof(modret_t));
ret->mr_handler_module = curr_module;
ret->mr_error = err;
if (n)
ret->mr_numeric = pstrdup(cmd->tmp_pool, n);
if (m)
ret->mr_message = pstrdup(cmd->tmp_pool, m);
return ret;
}
modret_t *mod_create_error(cmd_rec *cmd, int mr_errno) {
modret_t *ret;
ret = pcalloc(cmd->tmp_pool, sizeof(modret_t));
ret->mr_handler_module = curr_module;
ret->mr_error = mr_errno;
return ret;
}
/* Called after forking in order to inform/initialize modules
* need to know we are a child and have a connection.
*/
int modules_session_init(void) {
module *prev_module = curr_module, *m;
for (m = loaded_modules; m; m = m->next) {
if (m && m->sess_init) {
curr_module = m;
if (m->sess_init() < 0) {
pr_log_pri(PR_LOG_ERR, "mod_%s.c: error initializing session: %s",
m->name, strerror(errno));
return -1;
}
}
}
curr_module = prev_module;
return 0;
}
unsigned char command_exists(char *name) {
cmdtable *cmdtab = pr_stash_get_symbol(PR_SYM_CMD, name, NULL, NULL);
while (cmdtab && cmdtab->cmd_type != CMD)
cmdtab = pr_stash_get_symbol(PR_SYM_CMD, name, cmdtab, NULL);
return (cmdtab ? TRUE : FALSE);
}
unsigned char pr_module_exists(const char *name) {
return pr_module_get(name) != NULL ? TRUE : FALSE;
}
module *pr_module_get(const char *name) {
char buf[80] = {'\0'};
module *m;
if (!name) {
errno = EINVAL;
return NULL;
}
/* Check the list of compiled-in modules. */
for (m = loaded_modules; m; m = m->next) {
memset(buf, '\0', sizeof(buf));
snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
buf[sizeof(buf)-1] = '\0';
if (strcmp(buf, name) == 0)
return m;
}
errno = ENOENT;
return NULL;
}
void modules_list(void) {
register unsigned int i = 0;
printf("Compiled-in modules:\n");
for (i = 0; static_modules[i]; i++) {
module *m = static_modules[i];
printf(" mod_%s.c\n", m->name);
}
}
int pr_module_load(module *m) {
char buf[256];
if (!m) {
errno = EINVAL;
return -1;
}
/* Check the API version the module wants to use. */
if (m->api_version < PR_MODULE_API_VERSION) {
errno = EACCES;
return -1;
}
/* Do not allow multiple modules with the same name. */
memset(buf, '\0', sizeof(buf));
snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
buf[sizeof(buf)-1] = '\0';
if (pr_module_get(buf) != NULL) {
errno = EEXIST;
return -1;
}
/* Invoke the module's initialization routine. */
if (!m->init ||
m->init() >= 0) {
/* Assign a priority to this module. */
m->priority = curr_module_pri++;
/* Add the module's config, cmd, and auth tables. */
if (m->conftable) {
conftable *conftab;
for (conftab = m->conftable; conftab->directive; conftab++) {
conftab->m = m;
pr_stash_add_symbol(PR_SYM_CONF, conftab);
}
}
if (m->cmdtable) {
cmdtable *cmdtab;
for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
cmdtab->m = m;
if (cmdtab->cmd_type == HOOK)
pr_stash_add_symbol(PR_SYM_HOOK, cmdtab);
else
/* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
pr_stash_add_symbol(PR_SYM_CMD, cmdtab);
}
}
if (m->authtable) {
authtable *authtab;
for (authtab = m->authtable; authtab->name; authtab++) {
authtab->m = m;
pr_stash_add_symbol(PR_SYM_AUTH, authtab);
}
}
/* Add the module to the loaded_modules list. */
m->next = loaded_modules;
if (loaded_modules)
loaded_modules->prev = m;
loaded_modules = m;
/* Generate an event. */
pr_event_generate("core.module-load", buf);
return 0;
}
errno = EPERM;
return -1;
}
int pr_module_unload(module *m) {
char buf[256];
if (!m) {
errno = EINVAL;
return -1;
}
/* Make sure this module has been loaded. We can't unload a module that
* has not been loaded, now can we?
*/
memset(buf, '\0', sizeof(buf));
snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
buf[sizeof(buf)-1] = '\0';
if (pr_module_get(buf) == NULL) {
errno = ENOENT;
return -1;
}
/* Generate an event. */
pr_event_generate("core.module-unload", buf);
/* Remove the module from the loaded_modules list. */
if (m->prev)
m->prev->next = m->next;
else
/* This module is the start of the loaded_modules list (prev is NULL),
* so we need to update that pointer, too.
*/
loaded_modules = m->next;
if (m->next)
m->next->prev = m->prev;
m->prev = m->next = NULL;
/* Remove the module's config, cmd, and auth tables. */
if (m->conftable) {
conftable *conftab;
for (conftab = m->conftable; conftab->directive; conftab++)
pr_stash_remove_symbol(PR_SYM_CONF, conftab->directive, conftab->m);
}
if (m->cmdtable) {
cmdtable *cmdtab;
for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
if (cmdtab->cmd_type == HOOK)
pr_stash_remove_symbol(PR_SYM_HOOK, cmdtab->command, cmdtab->m);
else
/* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
pr_stash_remove_symbol(PR_SYM_CMD, cmdtab->command, cmdtab->m);
}
}
if (m->authtable) {
authtable *authtab;
for (authtab = m->authtable; authtab->name; authtab++)
pr_stash_remove_symbol(PR_SYM_AUTH, authtab->name, authtab->m);
}
return 0;
}
int modules_init(void) {
register unsigned int i = 0;
for (i = 0; static_modules[i]; i++) {
module *m = static_modules[i];
if (pr_module_load(m) < 0) {
pr_log_pri(PR_LOG_ERR, "Fatal: unable to load module 'mod_%s.c': %s",
m->name, strerror(errno));
exit(1);
}
}
return 0;
}
int init_stash(void) {
if (symbol_pool)
destroy_pool(symbol_pool);
symbol_pool = make_sub_pool(permanent_pool);
pr_pool_tag(symbol_pool, "Stash Pool");
memset(symbol_table, '\0', sizeof(symbol_table));
return 0;
}
Last Updated: Thu Feb 23 11:07:19 2006
HTML generated by tj's src2html script