diff options
author | Corey Farrell <git@cfware.com> | 2015-12-30 21:51:47 -0500 |
---|---|---|
committer | Corey Farrell <git@cfware.com> | 2016-01-01 14:01:15 -0500 |
commit | 2ffade45742a5c4361998625bef58eca0fbdb95e (patch) | |
tree | 8314333a4838cdb4439d7178707284aa51c3cfc7 /main | |
parent | bc7c882326a7f8176fbec018581046068ec0b592 (diff) |
main/pbx: Move custom function routines to pbx_functions.c.
This is the second patch in a series meant to reduce the bulk of pbx.c.
This moves custom function management routines to their own source.
Change-Id: I34a6190282f781cdbbd3ce9d3adeac3c3805e177
Diffstat (limited to 'main')
-rw-r--r-- | main/asterisk.c | 5 | ||||
-rw-r--r-- | main/pbx.c | 646 | ||||
-rw-r--r-- | main/pbx_functions.c | 723 |
3 files changed, 728 insertions, 646 deletions
diff --git a/main/asterisk.c b/main/asterisk.c index 18b5ddf87..0e179a397 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -4648,6 +4648,11 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou exit(1); } + if (load_pbx_functions_cli()) { + printf("Failed: load_pbx_functions_cli\n%s", term_quit()); + exit(1); + } + if (ast_local_init()) { printf("Failed: ast_local_init\n%s", term_quit()); exit(1); diff --git a/main/pbx.c b/main/pbx.c index 0ed043cd4..7c58e613a 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -229,17 +229,6 @@ struct ast_app; AST_THREADSTORAGE(switch_data); AST_THREADSTORAGE(extensionstate_buf); -/*! - * \brief A thread local indicating whether the current thread can run - * 'dangerous' dialplan functions. - */ -AST_THREADSTORAGE(thread_inhibit_escalations_tl); - -/*! - * \brief Set to true (non-zero) to globally allow all dangerous dialplan - * functions to run. - */ -static int live_dangerously; /*! \brief ast_exten: An extension @@ -759,13 +748,6 @@ AST_MUTEX_DEFINE_STATIC(maxcalllock); static int countcalls; static int totalcalls; -/*! - * \brief Registered functions container. - * - * It is sorted by function name. - */ -static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); - static struct ast_context *contexts; static struct ast_hashtab *contexts_table = NULL; @@ -3160,632 +3142,6 @@ static struct ast_custom_function exception_function = { .read = acf_exception_read, }; -static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct ast_custom_function *acf; - int count_acf = 0; - int like = 0; - - switch (cmd) { - case CLI_INIT: - e->command = "core show functions [like]"; - e->usage = - "Usage: core show functions [like <text>]\n" - " List builtin functions, optionally only those matching a given string\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) { - like = 1; - } else if (a->argc != 3) { - return CLI_SHOWUSAGE; - } - - ast_cli(a->fd, "%s Custom Functions:\n--------------------------------------------------------------------------------\n", like ? "Matching" : "Installed"); - - AST_RWLIST_RDLOCK(&acf_root); - AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { - if (!like || strstr(acf->name, a->argv[4])) { - count_acf++; - ast_cli(a->fd, "%-20.20s %-35.35s %s\n", - S_OR(acf->name, ""), - S_OR(acf->syntax, ""), - S_OR(acf->synopsis, "")); - } - } - AST_RWLIST_UNLOCK(&acf_root); - - ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : ""); - - return CLI_SUCCESS; -} - -static char *complete_functions(const char *word, int pos, int state) -{ - struct ast_custom_function *cur; - char *ret = NULL; - int which = 0; - int wordlen; - int cmp; - - if (pos != 3) { - return NULL; - } - - wordlen = strlen(word); - AST_RWLIST_RDLOCK(&acf_root); - AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { - /* - * Do a case-insensitive search for convenience in this - * 'complete' function. - * - * We must search the entire container because the functions are - * sorted and normally found case sensitively. - */ - cmp = strncasecmp(word, cur->name, wordlen); - if (!cmp) { - /* Found match. */ - if (++which <= state) { - /* Not enough matches. */ - continue; - } - ret = ast_strdup(cur->name); - break; - } - } - AST_RWLIST_UNLOCK(&acf_root); - - return ret; -} - -static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct ast_custom_function *acf; - /* Maximum number of characters added by terminal coloring is 22 */ - char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40]; - char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL; - char stxtitle[40], *syntax = NULL, *arguments = NULL; - int syntax_size, description_size, synopsis_size, arguments_size, seealso_size; - - switch (cmd) { - case CLI_INIT: - e->command = "core show function"; - e->usage = - "Usage: core show function <function>\n" - " Describe a particular dialplan function.\n"; - return NULL; - case CLI_GENERATE: - return complete_functions(a->word, a->pos, a->n); - } - - if (a->argc != 4) { - return CLI_SHOWUSAGE; - } - - if (!(acf = ast_custom_function_find(a->argv[3]))) { - ast_cli(a->fd, "No function by that name registered.\n"); - return CLI_FAILURE; - } - - syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - if (!(syntax = ast_malloc(syntax_size))) { - ast_cli(a->fd, "Memory allocation failure!\n"); - return CLI_FAILURE; - } - - snprintf(info, sizeof(info), "\n -= Info about function '%s' =- \n\n", acf->name); - term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); - term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); - term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); - term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); - term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); - term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); - term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); -#ifdef AST_XML_DOCS - if (acf->docsrc == AST_XML_DOC) { - arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1); - synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1); - description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1); - seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1); - } else -#endif - { - synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - synopsis = ast_malloc(synopsis_size); - - description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - description = ast_malloc(description_size); - - arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - arguments = ast_malloc(arguments_size); - - seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - seealso = ast_malloc(seealso_size); - - /* check allocated memory. */ - if (!synopsis || !description || !arguments || !seealso) { - ast_free(synopsis); - ast_free(description); - ast_free(arguments); - ast_free(seealso); - ast_free(syntax); - return CLI_FAILURE; - } - - term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); - term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); - term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size); - term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); - } - - ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", - infotitle, syntitle, synopsis, destitle, description, - stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); - - ast_free(arguments); - ast_free(synopsis); - ast_free(description); - ast_free(seealso); - ast_free(syntax); - - return CLI_SUCCESS; -} - -static struct ast_custom_function *ast_custom_function_find_nolock(const char *name) -{ - struct ast_custom_function *cur; - int cmp; - - AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { - cmp = strcmp(name, cur->name); - if (cmp > 0) { - continue; - } - if (!cmp) { - /* Found it. */ - break; - } - /* Not in container. */ - cur = NULL; - break; - } - - return cur; -} - -struct ast_custom_function *ast_custom_function_find(const char *name) -{ - struct ast_custom_function *acf; - - AST_RWLIST_RDLOCK(&acf_root); - acf = ast_custom_function_find_nolock(name); - AST_RWLIST_UNLOCK(&acf_root); - - return acf; -} - -int ast_custom_function_unregister(struct ast_custom_function *acf) -{ - struct ast_custom_function *cur; - - if (!acf) { - return -1; - } - - AST_RWLIST_WRLOCK(&acf_root); - if ((cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist))) { -#ifdef AST_XML_DOCS - if (cur->docsrc == AST_XML_DOC) { - ast_string_field_free_memory(acf); - } -#endif - ast_verb(2, "Unregistered custom function %s\n", cur->name); - } - AST_RWLIST_UNLOCK(&acf_root); - - return cur ? 0 : -1; -} - -/*! - * \brief Returns true if given custom function escalates privileges on read. - * - * \param acf Custom function to query. - * \return True (non-zero) if reads escalate privileges. - * \return False (zero) if reads just read. - */ -static int read_escalates(const struct ast_custom_function *acf) { - return acf->read_escalates; -} - -/*! - * \brief Returns true if given custom function escalates privileges on write. - * - * \param acf Custom function to query. - * \return True (non-zero) if writes escalate privileges. - * \return False (zero) if writes just write. - */ -static int write_escalates(const struct ast_custom_function *acf) { - return acf->write_escalates; -} - -/*! \internal - * \brief Retrieve the XML documentation of a specified ast_custom_function, - * and populate ast_custom_function string fields. - * \param acf ast_custom_function structure with empty 'desc' and 'synopsis' - * but with a function 'name'. - * \retval -1 On error. - * \retval 0 On succes. - */ -static int acf_retrieve_docs(struct ast_custom_function *acf) -{ -#ifdef AST_XML_DOCS - char *tmpxml; - - /* Let's try to find it in the Documentation XML */ - if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) { - return 0; - } - - if (ast_string_field_init(acf, 128)) { - return -1; - } - - /* load synopsis */ - tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, synopsis, tmpxml); - ast_free(tmpxml); - - /* load description */ - tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, desc, tmpxml); - ast_free(tmpxml); - - /* load syntax */ - tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, syntax, tmpxml); - ast_free(tmpxml); - - /* load arguments */ - tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, arguments, tmpxml); - ast_free(tmpxml); - - /* load seealso */ - tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, seealso, tmpxml); - ast_free(tmpxml); - - acf->docsrc = AST_XML_DOC; -#endif - - return 0; -} - -int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod) -{ - struct ast_custom_function *cur; - - if (!acf) { - return -1; - } - - acf->mod = mod; -#ifdef AST_XML_DOCS - acf->docsrc = AST_STATIC_DOC; -#endif - - if (acf_retrieve_docs(acf)) { - return -1; - } - - AST_RWLIST_WRLOCK(&acf_root); - - cur = ast_custom_function_find_nolock(acf->name); - if (cur) { - ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name); - AST_RWLIST_UNLOCK(&acf_root); - return -1; - } - - /* Store in alphabetical order */ - AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) { - if (strcmp(acf->name, cur->name) < 0) { - AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist); - break; - } - } - AST_RWLIST_TRAVERSE_SAFE_END; - if (!cur) { - AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist); - } - - AST_RWLIST_UNLOCK(&acf_root); - - ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name)); - - return 0; -} - -int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod) -{ - int res; - - res = __ast_custom_function_register(acf, mod); - if (res != 0) { - return -1; - } - - switch (escalation) { - case AST_CFE_NONE: - break; - case AST_CFE_READ: - acf->read_escalates = 1; - break; - case AST_CFE_WRITE: - acf->write_escalates = 1; - break; - case AST_CFE_BOTH: - acf->read_escalates = 1; - acf->write_escalates = 1; - break; - } - - return 0; -} - -/*! \brief return a pointer to the arguments of the function, - * and terminates the function name with '\\0' - */ -static char *func_args(char *function) -{ - char *args = strchr(function, '('); - - if (!args) { - ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses. Assuming null argument.\n", function); - } else { - char *p; - *args++ = '\0'; - if ((p = strrchr(args, ')'))) { - *p = '\0'; - } else { - ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args); - } - } - return args; -} - -void pbx_live_dangerously(int new_live_dangerously) -{ - if (new_live_dangerously && !live_dangerously) { - ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n" - "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n"); - } - - if (!new_live_dangerously && live_dangerously) { - ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n"); - } - live_dangerously = new_live_dangerously; -} - -int ast_thread_inhibit_escalations(void) -{ - int *thread_inhibit_escalations; - - thread_inhibit_escalations = ast_threadstorage_get( - &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); - - if (thread_inhibit_escalations == NULL) { - ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n"); - return -1; - } - - *thread_inhibit_escalations = 1; - return 0; -} - -/*! - * \brief Indicates whether the current thread inhibits the execution of - * dangerous functions. - * - * \return True (non-zero) if dangerous function execution is inhibited. - * \return False (zero) if dangerous function execution is allowed. - */ -static int thread_inhibits_escalations(void) -{ - int *thread_inhibit_escalations; - - thread_inhibit_escalations = ast_threadstorage_get( - &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); - - if (thread_inhibit_escalations == NULL) { - ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n"); - /* On error, assume that we are inhibiting */ - return 1; - } - - return *thread_inhibit_escalations; -} - -/*! - * \brief Determines whether execution of a custom function's read function - * is allowed. - * - * \param acfptr Custom function to check - * \return True (non-zero) if reading is allowed. - * \return False (zero) if reading is not allowed. - */ -static int is_read_allowed(struct ast_custom_function *acfptr) -{ - if (!acfptr) { - return 1; - } - - if (!read_escalates(acfptr)) { - return 1; - } - - if (!thread_inhibits_escalations()) { - return 1; - } - - if (live_dangerously) { - /* Global setting overrides the thread's preference */ - ast_debug(2, "Reading %s from a dangerous context\n", - acfptr->name); - return 1; - } - - /* We have no reason to allow this function to execute */ - return 0; -} - -/*! - * \brief Determines whether execution of a custom function's write function - * is allowed. - * - * \param acfptr Custom function to check - * \return True (non-zero) if writing is allowed. - * \return False (zero) if writing is not allowed. - */ -static int is_write_allowed(struct ast_custom_function *acfptr) -{ - if (!acfptr) { - return 1; - } - - if (!write_escalates(acfptr)) { - return 1; - } - - if (!thread_inhibits_escalations()) { - return 1; - } - - if (live_dangerously) { - /* Global setting overrides the thread's preference */ - ast_debug(2, "Writing %s from a dangerous context\n", - acfptr->name); - return 1; - } - - /* We have no reason to allow this function to execute */ - return 0; -} - -int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len) -{ - char *copy = ast_strdupa(function); - char *args = func_args(copy); - struct ast_custom_function *acfptr = ast_custom_function_find(copy); - int res; - struct ast_module_user *u = NULL; - - if (acfptr == NULL) { - ast_log(LOG_ERROR, "Function %s not registered\n", copy); - } else if (!acfptr->read && !acfptr->read2) { - ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); - } else if (!is_read_allowed(acfptr)) { - ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); - } else if (acfptr->read) { - if (acfptr->mod) { - u = __ast_module_user_add(acfptr->mod, chan); - } - res = acfptr->read(chan, copy, args, workspace, len); - if (acfptr->mod && u) { - __ast_module_user_remove(acfptr->mod, u); - } - return res; - } else { - struct ast_str *str = ast_str_create(16); - if (acfptr->mod) { - u = __ast_module_user_add(acfptr->mod, chan); - } - res = acfptr->read2(chan, copy, args, &str, 0); - if (acfptr->mod && u) { - __ast_module_user_remove(acfptr->mod, u); - } - ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len); - ast_free(str); - return res; - } - return -1; -} - -int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen) -{ - char *copy = ast_strdupa(function); - char *args = func_args(copy); - struct ast_custom_function *acfptr = ast_custom_function_find(copy); - int res; - struct ast_module_user *u = NULL; - - if (acfptr == NULL) { - ast_log(LOG_ERROR, "Function %s not registered\n", copy); - } else if (!acfptr->read && !acfptr->read2) { - ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); - } else if (!is_read_allowed(acfptr)) { - ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); - } else { - if (acfptr->mod) { - u = __ast_module_user_add(acfptr->mod, chan); - } - ast_str_reset(*str); - if (acfptr->read2) { - /* ast_str enabled */ - res = acfptr->read2(chan, copy, args, str, maxlen); - } else { - /* Legacy function pointer, allocate buffer for result */ - int maxsize = ast_str_size(*str); - if (maxlen > -1) { - if (maxlen == 0) { - if (acfptr->read_max) { - maxsize = acfptr->read_max; - } else { - maxsize = VAR_BUF_SIZE; - } - } else { - maxsize = maxlen; - } - ast_str_make_space(str, maxsize); - } - res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize); - } - if (acfptr->mod && u) { - __ast_module_user_remove(acfptr->mod, u); - } - return res; - } - return -1; -} - -int ast_func_write(struct ast_channel *chan, const char *function, const char *value) -{ - char *copy = ast_strdupa(function); - char *args = func_args(copy); - struct ast_custom_function *acfptr = ast_custom_function_find(copy); - - if (acfptr == NULL) { - ast_log(LOG_ERROR, "Function %s not registered\n", copy); - } else if (!acfptr->write) { - ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy); - } else if (!is_write_allowed(acfptr)) { - ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy); - } else { - int res; - struct ast_module_user *u = NULL; - if (acfptr->mod) - u = __ast_module_user_add(acfptr->mod, chan); - res = acfptr->write(chan, copy, args, value); - if (acfptr->mod && u) - __ast_module_user_remove(acfptr->mod, u); - return res; - } - - return -1; -} - void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used) { /* Substitutes variables into buf, based on string templ */ @@ -8057,7 +7413,6 @@ static struct ast_cli_entry pbx_cli[] = { AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"), #endif AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"), - AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"), AST_CLI_DEFINE(handle_show_switches, "Show alternative switches"), AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"), AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"), @@ -8066,7 +7421,6 @@ static struct ast_cli_entry pbx_cli[] = { AST_CLI_DEFINE(handle_show_device2extenstate, "Show expected exten state from multiple device states"), #endif AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"), - AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"), AST_CLI_DEFINE(handle_show_hangup_all, "Show hangup handlers of all channels"), AST_CLI_DEFINE(handle_show_hangup_channel, "Show hangup handlers of a specified channel"), AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"), diff --git a/main/pbx_functions.c b/main/pbx_functions.c new file mode 100644 index 000000000..b8be2bc40 --- /dev/null +++ b/main/pbx_functions.c @@ -0,0 +1,723 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, CFWare, LLC + * + * Corey Farrell <git@cfware.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Custom function management routines. + * + * \author Corey Farrell <git@cfware.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/_private.h" +#include "asterisk/cli.h" +#include "asterisk/linkedlists.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/term.h" +#include "asterisk/threadstorage.h" +#include "asterisk/xmldoc.h" +#include "pbx_private.h" + +/*! + * \brief A thread local indicating whether the current thread can run + * 'dangerous' dialplan functions. + */ +AST_THREADSTORAGE(thread_inhibit_escalations_tl); + +/*! + * \brief Set to true (non-zero) to globally allow all dangerous dialplan + * functions to run. + */ +static int live_dangerously; + +/*! + * \brief Registered functions container. + * + * It is sorted by function name. + */ +static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); + +static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_custom_function *acf; + int count_acf = 0; + int like = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "core show functions [like]"; + e->usage = + "Usage: core show functions [like <text>]\n" + " List builtin functions, optionally only those matching a given string\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) { + like = 1; + } else if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(a->fd, "%s Custom Functions:\n" + "--------------------------------------------------------------------------------\n", + like ? "Matching" : "Installed"); + + AST_RWLIST_RDLOCK(&acf_root); + AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { + if (!like || strstr(acf->name, a->argv[4])) { + count_acf++; + ast_cli(a->fd, "%-20.20s %-35.35s %s\n", + S_OR(acf->name, ""), + S_OR(acf->syntax, ""), + S_OR(acf->synopsis, "")); + } + } + AST_RWLIST_UNLOCK(&acf_root); + + ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : ""); + + return CLI_SUCCESS; +} + +static char *complete_functions(const char *word, int pos, int state) +{ + struct ast_custom_function *cur; + char *ret = NULL; + int which = 0; + int wordlen; + int cmp; + + if (pos != 3) { + return NULL; + } + + wordlen = strlen(word); + AST_RWLIST_RDLOCK(&acf_root); + AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { + /* + * Do a case-insensitive search for convenience in this + * 'complete' function. + * + * We must search the entire container because the functions are + * sorted and normally found case sensitively. + */ + cmp = strncasecmp(word, cur->name, wordlen); + if (!cmp) { + /* Found match. */ + if (++which <= state) { + /* Not enough matches. */ + continue; + } + ret = ast_strdup(cur->name); + break; + } + } + AST_RWLIST_UNLOCK(&acf_root); + + return ret; +} + +static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_custom_function *acf; + /* Maximum number of characters added by terminal coloring is 22 */ + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL; + char stxtitle[40], *syntax = NULL, *arguments = NULL; + int syntax_size, description_size, synopsis_size, arguments_size, seealso_size; + + switch (cmd) { + case CLI_INIT: + e->command = "core show function"; + e->usage = + "Usage: core show function <function>\n" + " Describe a particular dialplan function.\n"; + return NULL; + case CLI_GENERATE: + return complete_functions(a->word, a->pos, a->n); + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (!(acf = ast_custom_function_find(a->argv[3]))) { + ast_cli(a->fd, "No function by that name registered.\n"); + + return CLI_FAILURE; + } + + syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + syntax = ast_malloc(syntax_size); + if (!syntax) { + ast_cli(a->fd, "Memory allocation failure!\n"); + + return CLI_FAILURE; + } + + snprintf(info, sizeof(info), "\n -= Info about function '%s' =- \n\n", acf->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); + term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); + term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); + term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); +#ifdef AST_XML_DOCS + if (acf->docsrc == AST_XML_DOC) { + arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1); + synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1); + description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1); + seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1); + } else +#endif + { + synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + synopsis = ast_malloc(synopsis_size); + + description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + description = ast_malloc(description_size); + + arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + arguments = ast_malloc(arguments_size); + + seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + seealso = ast_malloc(seealso_size); + + /* check allocated memory. */ + if (!synopsis || !description || !arguments || !seealso) { + ast_free(synopsis); + ast_free(description); + ast_free(arguments); + ast_free(seealso); + ast_free(syntax); + + return CLI_FAILURE; + } + + term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); + term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); + term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size); + term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); + } + + ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", + infotitle, syntitle, synopsis, destitle, description, + stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); + + ast_free(arguments); + ast_free(synopsis); + ast_free(description); + ast_free(seealso); + ast_free(syntax); + + return CLI_SUCCESS; +} + +static struct ast_custom_function *ast_custom_function_find_nolock(const char *name) +{ + struct ast_custom_function *cur; + int cmp; + + AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { + cmp = strcmp(name, cur->name); + if (cmp > 0) { + continue; + } + if (!cmp) { + /* Found it. */ + break; + } + /* Not in container. */ + cur = NULL; + break; + } + + return cur; +} + +struct ast_custom_function *ast_custom_function_find(const char *name) +{ + struct ast_custom_function *acf; + + AST_RWLIST_RDLOCK(&acf_root); + acf = ast_custom_function_find_nolock(name); + AST_RWLIST_UNLOCK(&acf_root); + + return acf; +} + +int ast_custom_function_unregister(struct ast_custom_function *acf) +{ + struct ast_custom_function *cur; + + if (!acf) { + return -1; + } + + AST_RWLIST_WRLOCK(&acf_root); + cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist); + if (cur) { +#ifdef AST_XML_DOCS + if (cur->docsrc == AST_XML_DOC) { + ast_string_field_free_memory(acf); + } +#endif + ast_verb(2, "Unregistered custom function %s\n", cur->name); + } + AST_RWLIST_UNLOCK(&acf_root); + + return cur ? 0 : -1; +} + +/*! + * \brief Returns true if given custom function escalates privileges on read. + * + * \param acf Custom function to query. + * \return True (non-zero) if reads escalate privileges. + * \return False (zero) if reads just read. + */ +static int read_escalates(const struct ast_custom_function *acf) { + return acf->read_escalates; +} + +/*! + * \brief Returns true if given custom function escalates privileges on write. + * + * \param acf Custom function to query. + * \return True (non-zero) if writes escalate privileges. + * \return False (zero) if writes just write. + */ +static int write_escalates(const struct ast_custom_function *acf) { + return acf->write_escalates; +} + +/*! \internal + * \brief Retrieve the XML documentation of a specified ast_custom_function, + * and populate ast_custom_function string fields. + * \param acf ast_custom_function structure with empty 'desc' and 'synopsis' + * but with a function 'name'. + * \retval -1 On error. + * \retval 0 On succes. + */ +static int acf_retrieve_docs(struct ast_custom_function *acf) +{ +#ifdef AST_XML_DOCS + char *tmpxml; + + /* Let's try to find it in the Documentation XML */ + if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) { + return 0; + } + + if (ast_string_field_init(acf, 128)) { + return -1; + } + + /* load synopsis */ + tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, synopsis, tmpxml); + ast_free(tmpxml); + + /* load description */ + tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, desc, tmpxml); + ast_free(tmpxml); + + /* load syntax */ + tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, syntax, tmpxml); + ast_free(tmpxml); + + /* load arguments */ + tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, arguments, tmpxml); + ast_free(tmpxml); + + /* load seealso */ + tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, seealso, tmpxml); + ast_free(tmpxml); + + acf->docsrc = AST_XML_DOC; +#endif + + return 0; +} + +int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod) +{ + struct ast_custom_function *cur; + + if (!acf) { + return -1; + } + + acf->mod = mod; +#ifdef AST_XML_DOCS + acf->docsrc = AST_STATIC_DOC; +#endif + + if (acf_retrieve_docs(acf)) { + return -1; + } + + AST_RWLIST_WRLOCK(&acf_root); + + cur = ast_custom_function_find_nolock(acf->name); + if (cur) { + ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name); + AST_RWLIST_UNLOCK(&acf_root); + return -1; + } + + /* Store in alphabetical order */ + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) { + if (strcmp(acf->name, cur->name) < 0) { + AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist); + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + if (!cur) { + AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist); + } + + AST_RWLIST_UNLOCK(&acf_root); + + ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name)); + + return 0; +} + +int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod) +{ + int res; + + res = __ast_custom_function_register(acf, mod); + if (res != 0) { + return -1; + } + + switch (escalation) { + case AST_CFE_NONE: + break; + case AST_CFE_READ: + acf->read_escalates = 1; + break; + case AST_CFE_WRITE: + acf->write_escalates = 1; + break; + case AST_CFE_BOTH: + acf->read_escalates = 1; + acf->write_escalates = 1; + break; + } + + return 0; +} + +/*! \brief return a pointer to the arguments of the function, + * and terminates the function name with '\\0' + */ +static char *func_args(char *function) +{ + char *args = strchr(function, '('); + + if (!args) { + ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses. Assuming null argument.\n", function); + } else { + char *p; + *args++ = '\0'; + if ((p = strrchr(args, ')'))) { + *p = '\0'; + } else { + ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args); + } + } + return args; +} + +void pbx_live_dangerously(int new_live_dangerously) +{ + if (new_live_dangerously && !live_dangerously) { + ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n" + "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n"); + } + + if (!new_live_dangerously && live_dangerously) { + ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n"); + } + live_dangerously = new_live_dangerously; +} + +int ast_thread_inhibit_escalations(void) +{ + int *thread_inhibit_escalations; + + thread_inhibit_escalations = ast_threadstorage_get( + &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); + + if (thread_inhibit_escalations == NULL) { + ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n"); + return -1; + } + + *thread_inhibit_escalations = 1; + return 0; +} + +/*! + * \brief Indicates whether the current thread inhibits the execution of + * dangerous functions. + * + * \return True (non-zero) if dangerous function execution is inhibited. + * \return False (zero) if dangerous function execution is allowed. + */ +static int thread_inhibits_escalations(void) +{ + int *thread_inhibit_escalations; + + thread_inhibit_escalations = ast_threadstorage_get( + &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); + + if (thread_inhibit_escalations == NULL) { + ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n"); + /* On error, assume that we are inhibiting */ + return 1; + } + + return *thread_inhibit_escalations; +} + +/*! + * \brief Determines whether execution of a custom function's read function + * is allowed. + * + * \param acfptr Custom function to check + * \return True (non-zero) if reading is allowed. + * \return False (zero) if reading is not allowed. + */ +static int is_read_allowed(struct ast_custom_function *acfptr) +{ + if (!acfptr) { + return 1; + } + + if (!read_escalates(acfptr)) { + return 1; + } + + if (!thread_inhibits_escalations()) { + return 1; + } + + if (live_dangerously) { + /* Global setting overrides the thread's preference */ + ast_debug(2, "Reading %s from a dangerous context\n", + acfptr->name); + return 1; + } + + /* We have no reason to allow this function to execute */ + return 0; +} + +/*! + * \brief Determines whether execution of a custom function's write function + * is allowed. + * + * \param acfptr Custom function to check + * \return True (non-zero) if writing is allowed. + * \return False (zero) if writing is not allowed. + */ +static int is_write_allowed(struct ast_custom_function *acfptr) +{ + if (!acfptr) { + return 1; + } + + if (!write_escalates(acfptr)) { + return 1; + } + + if (!thread_inhibits_escalations()) { + return 1; + } + + if (live_dangerously) { + /* Global setting overrides the thread's preference */ + ast_debug(2, "Writing %s from a dangerous context\n", + acfptr->name); + return 1; + } + + /* We have no reason to allow this function to execute */ + return 0; +} + +int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len) +{ + char *copy = ast_strdupa(function); + char *args = func_args(copy); + struct ast_custom_function *acfptr = ast_custom_function_find(copy); + int res; + struct ast_module_user *u = NULL; + + if (acfptr == NULL) { + ast_log(LOG_ERROR, "Function %s not registered\n", copy); + } else if (!acfptr->read && !acfptr->read2) { + ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); + } else if (!is_read_allowed(acfptr)) { + ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); + } else if (acfptr->read) { + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + res = acfptr->read(chan, copy, args, workspace, len); + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + + return res; + } else { + struct ast_str *str = ast_str_create(16); + + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + res = acfptr->read2(chan, copy, args, &str, 0); + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len); + ast_free(str); + + return res; + } + + return -1; +} + +int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen) +{ + char *copy = ast_strdupa(function); + char *args = func_args(copy); + struct ast_custom_function *acfptr = ast_custom_function_find(copy); + int res; + struct ast_module_user *u = NULL; + + if (acfptr == NULL) { + ast_log(LOG_ERROR, "Function %s not registered\n", copy); + } else if (!acfptr->read && !acfptr->read2) { + ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); + } else if (!is_read_allowed(acfptr)) { + ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); + } else { + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + ast_str_reset(*str); + if (acfptr->read2) { + /* ast_str enabled */ + res = acfptr->read2(chan, copy, args, str, maxlen); + } else { + /* Legacy function pointer, allocate buffer for result */ + int maxsize = ast_str_size(*str); + + if (maxlen > -1) { + if (maxlen == 0) { + if (acfptr->read_max) { + maxsize = acfptr->read_max; + } else { + maxsize = VAR_BUF_SIZE; + } + } else { + maxsize = maxlen; + } + ast_str_make_space(str, maxsize); + } + res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize); + } + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + + return res; + } + + return -1; +} + +int ast_func_write(struct ast_channel *chan, const char *function, const char *value) +{ + char *copy = ast_strdupa(function); + char *args = func_args(copy); + struct ast_custom_function *acfptr = ast_custom_function_find(copy); + + if (acfptr == NULL) { + ast_log(LOG_ERROR, "Function %s not registered\n", copy); + } else if (!acfptr->write) { + ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy); + } else if (!is_write_allowed(acfptr)) { + ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy); + } else { + int res; + struct ast_module_user *u = NULL; + + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + res = acfptr->write(chan, copy, args, value); + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + + return res; + } + + return -1; +} + +static struct ast_cli_entry acf_cli[] = { + AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"), + AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"), +}; + +static void unload_pbx_functions_cli(void) +{ + ast_cli_unregister_multiple(acf_cli, ARRAY_LEN(acf_cli)); +} + +int load_pbx_functions_cli(void) +{ + ast_cli_register_multiple(acf_cli, ARRAY_LEN(acf_cli)); + ast_register_cleanup(unload_pbx_functions_cli); + + return 0; +} |