/* * 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; }