Skip to content
Snippets Groups Projects
app.c 84.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2005, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * 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.
     */
    
    
     * \brief Convenient Application Routines
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
     * \author Mark Spencer <markster@digium.com>
    
    /** \example
     * \par This is an example of how to develop an app.
     * Application Skeleton is an example of creating an application for Asterisk.
     * \verbinclude app_skel.c
    
    Andrew Latham's avatar
    Andrew Latham committed
     */
    
    
    /*** MODULEINFO
    	<support_level>core</support_level>
     ***/
    
    
    ASTERISK_REGISTER_FILE()
    
    #ifdef HAVE_SYS_STAT_H
    
    #include <sys/stat.h>
    
    #endif
    
    #include <regex.h>          /* for regcomp(3) */
    #include <sys/file.h>       /* for flock(2) */
    #include <signal.h>         /* for pthread_sigmask(3) */
    
    #include <stdlib.h>         /* for closefrom(3) */
    
    #include <sys/types.h>
    #include <sys/wait.h>       /* for waitpid(2) */
    #ifndef HAVE_CLOSEFROM
    #include <dirent.h>         /* for opendir(3)   */
    #endif
    
    #ifdef HAVE_CAP
    #include <sys/capability.h>
    #endif /* HAVE_CAP */
    
    #include "asterisk/paths.h"	/* use ast_config_AST_DATA_DIR */
    
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    #include "asterisk/file.h"
    #include "asterisk/app.h"
    #include "asterisk/dsp.h"
    #include "asterisk/utils.h"
    #include "asterisk/lock.h"
    #include "asterisk/indications.h"
    
    #include "asterisk/linkedlists.h"
    
    #include "asterisk/threadstorage.h"
    
    #include "asterisk/test.h"
    
    #include "asterisk/astobj2.h"
    #include "asterisk/stasis.h"
    
    #include "asterisk/stasis_channels.h"
    #include "asterisk/json.h"
    
    
    #define MWI_TOPIC_BUCKETS 57
    
    AST_THREADSTORAGE_PUBLIC(ast_str_thread_global_buf);
    
    static pthread_t shaun_of_the_dead_thread = AST_PTHREADT_NULL;
    
    struct zombie {
    	pid_t pid;
    	AST_LIST_ENTRY(zombie) list;
    };
    
    static AST_LIST_HEAD_STATIC(zombies, zombie);
    
    
     * @{ \brief Define \ref stasis topic objects
    
    static struct stasis_topic *mwi_topic_all;
    
    static struct stasis_cache *mwi_state_cache;
    
    static struct stasis_caching_topic *mwi_topic_cached;
    static struct stasis_topic_pool *mwi_topic_pool;
    
    
    static struct stasis_topic *queue_topic_all;
    static struct stasis_topic_pool *queue_topic_pool;
    
    /*! \brief Convert a MWI \ref stasis_message to a \ref ast_event */
    static struct ast_event *mwi_to_event(struct stasis_message *message)
    {
    	struct ast_event *event;
    	struct ast_mwi_state *mwi_state;
    	char *mailbox;
    	char *context;
    
    	if (!message) {
    		return NULL;
    	}
    
    	mwi_state = stasis_message_data(message);
    
    	/* Strip off @context */
    	context = mailbox = ast_strdupa(mwi_state->uniqueid);
    	strsep(&context, "@");
    	if (ast_strlen_zero(context)) {
    		context = "default";
    	}
    
    	event = ast_event_new(AST_EVENT_MWI,
    				AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
    				AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
    				AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, mwi_state->new_msgs,
    				AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, mwi_state->old_msgs,
    				AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, &mwi_state->eid, sizeof(mwi_state->eid),
    				AST_EVENT_IE_END);
    
    	return event;
    }
    
    
    /*
     * @{ \brief Define \ref stasis message types for MWI
     */
    
    STASIS_MESSAGE_TYPE_DEFN(ast_mwi_state_type,
    	.to_event = mwi_to_event, );
    
    STASIS_MESSAGE_TYPE_DEFN(ast_mwi_vm_app_type);
    /* @} */
    
    
    static void *shaun_of_the_dead(void *data)
    {
    	struct zombie *cur;
    	int status;
    	for (;;) {
    		if (!AST_LIST_EMPTY(&zombies)) {
    			/* Don't allow cancellation while we have a lock. */
    			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    			AST_LIST_LOCK(&zombies);
    			AST_LIST_TRAVERSE_SAFE_BEGIN(&zombies, cur, list) {
    				if (waitpid(cur->pid, &status, WNOHANG) != 0) {
    					AST_LIST_REMOVE_CURRENT(list);
    					ast_free(cur);
    				}
    			}
    			AST_LIST_TRAVERSE_SAFE_END
    			AST_LIST_UNLOCK(&zombies);
    			pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    		}
    		pthread_testcancel();
    		/* Wait for 60 seconds, without engaging in a busy loop. */
    		ast_poll(NULL, 0, AST_LIST_FIRST(&zombies) ? 5000 : 60000);
    	}
    	return NULL;
    }
    
    
    #define AST_MAX_FORMATS 10
    
    static AST_RWLIST_HEAD_STATIC(groups, ast_group_info);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
     * \brief This function presents a dialtone and reads an extension into 'collect'
     * which must be a pointer to a **pre-initialized** array of char having a
     * size of 'size' suitable for writing to.  It will collect no more than the smaller
    
     * of 'maxlen' or 'size' minus the original strlen() of collect digits.
     * \param chan struct.
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
     * \param context
     * \param collect
     * \param size
    
     * \param timeout timeout in milliseconds
    
     *
     * \return 0 if extension does not exist, 1 if extension exists
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect, size_t size, int maxlen, int timeout)
    
    	struct ast_tone_zone_sound *ts;
    
    	int res = 0, x = 0;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (maxlen > size) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		maxlen = size;
    
    		if (ast_channel_pbx(chan) && ast_channel_pbx(chan)->dtimeoutms) {
    			timeout = ast_channel_pbx(chan)->dtimeoutms;
    
    		} else {
    			timeout = 5000;
    		}
    
    	if ((ts = ast_get_indication_tone(ast_channel_zone(chan), "dial"))) {
    
    		res = ast_playtones_start(chan, 0, ts->data, 0);
    
    		ts = ast_tone_zone_sound_unref(ts);
    	} else {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		ast_log(LOG_NOTICE, "Huh....? no dial for indications?\n");
    
    	for (x = strlen(collect); x < maxlen; ) {
    
    		res = ast_waitfordigit(chan, timeout);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (!ast_ignore_pattern(context, collect)) {
    
    			ast_playtones_stop(chan);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    		if (res < 1) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    		if (res == '#') {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    
    		if (!ast_matchmore_extension(chan, context, collect, 1,
    
    			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (res >= 0) {
    
    		res = ast_exists_extension(chan, context, collect, 1,
    
    			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) ? 1 : 0;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    /*!
     * \brief ast_app_getdata
     * \param c The channel to read from
     * \param prompt The file to stream to the channel
     * \param s The string to read in to.  Must be at least the size of your length
     * \param maxlen How many digits to read (maximum)
    
     * \param timeout set timeout to 0 for "standard" timeouts. Set timeout to -1 for
    
     *      "ludicrous time" (essentially never times out) */
    
    enum ast_getdata_result ast_app_getdata(struct ast_channel *c, const char *prompt, char *s, int maxlen, int timeout)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	/* XXX Merge with full version? XXX */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		prompt = "";
    
    
    	filename = ast_strdupa(prompt);
    	while ((front = strsep(&filename, "&"))) {
    
    			res = ast_streamfile(c, front, ast_channel_language(c));
    
    		if (ast_strlen_zero(filename)) {
    			/* set timeouts for the last prompt */
    
    			fto = ast_channel_pbx(c) ? ast_channel_pbx(c)->rtimeoutms : 6000;
    			to = ast_channel_pbx(c) ? ast_channel_pbx(c)->dtimeoutms : 2000;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			if (timeout > 0) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			}
    			if (timeout < 0) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			}
    
    		} else {
    			/* there is more than one prompt, so
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			 * get rid of the long timeout between
    			 * prompts, and make it 50ms */
    
    			to = ast_channel_pbx(c) ? ast_channel_pbx(c)->dtimeoutms : 2000;
    
    		}
    		res = ast_readstring(c, s, maxlen, to, fto, "#");
    
    		if (res == AST_GETDATA_EMPTY_END_TERMINATED) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			return res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    /* The lock type used by ast_lock_path() / ast_unlock_path() */
    static enum AST_LOCK_TYPE ast_lock_type = AST_LOCK_TYPE_LOCKFILE;
    
    int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int maxlen, int timeout, int audiofd, int ctrlfd)
    
    	int res, to = 2000, fto = 6000;
    
    	if (!ast_strlen_zero(prompt)) {
    
    		res = ast_streamfile(c, prompt, ast_channel_language(c));
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (res < 0) {
    
    			return res;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    
    	if (timeout > 0) {
    
    		fto = to = timeout;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    	if (timeout < 0) {
    
    		fto = to = 1000000000;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    	res = ast_readstring_full(c, s, maxlen, to, fto, "#", audiofd, ctrlfd);
    
    int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_args)
    
    	struct ast_app *macro_app;
    	int res;
    
    	macro_app = pbx_findapp("Macro");
    	if (!macro_app) {
    		ast_log(LOG_WARNING,
    			"Cannot run 'Macro(%s)'.  The application is not available.\n", macro_args);
    		return -1;
    	}
    	if (autoservice_chan) {
    		ast_autoservice_start(autoservice_chan);
    	}
    
    	ast_debug(4, "%s Original location: %s,%s,%d\n", ast_channel_name(macro_chan),
    		ast_channel_context(macro_chan), ast_channel_exten(macro_chan),
    		ast_channel_priority(macro_chan));
    
    	res = pbx_exec(macro_chan, macro_app, macro_args);
    	ast_debug(4, "Macro exited with status %d\n", res);
    
    	/*
    	 * Assume anything negative from Macro is an error.
    	 * Anything else is success.
    	 */
    	if (res < 0) {
    		res = -1;
    	} else {
    		res = 0;
    	}
    
    	ast_debug(4, "%s Ending location: %s,%s,%d\n", ast_channel_name(macro_chan),
    		ast_channel_context(macro_chan), ast_channel_exten(macro_chan),
    		ast_channel_priority(macro_chan));
    
    	if (autoservice_chan) {
    		ast_autoservice_stop(autoservice_chan);
    	}
    	return res;
    }
    
    int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_name, const char *macro_args)
    {
    	int res;
    	char *args_str;
    	size_t args_len;
    
    	if (ast_strlen_zero(macro_args)) {
    		return ast_app_exec_macro(autoservice_chan, macro_chan, macro_name);
    	}
    
    	/* Create the Macro application argument string. */
    	args_len = strlen(macro_name) + strlen(macro_args) + 2;
    	args_str = ast_malloc(args_len);
    	if (!args_str) {
    		return -1;
    	}
    	snprintf(args_str, args_len, "%s,%s", macro_name, macro_args);
    
    	res = ast_app_exec_macro(autoservice_chan, macro_chan, args_str);
    	ast_free(args_str);
    	return res;
    }
    
    static const struct ast_app_stack_funcs *app_stack_callbacks;
    
    void ast_install_stack_functions(const struct ast_app_stack_funcs *funcs)
    
    const char *ast_app_expand_sub_args(struct ast_channel *chan, const char *args)
    {
    	const struct ast_app_stack_funcs *funcs;
    	const char *new_args;
    
    	funcs = app_stack_callbacks;
    	if (!funcs || !funcs->expand_sub_args) {
    
    			"Cannot expand 'Gosub(%s)' arguments.  The app_stack module is not available.\n",
    			args);
    		return NULL;
    
    	new_args = funcs->expand_sub_args(chan, args);
    	ast_module_unref(funcs->module);
    	return new_args;
    }
    
    int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args, int ignore_hangup)
    {
    	const struct ast_app_stack_funcs *funcs;
    	int res;
    
    	funcs = app_stack_callbacks;
    	if (!funcs || !funcs->run_sub) {
    		ast_log(LOG_WARNING,
    			"Cannot run 'Gosub(%s)'.  The app_stack module is not available.\n",
    			sub_args);
    		return -1;
    
    	if (autoservice_chan) {
    		ast_autoservice_start(autoservice_chan);
    	}
    
    	res = funcs->run_sub(sub_chan, sub_args, ignore_hangup);
    	ast_module_unref(funcs->module);
    
    	if (autoservice_chan) {
    		ast_autoservice_stop(autoservice_chan);
    	}
    	return res;
    }
    
    
    int ast_app_run_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_location, const char *sub_args, int ignore_hangup)
    
    		return ast_app_exec_sub(autoservice_chan, sub_chan, sub_location, ignore_hangup);
    
    
    	/* Create the Gosub application argument string. */
    	args_len = strlen(sub_location) + strlen(sub_args) + 3;
    	args_str = ast_malloc(args_len);
    	if (!args_str) {
    		return -1;
    
    	snprintf(args_str, args_len, "%s(%s)", sub_location, sub_args);
    
    
    	res = ast_app_exec_sub(autoservice_chan, sub_chan, args_str, ignore_hangup);
    
    /*! \brief The container for the voicemail provider */
    static AO2_GLOBAL_OBJ_STATIC(vm_provider);
    
    /*! Voicemail not registered warning */
    static int vm_warnings;
    
    
    int ast_vm_is_registered(void)
    {
    	struct ast_vm_functions *table;
    	int is_registered;
    
    	table = ao2_global_obj_ref(vm_provider);
    	is_registered = table ? 1 : 0;
    	ao2_cleanup(table);
    	return is_registered;
    }
    
    
    int __ast_vm_register(const struct ast_vm_functions *vm_table, struct ast_module *module)
    
    	RAII_VAR(struct ast_vm_functions *, table, NULL, ao2_cleanup);
    
    	if (!vm_table->module_name) {
    		ast_log(LOG_ERROR, "Voicemail provider missing required information.\n");
    		return -1;
    	}
    	if (vm_table->module_version != VM_MODULE_VERSION) {
    		ast_log(LOG_ERROR, "Voicemail provider '%s' has incorrect version\n",
    			vm_table->module_name);
    		return -1;
    	}
    
    	table = ao2_global_obj_ref(vm_provider);
    	if (table) {
    		ast_log(LOG_WARNING, "Voicemail provider already registered by %s.\n",
    			table->module_name);
    		return -1;
    	}
    
    	table = ao2_alloc_options(sizeof(*table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
    	if (!table) {
    		return -1;
    	}
    	*table = *vm_table;
    	table->module = module;
    
    	ao2_global_obj_replace_unref(vm_provider, table);
    	return 0;
    
    void ast_vm_unregister(const char *module_name)
    
    	struct ast_vm_functions *table;
    
    	table = ao2_global_obj_ref(vm_provider);
    	if (table && !strcmp(table->module_name, module_name)) {
    		ao2_global_obj_release(vm_provider);
    	}
    	ao2_cleanup(table);
    
    #ifdef TEST_FRAMEWORK
    /*! \brief Holding container for the voicemail provider used while testing */
    static AO2_GLOBAL_OBJ_STATIC(vm_provider_holder);
    static int provider_is_swapped = 0;
    
    void ast_vm_test_swap_table_in(const struct ast_vm_functions *vm_table)
    {
    	RAII_VAR(struct ast_vm_functions *, holding_table, NULL, ao2_cleanup);
    	RAII_VAR(struct ast_vm_functions *, new_table, NULL, ao2_cleanup);
    
    	if (provider_is_swapped) {
    		ast_log(LOG_ERROR, "Attempted to swap in test function table without swapping out old test table.\n");
    		return;
    	}
    
    	holding_table = ao2_global_obj_ref(vm_provider);
    
    	if (holding_table) {
    		ao2_global_obj_replace_unref(vm_provider_holder, holding_table);
    	}
    
    	new_table = ao2_alloc_options(sizeof(*new_table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
    	if (!new_table) {
    		return;
    	}
    	*new_table = *vm_table;
    
    	ao2_global_obj_replace_unref(vm_provider, new_table);
    	provider_is_swapped = 1;
    }
    
    void ast_vm_test_swap_table_out(void)
    {
    	RAII_VAR(struct ast_vm_functions *, held_table, NULL, ao2_cleanup);
    
    	if (!provider_is_swapped) {
    		ast_log(LOG_ERROR, "Attempted to swap out test function table, but none is currently installed.\n");
    		return;
    	}
    
    	held_table = ao2_global_obj_ref(vm_provider_holder);
    	if (!held_table) {
    		return;
    	}
    
    	ao2_global_obj_replace_unref(vm_provider, held_table);
    	ao2_global_obj_release(vm_provider_holder);
    	provider_is_swapped = 0;
    }
    #endif
    
    
    /*! \brief The container for the voicemail greeter provider */
    static AO2_GLOBAL_OBJ_STATIC(vm_greeter_provider);
    
    /*! Voicemail greeter not registered warning */
    static int vm_greeter_warnings;
    
    int ast_vm_greeter_is_registered(void)
    {
    	struct ast_vm_greeter_functions *table;
    	int is_registered;
    
    	table = ao2_global_obj_ref(vm_greeter_provider);
    	is_registered = table ? 1 : 0;
    	ao2_cleanup(table);
    	return is_registered;
    }
    
    int __ast_vm_greeter_register(const struct ast_vm_greeter_functions *vm_table, struct ast_module *module)
    {
    	RAII_VAR(struct ast_vm_greeter_functions *, table, NULL, ao2_cleanup);
    
    	if (!vm_table->module_name) {
    		ast_log(LOG_ERROR, "Voicemail greeter provider missing required information.\n");
    		return -1;
    	}
    	if (vm_table->module_version != VM_GREETER_MODULE_VERSION) {
    		ast_log(LOG_ERROR, "Voicemail greeter provider '%s' has incorrect version\n",
    			vm_table->module_name);
    		return -1;
    	}
    
    	table = ao2_global_obj_ref(vm_greeter_provider);
    	if (table) {
    		ast_log(LOG_WARNING, "Voicemail greeter provider already registered by %s.\n",
    			table->module_name);
    		return -1;
    	}
    
    	table = ao2_alloc_options(sizeof(*table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
    	if (!table) {
    		return -1;
    	}
    	*table = *vm_table;
    	table->module = module;
    
    	ao2_global_obj_replace_unref(vm_greeter_provider, table);
    	return 0;
    }
    
    void ast_vm_greeter_unregister(const char *module_name)
    {
    	struct ast_vm_greeter_functions *table;
    
    	table = ao2_global_obj_ref(vm_greeter_provider);
    	if (table && !strcmp(table->module_name, module_name)) {
    		ao2_global_obj_release(vm_greeter_provider);
    	}
    	ao2_cleanup(table);
    }
    
    
    #ifdef TEST_FRAMEWORK
    
    static ast_vm_test_create_user_fn *ast_vm_test_create_user_func = NULL;
    static ast_vm_test_destroy_user_fn *ast_vm_test_destroy_user_func = NULL;
    
    void ast_install_vm_test_functions(ast_vm_test_create_user_fn *vm_test_create_user_func,
    	ast_vm_test_destroy_user_fn *vm_test_destroy_user_func)
    
    {
    	ast_vm_test_create_user_func = vm_test_create_user_func;
    	ast_vm_test_destroy_user_func = vm_test_destroy_user_func;
    }
    
    void ast_uninstall_vm_test_functions(void)
    {
    	ast_vm_test_create_user_func = NULL;
    	ast_vm_test_destroy_user_func = NULL;
    }
    #endif
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if (vm_warnings++ % 10 == 0) {
    		ast_verb(3, "No voicemail provider registered.\n");
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    #define VM_API_CALL(res, api_call, api_parms)								\
    	do {																	\
    
    		struct ast_vm_functions *table;										\
    		table = ao2_global_obj_ref(vm_provider);							\
    
    		if (!table) {														\
    			vm_warn_no_provider();											\
    		} else if (table->api_call) {										\
    			ast_module_ref(table->module);									\
    			(res) = table->api_call api_parms;								\
    			ast_module_unref(table->module);								\
    		}																	\
    		ao2_cleanup(table);													\
    	} while (0)
    
    
    static void vm_greeter_warn_no_provider(void)
    {
    	if (vm_greeter_warnings++ % 10 == 0) {
    		ast_verb(3, "No voicemail greeter provider registered.\n");
    	}
    }
    
    #define VM_GREETER_API_CALL(res, api_call, api_parms)						\
    	do {																	\
    		struct ast_vm_greeter_functions *table;								\
    		table = ao2_global_obj_ref(vm_greeter_provider);					\
    		if (!table) {														\
    			vm_greeter_warn_no_provider();									\
    		} else if (table->api_call) {										\
    			ast_module_ref(table->module);									\
    			(res) = table->api_call api_parms;								\
    			ast_module_unref(table->module);								\
    		}																	\
    		ao2_cleanup(table);													\
    	} while (0)
    
    
    int ast_app_has_voicemail(const char *mailboxes, const char *folder)
    {
    	int res = 0;
    
    	VM_API_CALL(res, has_voicemail, (mailboxes, folder));
    	return res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    /*!
     * \internal
     * \brief Function used as a callback for ast_copy_recording_to_vm when a real one isn't installed.
     * \param vm_rec_data Stores crucial information about the voicemail that will basically just be used
     * to figure out what the name of the recipient was supposed to be
     */
    int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data)
    {
    
    	VM_API_CALL(res, copy_recording_to_vm, (vm_rec_data));
    	return res;
    
    int ast_app_inboxcount(const char *mailboxes, int *newmsgs, int *oldmsgs)
    
    	VM_API_CALL(res, inboxcount, (mailboxes, newmsgs, oldmsgs));
    	return res;
    
    int ast_app_inboxcount2(const char *mailboxes, int *urgentmsgs, int *newmsgs, int *oldmsgs)
    
    	if (newmsgs) {
    		*newmsgs = 0;
    	}
    	if (oldmsgs) {
    
    	VM_API_CALL(res, inboxcount2, (mailboxes, urgentmsgs, newmsgs, oldmsgs));
    	return res;
    
    int ast_app_sayname(struct ast_channel *chan, const char *mailbox_id)
    
    	VM_GREETER_API_CALL(res, sayname, (chan, mailbox_id));
    
    int ast_app_messagecount(const char *mailbox_id, const char *folder)
    
    	VM_API_CALL(res, messagecount, (mailbox_id, folder));
    
    const char *ast_vm_index_to_foldername(int id)
    {
    
    	const char *res = NULL;
    
    	VM_API_CALL(res, index_to_foldername, (id));
    	return res;
    
    }
    
    struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailbox,
    	const char *context,
    	const char *folder,
    	int descending,
    	enum ast_vm_snapshot_sort_val sort_val,
    	int combine_INBOX_and_OLD)
    {
    
    	struct ast_vm_mailbox_snapshot *res = NULL;
    
    	VM_API_CALL(res, mailbox_snapshot_create, (mailbox, context, folder, descending,
    		sort_val, combine_INBOX_and_OLD));
    	return res;
    
    }
    
    struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot)
    {
    
    	struct ast_vm_mailbox_snapshot *res = NULL;
    
    	VM_API_CALL(res, mailbox_snapshot_destroy, (mailbox_snapshot));
    	return res;
    
    }
    
    int ast_vm_msg_move(const char *mailbox,
    	const char *context,
    	size_t num_msgs,
    	const char *oldfolder,
    	const char *old_msg_ids[],
    	const char *newfolder)
    {
    
    	int res = 0;
    
    	VM_API_CALL(res, msg_move, (mailbox, context, num_msgs, oldfolder, old_msg_ids,
    		newfolder));
    	return res;
    
    }
    
    int ast_vm_msg_remove(const char *mailbox,
    	const char *context,
    	size_t num_msgs,
    	const char *folder,
    	const char *msgs[])
    {
    
    	int res = 0;
    
    	VM_API_CALL(res, msg_remove, (mailbox, context, num_msgs, folder, msgs));
    	return res;
    
    }
    
    int ast_vm_msg_forward(const char *from_mailbox,
    	const char *from_context,
    	const char *from_folder,
    	const char *to_mailbox,
    	const char *to_context,
    	const char *to_folder,
    	size_t num_msgs,
    	const char *msg_ids[],
    	int delete_old)
    {
    
    	int res = 0;
    
    	VM_API_CALL(res, msg_forward, (from_mailbox, from_context, from_folder, to_mailbox,
    		to_context, to_folder, num_msgs, msg_ids, delete_old));
    	return res;
    
    }
    
    int ast_vm_msg_play(struct ast_channel *chan,
    	const char *mailbox,
    	const char *context,
    	const char *folder,
    	const char *msg_num,
    
    	int res = 0;
    
    	VM_API_CALL(res, msg_play, (chan, mailbox, context, folder, msg_num, cb));
    	return res;
    
    }
    
    #ifdef TEST_FRAMEWORK
    int ast_vm_test_create_user(const char *context, const char *mailbox)
    {
    	if (ast_vm_test_create_user_func) {
    		return ast_vm_test_create_user_func(context, mailbox);
    	}
    	return 0;
    }
    
    int ast_vm_test_destroy_user(const char *context, const char *mailbox)
    {
    	if (ast_vm_test_destroy_user_func) {
    		return ast_vm_test_destroy_user_func(context, mailbox);
    	}
    	return 0;
    }
    #endif
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between, unsigned int duration)
    
    James Golovich's avatar
    James Golovich committed
    {
    
    	int res;
    
    	struct ast_silence_generator *silgen = NULL;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (!between) {
    
    James Golovich's avatar
    James Golovich committed
    		between = 100;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    	if (peer && ast_autoservice_start(peer)) {
    		return -1;
    
    	/* Need a quiet time before sending digits. */
    
    	if (ast_opt_transmit_silence) {
    		silgen = ast_channel_start_silence_generator(chan);
    	}
    
    	res = ast_safe_sleep(chan, 100);
    	if (res) {
    		goto dtmf_stream_cleanup;
    	}
    
    	for (ptr = digits; *ptr; ptr++) {
    		if (*ptr == 'w') {
    			/* 'w' -- wait half a second */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			if ((res = ast_safe_sleep(chan, 500))) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			}
    
    		} else if (*ptr == 'W') {
    			/* 'W' -- wait a second */
    			if ((res = ast_safe_sleep(chan, 1000))) {
    				break;
    			}
    
    		} else if (strchr("0123456789*#abcdfABCDF", *ptr)) {
    			if (*ptr == 'f' || *ptr == 'F') {
    				/* ignore return values if not supported by channel */
    				ast_indicate(chan, AST_CONTROL_FLASH);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			} else {
    
    				/* Character represents valid DTMF */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			if ((res = ast_safe_sleep(chan, between))) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			}
    		} else {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			ast_log(LOG_WARNING, "Illegal DTMF character '%c' in string. (0-9*#aAbBcCdD allowed)\n", *ptr);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    
    James Golovich's avatar
    James Golovich committed
    	}
    
    dtmf_stream_cleanup:
    
    	if (silgen) {
    		ast_channel_stop_silence_generator(chan, silgen);
    	}
    
    	if (peer && ast_autoservice_stop(peer)) {
    		res = -1;
    	}
    
    James Golovich's avatar
    James Golovich committed
    	return res;
    
    
    struct linear_state {
    	int fd;
    	int autoclose;
    	int allowoverride;
    
    };
    
    static void linear_release(struct ast_channel *chan, void *params)
    {
    	struct linear_state *ls = params;
    
    	if (ls->origwfmt && ast_set_write_format(chan, ls->origwfmt)) {
    		ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%s'\n",
    			ast_channel_name(chan), ast_format_get_name(ls->origwfmt));
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (ls->autoclose) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    }
    
    static int linear_generator(struct ast_channel *chan, void *data, int len, int samples)
    {
    	short buf[2048 + AST_FRIENDLY_OFFSET / 2];
    	struct linear_state *ls = data;
    
    	struct ast_frame f = {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		.frametype = AST_FRAME_VOICE,
    
    		.data.ptr = buf + AST_FRIENDLY_OFFSET / 2,
    
    		.offset = AST_FRIENDLY_OFFSET,
    
    	len = samples * 2;
    	if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		ast_log(LOG_WARNING, "Can't generate %d bytes of data!\n" , len);
    
    		len = sizeof(buf) - AST_FRIENDLY_OFFSET;
    	}
    	res = read(ls->fd, buf + AST_FRIENDLY_OFFSET/2, len);
    	if (res > 0) {
    		f.datalen = res;
    		f.samples = res / 2;
    		ast_write(chan, &f);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (res == len) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		}
    
    	}
    	return -1;
    }
    
    static void *linear_alloc(struct ast_channel *chan, void *params)
    {
    
    	struct linear_state *ls = params;
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (!params) {
    
    		return NULL;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    	/* In this case, params is already malloc'd */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (ls->allowoverride) {
    
    		ast_set_flag(ast_channel_flags(chan), AST_FLAG_WRITE_INT);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	} else {
    
    		ast_clear_flag(ast_channel_flags(chan), AST_FLAG_WRITE_INT);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    	ls->origwfmt = ao2_bump(ast_channel_writeformat(chan));