/*
* Asterisk -- An open source telephony toolkit.
*
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2008, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
*
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk/_private.h"
+
#include <pthread.h>
#include <sys/time.h>
#include <sys/signal.h>
static unsigned int atxferloopdelay;
static unsigned int atxfercallbackretries;
-static char *registrar = "res_features"; /*!< Registrar for operations */
+static char *registrar = "features"; /*!< Registrar for operations */
/* module and CLI command definitions */
static char *synopsis = "Answer a parked call";
struct ast_channel *parker;
struct ast_channel *parkee;
int res = 0;
- struct ast_module_user *u;
-
- u = ast_module_user_add(chan);
set_peers(&parker, &parkee, peer, chan, sense);
/* Setup the exten/priority to be s/1 since we don't know
if (!res)
res = ast_park_call(parkee, parker, 0, NULL);
- ast_module_user_remove(u);
-
if (!res) {
if (sense == FEATURE_SENSE_CHAN)
res = AST_PBX_NO_HANGUP_PEER;
if (callee_chan->monitor) {
ast_verb(4, "User hit '%s' to stop recording call.\n", code);
- ast_monitor_stop(callee_chan, 1);
+ callee_chan->monitor->stop(callee_chan, 1);
return FEATURE_RETURN_SUCCESS;
}
return res;
}
-/*!
- * \brief CLI command to list configured features
- * \param e
- * \param cmd
- * \param a
- *
- * \retval CLI_SUCCESS on success.
- * \retval NULL when tab completion is used.
- */
-static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) {
- int i;
- struct ast_call_feature *feature;
- char format[] = "%-25s %-7s %-7s\n";
-
- switch (cmd) {
-
- case CLI_INIT:
- e->command = "features show";
- e->usage =
- "Usage: features show\n"
- " Lists configured features\n";
- return NULL;
- case CLI_GENERATE:
- return NULL;
- }
-
- ast_cli(a->fd, format, "Builtin Feature", "Default", "Current");
- ast_cli(a->fd, format, "---------------", "-------", "-------");
-
- ast_cli(a->fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
-
- ast_rwlock_rdlock(&features_lock);
- for (i = 0; i < FEATURES_COUNT; i++)
- ast_cli(a->fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
- ast_rwlock_unlock(&features_lock);
-
- ast_cli(a->fd, "\n");
- ast_cli(a->fd, format, "Dynamic Feature", "Default", "Current");
- ast_cli(a->fd, format, "---------------", "-------", "-------");
- if (AST_LIST_EMPTY(&feature_list))
- ast_cli(a->fd, "(none)\n");
- else {
- AST_LIST_LOCK(&feature_list);
- AST_LIST_TRAVERSE(&feature_list, feature, feature_entry)
- ast_cli(a->fd, format, feature->sname, "no def", feature->exten);
- AST_LIST_UNLOCK(&feature_list);
- }
- ast_cli(a->fd, "\nCall parking\n");
- ast_cli(a->fd, "------------\n");
- ast_cli(a->fd,"%-20s: %s\n", "Parking extension", parking_ext);
- ast_cli(a->fd,"%-20s: %s\n", "Parking context", parking_con);
- ast_cli(a->fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop);
- ast_cli(a->fd,"\n");
-
- return CLI_SUCCESS;
-}
-
-static char mandescr_bridge[] =
-"Description: Bridge together two channels already in the PBX\n"
-"Variables: ( Headers marked with * are required )\n"
-" *Channel1: Channel to Bridge to Channel2\n"
-" *Channel2: Channel to Bridge to Channel1\n"
-" Tone: (Yes|No) Play courtesy tone to Channel 2\n"
-"\n";
-
-/*!
- * \brief Actual bridge
- * \param chan
- * \param tmpchan
- *
- * Stop hold music, lock both channels, masq channels,
- * after bridge return channel to next priority.
-*/
-static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan)
-{
- ast_moh_stop(chan);
- ast_channel_lock(chan);
- ast_setstate(tmpchan, chan->_state);
- tmpchan->readformat = chan->readformat;
- tmpchan->writeformat = chan->writeformat;
- ast_channel_masquerade(tmpchan, chan);
- ast_channel_lock(tmpchan);
- ast_do_masquerade(tmpchan);
- /* when returning from bridge, the channel will continue at the next priority */
- ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1);
- ast_channel_unlock(tmpchan);
- ast_channel_unlock(chan);
-}
-
-/*!
- * \brief Bridge channels together
- * \param s
- * \param m
- *
- * Make sure valid channels were specified,
- * send errors if any of the channels could not be found/locked, answer channels if needed,
- * create the placeholder channels and grab the other channels
- * make the channels compatible, send error if we fail doing so
- * setup the bridge thread object and start the bridge.
- *
- * \retval 0 on success or on incorrect use.
- * \retval 1 on failure to bridge channels.
+/*!
+ * \brief Add parking hints for all defined parking lots
+ * \param context
+ * \param start starting parkinglot number
+ * \param stop ending parkinglot number
*/
-static int action_bridge(struct mansession *s, const struct message *m)
+static void park_add_hints(char *context, int start, int stop)
{
- const char *channela = astman_get_header(m, "Channel1");
- const char *channelb = astman_get_header(m, "Channel2");
- const char *playtone = astman_get_header(m, "Tone");
- struct ast_channel *chana = NULL, *chanb = NULL;
- struct ast_channel *tmpchana = NULL, *tmpchanb = NULL;
- struct ast_bridge_thread_obj *tobj = NULL;
-
- /* make sure valid channels were specified */
- if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) {
- chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela));
- chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb));
- if (chana)
- ast_channel_unlock(chana);
- if (chanb)
- ast_channel_unlock(chanb);
+ int numext;
+ char device[AST_MAX_EXTENSION];
+ char exten[10];
- /* send errors if any of the channels could not be found/locked */
- if (!chana) {
- char buf[256];
- snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela);
- astman_send_error(s, m, buf);
- return 0;
- }
- if (!chanb) {
- char buf[256];
- snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb);
- astman_send_error(s, m, buf);
- return 0;
- }
- } else {
- astman_send_error(s, m, "Missing channel parameter in request");
- return 0;
+ for (numext = start; numext <= stop; numext++) {
+ snprintf(exten, sizeof(exten), "%d", numext);
+ snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+ ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
}
+}
- /* Answer the channels if needed */
- if (chana->_state != AST_STATE_UP)
- ast_answer(chana);
- if (chanb->_state != AST_STATE_UP)
- ast_answer(chanb);
-
- /* create the placeholder channels and grab the other channels */
- if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
- NULL, NULL, 0, "Bridge/%s", chana->name))) {
- astman_send_error(s, m, "Unable to create temporary channel!");
- return 1;
- }
+static int load_config(void)
+{
+ int start = 0, end = 0;
+ int res;
+ int i;
+ struct ast_context *con = NULL;
+ struct ast_config *cfg = NULL;
+ struct ast_variable *var = NULL;
+ struct feature_group *fg = NULL;
+ struct ast_flags config_flags = { 0 };
+ char old_parking_ext[AST_MAX_EXTENSION];
+ char old_parking_con[AST_MAX_EXTENSION] = "";
+ char *ctg;
+ static const char *categories[] = {
+ /* Categories in features.conf that are not
+ * to be parsed as group categories
+ */
+ "general",
+ "featuremap",
+ "applicationmap"
+ };
- if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
- NULL, NULL, 0, "Bridge/%s", chanb->name))) {
- astman_send_error(s, m, "Unable to create temporary channels!");
- ast_channel_free(tmpchana);
- return 1;
- }
+ if (!ast_strlen_zero(parking_con)) {
+ strcpy(old_parking_ext, parking_ext);
+ strcpy(old_parking_con, parking_con);
+ }
- do_bridge_masquerade(chana, tmpchana);
- do_bridge_masquerade(chanb, tmpchanb);
-
- /* make the channels compatible, send error if we fail doing so */
- if (ast_channel_make_compatible(tmpchana, tmpchanb)) {
- ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name);
- astman_send_error(s, m, "Could not make channels compatible for manager bridge");
- ast_hangup(tmpchana);
- ast_hangup(tmpchanb);
- return 1;
- }
+ /* Reset to defaults */
+ strcpy(parking_con, "parkedcalls");
+ strcpy(parking_con_dial, "park-dial");
+ strcpy(parking_ext, "700");
+ strcpy(pickup_ext, "*8");
+ strcpy(parkmohclass, "default");
+ courtesytone[0] = '\0';
+ strcpy(xfersound, "beep");
+ strcpy(xferfailsound, "pbx-invalid");
+ parking_start = 701;
+ parking_stop = 750;
+ parkfindnext = 0;
+ adsipark = 0;
+ comebacktoorigin = 1;
+ parkaddhints = 0;
+ parkedcalltransfers = 0;
+ parkedcallreparking = 0;
- /* setup the bridge thread object and start the bridge */
- if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
- ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno));
- astman_send_error(s, m, "Unable to spawn a new bridge thread");
- ast_hangup(tmpchana);
- ast_hangup(tmpchanb);
- return 1;
- }
+ transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+ featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+ atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+ atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+ atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
+ atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
- tobj->chan = tmpchana;
- tobj->peer = tmpchanb;
- tobj->return_to_pbx = 1;
-
- if (ast_true(playtone)) {
- if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) {
- if (ast_waitstream(tmpchanb, "") < 0)
- ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name);
- }
+ cfg = ast_config_load("features.conf", config_flags);
+ if (!cfg) {
+ ast_log(LOG_WARNING,"Could not load features.conf\n");
+ return 0;
}
-
- ast_bridge_call_thread_launch(tobj);
-
- astman_send_ack(s, m, "Launched bridge thread with success");
-
- return 0;
-}
-
+ for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+ if (!strcasecmp(var->name, "parkext")) {
+ ast_copy_string(parking_ext, var->value, sizeof(parking_ext));
+ } else if (!strcasecmp(var->name, "context")) {
+ ast_copy_string(parking_con, var->value, sizeof(parking_con));
+ } else if (!strcasecmp(var->name, "parkingtime")) {
+ if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
+ parkingtime = DEFAULT_PARK_TIME;
+ } else
+ parkingtime = parkingtime * 1000;
+ } else if (!strcasecmp(var->name, "parkpos")) {
+ if (sscanf(var->value, "%d-%d", &start, &end) != 2) {
+ ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of features.conf\n", var->lineno);
+ } else {
+ parking_start = start;
+ parking_stop = end;
+ }
+ } else if (!strcasecmp(var->name, "findslot")) {
+ parkfindnext = (!strcasecmp(var->value, "next"));
+ } else if (!strcasecmp(var->name, "parkinghints")) {
+ parkaddhints = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
+ if (!strcasecmp(var->value, "both"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+ else if (!strcasecmp(var->value, "caller"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+ else if (!strcasecmp(var->value, "callee"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
+ } else if (!strcasecmp(var->name, "parkedcallreparking")) {
+ if (!strcasecmp(var->value, "both"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+ else if (!strcasecmp(var->value, "caller"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+ else if (!strcasecmp(var->value, "callee"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
+ } else if (!strcasecmp(var->name, "adsipark")) {
+ adsipark = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "transferdigittimeout")) {
+ if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
+ transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+ } else
+ transferdigittimeout = transferdigittimeout * 1000;
+ } else if (!strcasecmp(var->name, "featuredigittimeout")) {
+ if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
+ featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+ }
+ } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) {
+ if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value);
+ atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+ } else
+ atxfernoanswertimeout = atxfernoanswertimeout * 1000;
+ } else if (!strcasecmp(var->name, "atxferloopdelay")) {
+ if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value);
+ atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+ } else
+ atxferloopdelay *= 1000;
+ } else if (!strcasecmp(var->name, "atxferdropcall")) {
+ atxferdropcall = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "atxfercallbackretries")) {
+ if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value);
+ atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
+ }
+ } else if (!strcasecmp(var->name, "courtesytone")) {
+ ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
+ } else if (!strcasecmp(var->name, "parkedplay")) {
+ if (!strcasecmp(var->value, "both"))
+ parkedplay = 2;
+ else if (!strcasecmp(var->value, "parked"))
+ parkedplay = 1;
+ else
+ parkedplay = 0;
+ } else if (!strcasecmp(var->name, "xfersound")) {
+ ast_copy_string(xfersound, var->value, sizeof(xfersound));
+ } else if (!strcasecmp(var->name, "xferfailsound")) {
+ ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound));
+ } else if (!strcasecmp(var->name, "pickupexten")) {
+ ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext));
+ } else if (!strcasecmp(var->name, "comebacktoorigin")) {
+ comebacktoorigin = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "parkedmusicclass")) {
+ ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass));
+ }
+ }
+
+ unmap_features();
+ for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) {
+ if (remap_feature(var->name, var->value))
+ ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+ }
+
+ /* Map a key combination to an application*/
+ ast_unregister_features();
+ for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) {
+ char *tmp_val = ast_strdupa(var->value);
+ char *exten, *activateon, *activatedby, *app, *app_args, *moh_class;
+ struct ast_call_feature *feature;
+
+ /* strsep() sets the argument to NULL if match not found, and it
+ * is safe to use it with a NULL argument, so we don't check
+ * between calls.
+ */
+ exten = strsep(&tmp_val,",");
+ activatedby = strsep(&tmp_val,",");
+ app = strsep(&tmp_val,",");
+ app_args = strsep(&tmp_val,",");
+ moh_class = strsep(&tmp_val,",");
+
+ activateon = strsep(&activatedby, "/");
+
+ /*! \todo XXX var_name or app_args ? */
+ if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) {
+ ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",
+ app, exten, activateon, var->name);
+ continue;
+ }
+
+ AST_LIST_LOCK(&feature_list);
+ if ((feature = find_dynamic_feature(var->name))) {
+ AST_LIST_UNLOCK(&feature_list);
+ ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name);
+ continue;
+ }
+ AST_LIST_UNLOCK(&feature_list);
+
+ if (!(feature = ast_calloc(1, sizeof(*feature))))
+ continue;
+
+ ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN);
+ ast_copy_string(feature->app, app, FEATURE_APP_LEN);
+ ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN);
+
+ if (app_args)
+ ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN);
+
+ if (moh_class)
+ ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN);
+
+ ast_copy_string(feature->exten, exten, sizeof(feature->exten));
+ feature->operation = feature_exec_app;
+ ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF);
+
+ /* Allow caller and calle to be specified for backwards compatability */
+ if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF);
+ else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER);
+ else {
+ ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s',"
+ " must be 'self', or 'peer'\n", var->name);
+ continue;
+ }
+
+ if (ast_strlen_zero(activatedby))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
+ else if (!strcasecmp(activatedby, "caller"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER);
+ else if (!strcasecmp(activatedby, "callee"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE);
+ else if (!strcasecmp(activatedby, "both"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
+ else {
+ ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s',"
+ " must be 'caller', or 'callee', or 'both'\n", var->name);
+ continue;
+ }
+
+ ast_register_feature(feature);
+
+ ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);
+ }
+
+ ast_unregister_groups();
+ AST_RWLIST_WRLOCK(&feature_groups);
+
+ ctg = NULL;
+ while ((ctg = ast_category_browse(cfg, ctg))) {
+ for (i = 0; i < ARRAY_LEN(categories); i++) {
+ if (!strcasecmp(categories[i], ctg))
+ break;
+ }
+
+ if (i < ARRAY_LEN(categories))
+ continue;
+
+ if (!(fg = register_group(ctg)))
+ continue;
+
+ for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
+ struct ast_call_feature *feature;
+
+ AST_LIST_LOCK(&feature_list);
+ if(!(feature = find_dynamic_feature(var->name)) &&
+ !(feature = ast_find_call_feature(var->name))) {
+ AST_LIST_UNLOCK(&feature_list);
+ ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
+ continue;
+ }
+ AST_LIST_UNLOCK(&feature_list);
+
+ register_group_feature(fg, var->value, feature);
+ }
+ }
+
+ AST_RWLIST_UNLOCK(&feature_groups);
+
+ ast_config_destroy(cfg);
+
+ /* Remove the old parking extension */
+ if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) {
+ if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
+ notify_metermaids(old_parking_ext, old_parking_con, AST_DEVICE_NOT_INUSE);
+ ast_debug(1, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
+ }
+
+ if (!(con = ast_context_find(parking_con)) && !(con = ast_context_create(NULL, parking_con, registrar))) {
+ ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
+ return -1;
+ }
+ res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar);
+ if (parkaddhints)
+ park_add_hints(parking_con, parking_start, parking_stop);
+ if (!res)
+ notify_metermaids(ast_parking_ext(), parking_con, AST_DEVICE_INUSE);
+ return res;
+
+}
+
/*!
- * \brief CLI command to list parked calls
- * \param e
+ * \brief CLI command to list configured features
+ * \param e
* \param cmd
* \param a
- *
- * Check right usage, lock parking lot, display parked calls, unlock parking lot list.
+ *
* \retval CLI_SUCCESS on success.
- * \retval CLI_SHOWUSAGE on incorrect number of arguments.
* \retval NULL when tab completion is used.
-*/
-static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+ */
+static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- struct parkeduser *cur;
- int numparked = 0;
+ int i;
+ struct ast_call_feature *feature;
+ char format[] = "%-25s %-7s %-7s\n";
switch (cmd) {
+
case CLI_INIT:
- e->command = "parkedcalls show";
+ e->command = "features show";
e->usage =
- "Usage: parkedcalls show\n"
- " List currently parked calls\n";
+ "Usage: features show\n"
+ " Lists configured features\n";
return NULL;
case CLI_GENERATE:
return NULL;
- }
+ }
- if (a->argc > e->args)
- return CLI_SHOWUSAGE;
+ ast_cli(a->fd, format, "Builtin Feature", "Default", "Current");
+ ast_cli(a->fd, format, "---------------", "-------", "-------");
- ast_cli(a->fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel"
- , "Context", "Extension", "Pri", "Timeout");
+ ast_cli(a->fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
- AST_LIST_LOCK(&parkinglot);
- AST_LIST_TRAVERSE(&parkinglot, cur, list) {
- ast_cli(a->fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n"
- ,cur->parkingexten, cur->chan->name, cur->context, cur->exten
- ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
+ ast_rwlock_rdlock(&features_lock);
+ for (i = 0; i < FEATURES_COUNT; i++)
+ ast_cli(a->fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
+ ast_rwlock_unlock(&features_lock);
- numparked++;
+ ast_cli(a->fd, "\n");
+ ast_cli(a->fd, format, "Dynamic Feature", "Default", "Current");
+ ast_cli(a->fd, format, "---------------", "-------", "-------");
+ if (AST_LIST_EMPTY(&feature_list))
+ ast_cli(a->fd, "(none)\n");
+ else {
+ AST_LIST_LOCK(&feature_list);
+ AST_LIST_TRAVERSE(&feature_list, feature, feature_entry)
+ ast_cli(a->fd, format, feature->sname, "no def", feature->exten);
+ AST_LIST_UNLOCK(&feature_list);
}
- AST_LIST_UNLOCK(&parkinglot);
- ast_cli(a->fd, "%d parked call%s.\n", numparked, ESS(numparked));
-
+ ast_cli(a->fd, "\nCall parking\n");
+ ast_cli(a->fd, "------------\n");
+ ast_cli(a->fd,"%-20s: %s\n", "Parking extension", parking_ext);
+ ast_cli(a->fd,"%-20s: %s\n", "Parking context", parking_con);
+ ast_cli(a->fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop);
+ ast_cli(a->fd,"\n");
return CLI_SUCCESS;
}
-static char *handle_parkedcalls_deprecated(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+int ast_features_reload(void)
{
- char *res = handle_parkedcalls(e, cmd, a);
- if (cmd == CLI_INIT)
- e->command = "show parkedcalls";
- return res;
+ load_config();
+
+ return RESULT_SUCCESS;
}
-static struct ast_cli_entry cli_show_parkedcalls_deprecated = AST_CLI_DEFINE(handle_parkedcalls_deprecated, "List currently parked calls.");
+static char *handle_features_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "features reload";
+ e->usage =
+ "Usage: features reload\n"
+ " Reloads configured call features from features.conf\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ load_config();
-static struct ast_cli_entry cli_features[] = {
- AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
- AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls", .deprecate_cmd = &cli_show_parkedcalls_deprecated),
-};
+ return CLI_SUCCESS;
+}
-/*!
- * \brief Dump parking lot status
- * \param s
- * \param m
+static char mandescr_bridge[] =
+"Description: Bridge together two channels already in the PBX\n"
+"Variables: ( Headers marked with * are required )\n"
+" *Channel1: Channel to Bridge to Channel2\n"
+" *Channel2: Channel to Bridge to Channel1\n"
+" Tone: (Yes|No) Play courtesy tone to Channel 2\n"
+"\n";
+
+/*!
+ * \brief Actual bridge
+ * \param chan
+ * \param tmpchan
*
- * Lock parking lot, iterate list and append parked calls status, unlock parking lot.
- * \return Always RESULT_SUCCESS
+ * Stop hold music, lock both channels, masq channels,
+ * after bridge return channel to next priority.
*/
-static int manager_parking_status(struct mansession *s, const struct message *m)
+static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan)
{
- struct parkeduser *cur;
- const char *id = astman_get_header(m, "ActionID");
- char idText[256] = "";
-
- if (!ast_strlen_zero(id))
- snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
- astman_send_ack(s, m, "Parked calls will follow");
-
- AST_LIST_LOCK(&parkinglot);
-
- AST_LIST_TRAVERSE(&parkinglot, cur, list) {
- astman_append(s, "Event: ParkedCall\r\n"
- "Exten: %d\r\n"
- "Channel: %s\r\n"
- "From: %s\r\n"
- "Timeout: %ld\r\n"
- "CallerIDNum: %s\r\n"
- "CallerIDName: %s\r\n"
- "%s"
- "\r\n",
- cur->parkingnum, cur->chan->name, cur->peername,
- (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
- S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is <unknown> */
- S_OR(cur->chan->cid.cid_name, ""),
- idText);
- }
-
- astman_append(s,
- "Event: ParkedCallsComplete\r\n"
- "%s"
- "\r\n",idText);
-
- AST_LIST_UNLOCK(&parkinglot);
-
- return RESULT_SUCCESS;
+ ast_moh_stop(chan);
+ ast_channel_lock(chan);
+ ast_setstate(tmpchan, chan->_state);
+ tmpchan->readformat = chan->readformat;
+ tmpchan->writeformat = chan->writeformat;
+ ast_channel_masquerade(tmpchan, chan);
+ ast_channel_lock(tmpchan);
+ ast_do_masquerade(tmpchan);
+ /* when returning from bridge, the channel will continue at the next priority */
+ ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1);
+ ast_channel_unlock(tmpchan);
+ ast_channel_unlock(chan);
}
-static char mandescr_park[] =
-"Description: Park a channel.\n"
-"Variables: (Names marked with * are required)\n"
-" *Channel: Channel name to park\n"
-" *Channel2: Channel to announce park info to (and return to if timeout)\n"
-" Timeout: Number of milliseconds to wait before callback.\n";
-
/*!
- * \brief Create manager event for parked calls
+ * \brief Bridge channels together
* \param s
* \param m
- *
- * Get channels involved in park, create event.
- * \return Always 0
+ *
+ * Make sure valid channels were specified,
+ * send errors if any of the channels could not be found/locked, answer channels if needed,
+ * create the placeholder channels and grab the other channels
+ * make the channels compatible, send error if we fail doing so
+ * setup the bridge thread object and start the bridge.
+ *
+ * \retval 0 on success or on incorrect use.
+ * \retval 1 on failure to bridge channels.
*/
-static int manager_park(struct mansession *s, const struct message *m)
+static int action_bridge(struct mansession *s, const struct message *m)
{
- const char *channel = astman_get_header(m, "Channel");
- const char *channel2 = astman_get_header(m, "Channel2");
- const char *timeout = astman_get_header(m, "Timeout");
- char buf[BUFSIZ];
- int to = 0;
- int res = 0;
- int parkExt = 0;
- struct ast_channel *ch1, *ch2;
+ const char *channela = astman_get_header(m, "Channel1");
+ const char *channelb = astman_get_header(m, "Channel2");
+ const char *playtone = astman_get_header(m, "Tone");
+ struct ast_channel *chana = NULL, *chanb = NULL;
+ struct ast_channel *tmpchana = NULL, *tmpchanb = NULL;
+ struct ast_bridge_thread_obj *tobj = NULL;
- if (ast_strlen_zero(channel)) {
- astman_send_error(s, m, "Channel not specified");
+ /* make sure valid channels were specified */
+ if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) {
+ chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela));
+ chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb));
+ if (chana)
+ ast_channel_unlock(chana);
+ if (chanb)
+ ast_channel_unlock(chanb);
+
+ /* send errors if any of the channels could not be found/locked */
+ if (!chana) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela);
+ astman_send_error(s, m, buf);
+ return 0;
+ }
+ if (!chanb) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb);
+ astman_send_error(s, m, buf);
+ return 0;
+ }
+ } else {
+ astman_send_error(s, m, "Missing channel parameter in request");
return 0;
}
- if (ast_strlen_zero(channel2)) {
- astman_send_error(s, m, "Channel2 not specified");
- return 0;
+ /* Answer the channels if needed */
+ if (chana->_state != AST_STATE_UP)
+ ast_answer(chana);
+ if (chanb->_state != AST_STATE_UP)
+ ast_answer(chanb);
+
+ /* create the placeholder channels and grab the other channels */
+ if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+ NULL, NULL, 0, "Bridge/%s", chana->name))) {
+ astman_send_error(s, m, "Unable to create temporary channel!");
+ return 1;
}
- ch1 = ast_get_channel_by_name_locked(channel);
- if (!ch1) {
- snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel);
- astman_send_error(s, m, buf);
- return 0;
+ if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+ NULL, NULL, 0, "Bridge/%s", chanb->name))) {
+ astman_send_error(s, m, "Unable to create temporary channels!");
+ ast_channel_free(tmpchana);
+ return 1;
}
- ch2 = ast_get_channel_by_name_locked(channel2);
- if (!ch2) {
- snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2);
- astman_send_error(s, m, buf);
- ast_channel_unlock(ch1);
- return 0;
+ do_bridge_masquerade(chana, tmpchana);
+ do_bridge_masquerade(chanb, tmpchanb);
+
+ /* make the channels compatible, send error if we fail doing so */
+ if (ast_channel_make_compatible(tmpchana, tmpchanb)) {
+ ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name);
+ astman_send_error(s, m, "Could not make channels compatible for manager bridge");
+ ast_hangup(tmpchana);
+ ast_hangup(tmpchanb);
+ return 1;
}
- if (!ast_strlen_zero(timeout)) {
- sscanf(timeout, "%d", &to);
+ /* setup the bridge thread object and start the bridge */
+ if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+ ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno));
+ astman_send_error(s, m, "Unable to spawn a new bridge thread");
+ ast_hangup(tmpchana);
+ ast_hangup(tmpchanb);
+ return 1;
}
- res = ast_masq_park_call(ch1, ch2, to, &parkExt);
- if (!res) {
- ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT);
- astman_send_ack(s, m, "Park successful");
- } else {
- astman_send_error(s, m, "Park failure");
+ tobj->chan = tmpchana;
+ tobj->peer = tmpchanb;
+ tobj->return_to_pbx = 1;
+
+ if (ast_true(playtone)) {
+ if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) {
+ if (ast_waitstream(tmpchanb, "") < 0)
+ ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name);
+ }
}
- ast_channel_unlock(ch1);
- ast_channel_unlock(ch2);
+ ast_bridge_call_thread_launch(tobj);
+
+ astman_send_ack(s, m, "Launched bridge thread with success");
return 0;
}
/*!
- * \brief Pickup a call
- * \param chan channel that initiated pickup.
- *
- * Walk list of channels, checking it is not itself, channel is pbx one,
- * check that the callgroup for both channels are the same and the channel is ringing.
- * Answer calling channel, flag channel as answered on queue, masq channels together.
-*/
-int ast_pickup_call(struct ast_channel *chan)
-{
- struct ast_channel *cur = NULL;
- int res = -1;
-
- while ((cur = ast_channel_walk_locked(cur)) != NULL) {
- if (!cur->pbx &&
- (cur != chan) &&
- (chan->pickupgroup & cur->callgroup) &&
- ((cur->_state == AST_STATE_RINGING) ||
- (cur->_state == AST_STATE_RING))) {
- break;
- }
- ast_channel_unlock(cur);
- }
- if (cur) {
- ast_debug(1, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name);
- res = ast_answer(chan);
- if (res)
- ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
- res = ast_queue_control(chan, AST_CONTROL_ANSWER);
- if (res)
- ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
- res = ast_channel_masquerade(cur, chan);
- if (res)
- ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name); /* Done */
- ast_channel_unlock(cur);
- } else {
- ast_debug(1, "No call pickup possible...\n");
- }
- return res;
-}
-
-/*!
- * \brief Add parking hints for all defined parking lots
- * \param context
- * \param start starting parkinglot number
- * \param stop ending parkinglot number
+ * \brief CLI command to list parked calls
+ * \param e
+ * \param cmd
+ * \param a
+ *
+ * Check right usage, lock parking lot, display parked calls, unlock parking lot list.
+ * \retval CLI_SUCCESS on success.
+ * \retval CLI_SHOWUSAGE on incorrect number of arguments.
+ * \retval NULL when tab completion is used.
*/
-static void park_add_hints(char *context, int start, int stop)
+static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- int numext;
- char device[AST_MAX_EXTENSION];
- char exten[10];
+ struct parkeduser *cur;
+ int numparked = 0;
- for (numext = start; numext <= stop; numext++) {
- snprintf(exten, sizeof(exten), "%d", numext);
- snprintf(device, sizeof(device), "park:%s@%s", exten, context);
- ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "parkedcalls show";
+ e->usage =
+ "Usage: parkedcalls show\n"
+ " List currently parked calls\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
}
-}
-
-
-static int load_config(void)
-{
- int start = 0, end = 0;
- int res;
- int i;
- struct ast_context *con = NULL;
- struct ast_config *cfg = NULL;
- struct ast_variable *var = NULL;
- struct feature_group *fg = NULL;
- struct ast_flags config_flags = { 0 };
- char old_parking_ext[AST_MAX_EXTENSION];
- char old_parking_con[AST_MAX_EXTENSION] = "";
- char *ctg;
- static const char *categories[] = {
- /* Categories in features.conf that are not
- * to be parsed as group categories
- */
- "general",
- "featuremap",
- "applicationmap"
- };
-
- if (!ast_strlen_zero(parking_con)) {
- strcpy(old_parking_ext, parking_ext);
- strcpy(old_parking_con, parking_con);
- }
- /* Reset to defaults */
- strcpy(parking_con, "parkedcalls");
- strcpy(parking_con_dial, "park-dial");
- strcpy(parking_ext, "700");
- strcpy(pickup_ext, "*8");
- strcpy(parkmohclass, "default");
- courtesytone[0] = '\0';
- strcpy(xfersound, "beep");
- strcpy(xferfailsound, "pbx-invalid");
- parking_start = 701;
- parking_stop = 750;
- parkfindnext = 0;
- adsipark = 0;
- comebacktoorigin = 1;
- parkaddhints = 0;
- parkedcalltransfers = 0;
- parkedcallreparking = 0;
+ if (a->argc > e->args)
+ return CLI_SHOWUSAGE;
- transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
- featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
- atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
- atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
- atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
- atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
+ ast_cli(a->fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel"
+ , "Context", "Extension", "Pri", "Timeout");
- cfg = ast_config_load("features.conf", config_flags);
- if (!cfg) {
- ast_log(LOG_WARNING,"Could not load features.conf\n");
- return AST_MODULE_LOAD_DECLINE;
- }
- for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
- if (!strcasecmp(var->name, "parkext")) {
- ast_copy_string(parking_ext, var->value, sizeof(parking_ext));
- } else if (!strcasecmp(var->name, "context")) {
- ast_copy_string(parking_con, var->value, sizeof(parking_con));
- } else if (!strcasecmp(var->name, "parkingtime")) {
- if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
- parkingtime = DEFAULT_PARK_TIME;
- } else
- parkingtime = parkingtime * 1000;
- } else if (!strcasecmp(var->name, "parkpos")) {
- if (sscanf(var->value, "%d-%d", &start, &end) != 2) {
- ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of features.conf\n", var->lineno);
- } else {
- parking_start = start;
- parking_stop = end;
- }
- } else if (!strcasecmp(var->name, "findslot")) {
- parkfindnext = (!strcasecmp(var->value, "next"));
- } else if (!strcasecmp(var->name, "parkinghints")) {
- parkaddhints = ast_true(var->value);
- } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
- if (!strcasecmp(var->value, "both"))
- parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
- else if (!strcasecmp(var->value, "caller"))
- parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
- else if (!strcasecmp(var->value, "callee"))
- parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
- } else if (!strcasecmp(var->name, "parkedcallreparking")) {
- if (!strcasecmp(var->value, "both"))
- parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
- else if (!strcasecmp(var->value, "caller"))
- parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
- else if (!strcasecmp(var->value, "callee"))
- parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
- } else if (!strcasecmp(var->name, "adsipark")) {
- adsipark = ast_true(var->value);
- } else if (!strcasecmp(var->name, "transferdigittimeout")) {
- if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
- transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
- } else
- transferdigittimeout = transferdigittimeout * 1000;
- } else if (!strcasecmp(var->name, "featuredigittimeout")) {
- if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
- featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
- }
- } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) {
- if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value);
- atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
- } else
- atxfernoanswertimeout = atxfernoanswertimeout * 1000;
- } else if (!strcasecmp(var->name, "atxferloopdelay")) {
- if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
- ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value);
- atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
- } else
- atxferloopdelay *= 1000;
- } else if (!strcasecmp(var->name, "atxferdropcall")) {
- atxferdropcall = ast_true(var->value);
- } else if (!strcasecmp(var->name, "atxfercallbackretries")) {
- if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
- ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value);
- atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
- }
- } else if (!strcasecmp(var->name, "courtesytone")) {
- ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
- } else if (!strcasecmp(var->name, "parkedplay")) {
- if (!strcasecmp(var->value, "both"))
- parkedplay = 2;
- else if (!strcasecmp(var->value, "parked"))
- parkedplay = 1;
- else
- parkedplay = 0;
- } else if (!strcasecmp(var->name, "xfersound")) {
- ast_copy_string(xfersound, var->value, sizeof(xfersound));
- } else if (!strcasecmp(var->name, "xferfailsound")) {
- ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound));
- } else if (!strcasecmp(var->name, "pickupexten")) {
- ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext));
- } else if (!strcasecmp(var->name, "comebacktoorigin")) {
- comebacktoorigin = ast_true(var->value);
- } else if (!strcasecmp(var->name, "parkedmusicclass")) {
- ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass));
- }
- }
+ AST_LIST_LOCK(&parkinglot);
+ AST_LIST_TRAVERSE(&parkinglot, cur, list) {
+ ast_cli(a->fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n"
+ ,cur->parkingexten, cur->chan->name, cur->context, cur->exten
+ ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
- unmap_features();
- for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) {
- if (remap_feature(var->name, var->value))
- ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+ numparked++;
}
+ AST_LIST_UNLOCK(&parkinglot);
+ ast_cli(a->fd, "%d parked call%s.\n", numparked, ESS(numparked));
- /* Map a key combination to an application*/
- ast_unregister_features();
- for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) {
- char *tmp_val = ast_strdupa(var->value);
- char *exten, *activateon, *activatedby, *app, *app_args, *moh_class;
- struct ast_call_feature *feature;
- /* strsep() sets the argument to NULL if match not found, and it
- * is safe to use it with a NULL argument, so we don't check
- * between calls.
- */
- exten = strsep(&tmp_val,",");
- activatedby = strsep(&tmp_val,",");
- app = strsep(&tmp_val,",");
- app_args = strsep(&tmp_val,",");
- moh_class = strsep(&tmp_val,",");
+ return CLI_SUCCESS;
+}
- activateon = strsep(&activatedby, "/");
+static char *handle_parkedcalls_deprecated(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *res = handle_parkedcalls(e, cmd, a);
+ if (cmd == CLI_INIT)
+ e->command = "show parkedcalls";
+ return res;
+}
- /*! \todo XXX var_name or app_args ? */
- if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) {
- ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",
- app, exten, activateon, var->name);
- continue;
- }
+static struct ast_cli_entry cli_show_parkedcalls_deprecated = AST_CLI_DEFINE(handle_parkedcalls_deprecated, "List currently parked calls.");
- AST_LIST_LOCK(&feature_list);
- if ((feature = find_dynamic_feature(var->name))) {
- AST_LIST_UNLOCK(&feature_list);
- ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name);
- continue;
- }
- AST_LIST_UNLOCK(&feature_list);
-
- if (!(feature = ast_calloc(1, sizeof(*feature))))
- continue;
+static struct ast_cli_entry cli_features[] = {
+ AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
+ AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"),
+ AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls", .deprecate_cmd = &cli_show_parkedcalls_deprecated),
+};
- ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN);
- ast_copy_string(feature->app, app, FEATURE_APP_LEN);
- ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN);
-
- if (app_args)
- ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN);
+/*!
+ * \brief Dump parking lot status
+ * \param s
+ * \param m
+ *
+ * Lock parking lot, iterate list and append parked calls status, unlock parking lot.
+ * \return Always RESULT_SUCCESS
+*/
+static int manager_parking_status(struct mansession *s, const struct message *m)
+{
+ struct parkeduser *cur;
+ const char *id = astman_get_header(m, "ActionID");
+ char idText[256] = "";
- if (moh_class)
- ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN);
-
- ast_copy_string(feature->exten, exten, sizeof(feature->exten));
- feature->operation = feature_exec_app;
- ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF);
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
- /* Allow caller and calle to be specified for backwards compatability */
- if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller"))
- ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF);
- else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee"))
- ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER);
- else {
- ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s',"
- " must be 'self', or 'peer'\n", var->name);
- continue;
- }
+ astman_send_ack(s, m, "Parked calls will follow");
- if (ast_strlen_zero(activatedby))
- ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
- else if (!strcasecmp(activatedby, "caller"))
- ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER);
- else if (!strcasecmp(activatedby, "callee"))
- ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE);
- else if (!strcasecmp(activatedby, "both"))
- ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
- else {
- ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s',"
- " must be 'caller', or 'callee', or 'both'\n", var->name);
- continue;
- }
+ AST_LIST_LOCK(&parkinglot);
- ast_register_feature(feature);
-
- ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);
+ AST_LIST_TRAVERSE(&parkinglot, cur, list) {
+ astman_append(s, "Event: ParkedCall\r\n"
+ "Exten: %d\r\n"
+ "Channel: %s\r\n"
+ "From: %s\r\n"
+ "Timeout: %ld\r\n"
+ "CallerIDNum: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "%s"
+ "\r\n",
+ cur->parkingnum, cur->chan->name, cur->peername,
+ (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
+ S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is <unknown> */
+ S_OR(cur->chan->cid.cid_name, ""),
+ idText);
}
- ast_unregister_groups();
- AST_RWLIST_WRLOCK(&feature_groups);
+ astman_append(s,
+ "Event: ParkedCallsComplete\r\n"
+ "%s"
+ "\r\n",idText);
- ctg = NULL;
- while ((ctg = ast_category_browse(cfg, ctg))) {
- for (i = 0; i < ARRAY_LEN(categories); i++) {
- if (!strcasecmp(categories[i], ctg))
- break;
- }
+ AST_LIST_UNLOCK(&parkinglot);
- if (i < ARRAY_LEN(categories))
- continue;
+ return RESULT_SUCCESS;
+}
- if (!(fg = register_group(ctg)))
- continue;
+static char mandescr_park[] =
+"Description: Park a channel.\n"
+"Variables: (Names marked with * are required)\n"
+" *Channel: Channel name to park\n"
+" *Channel2: Channel to announce park info to (and return to if timeout)\n"
+" Timeout: Number of milliseconds to wait before callback.\n";
- for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
- struct ast_call_feature *feature;
+/*!
+ * \brief Create manager event for parked calls
+ * \param s
+ * \param m
+ *
+ * Get channels involved in park, create event.
+ * \return Always 0
+*/
+static int manager_park(struct mansession *s, const struct message *m)
+{
+ const char *channel = astman_get_header(m, "Channel");
+ const char *channel2 = astman_get_header(m, "Channel2");
+ const char *timeout = astman_get_header(m, "Timeout");
+ char buf[BUFSIZ];
+ int to = 0;
+ int res = 0;
+ int parkExt = 0;
+ struct ast_channel *ch1, *ch2;
- AST_LIST_LOCK(&feature_list);
- if(!(feature = find_dynamic_feature(var->name)) &&
- !(feature = ast_find_call_feature(var->name))) {
- AST_LIST_UNLOCK(&feature_list);
- ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
- continue;
- }
- AST_LIST_UNLOCK(&feature_list);
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "Channel not specified");
+ return 0;
+ }
- register_group_feature(fg, var->value, feature);
- }
+ if (ast_strlen_zero(channel2)) {
+ astman_send_error(s, m, "Channel2 not specified");
+ return 0;
}
- AST_RWLIST_UNLOCK(&feature_groups);
+ ch1 = ast_get_channel_by_name_locked(channel);
+ if (!ch1) {
+ snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel);
+ astman_send_error(s, m, buf);
+ return 0;
+ }
- ast_config_destroy(cfg);
+ ch2 = ast_get_channel_by_name_locked(channel2);
+ if (!ch2) {
+ snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2);
+ astman_send_error(s, m, buf);
+ ast_channel_unlock(ch1);
+ return 0;
+ }
- /* Remove the old parking extension */
- if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) {
- if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
- notify_metermaids(old_parking_ext, old_parking_con, AST_DEVICE_NOT_INUSE);
- ast_debug(1, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
+ if (!ast_strlen_zero(timeout)) {
+ sscanf(timeout, "%d", &to);
}
-
- if (!(con = ast_context_find(parking_con)) && !(con = ast_context_create(NULL, parking_con, registrar))) {
- ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
- return -1;
+
+ res = ast_masq_park_call(ch1, ch2, to, &parkExt);
+ if (!res) {
+ ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT);
+ astman_send_ack(s, m, "Park successful");
+ } else {
+ astman_send_error(s, m, "Park failure");
}
- res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar);
- if (parkaddhints)
- park_add_hints(parking_con, parking_start, parking_stop);
- if (!res)
- notify_metermaids(ast_parking_ext(), parking_con, AST_DEVICE_INUSE);
- return res;
+ ast_channel_unlock(ch1);
+ ast_channel_unlock(ch2);
+
+ return 0;
+}
+
+/*!
+ * \brief Pickup a call
+ * \param chan channel that initiated pickup.
+ *
+ * Walk list of channels, checking it is not itself, channel is pbx one,
+ * check that the callgroup for both channels are the same and the channel is ringing.
+ * Answer calling channel, flag channel as answered on queue, masq channels together.
+*/
+int ast_pickup_call(struct ast_channel *chan)
+{
+ struct ast_channel *cur = NULL;
+ int res = -1;
+
+ while ((cur = ast_channel_walk_locked(cur)) != NULL) {
+ if (!cur->pbx &&
+ (cur != chan) &&
+ (chan->pickupgroup & cur->callgroup) &&
+ ((cur->_state == AST_STATE_RINGING) ||
+ (cur->_state == AST_STATE_RING))) {
+ break;
+ }
+ ast_channel_unlock(cur);
+ }
+ if (cur) {
+ ast_debug(1, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name);
+ res = ast_answer(chan);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
+ res = ast_queue_control(chan, AST_CONTROL_ANSWER);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
+ res = ast_channel_masquerade(cur, chan);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name); /* Done */
+ ast_channel_unlock(cur);
+ } else {
+ ast_debug(1, "No call pickup possible...\n");
+ }
+ return res;
}
static char *app_bridge = "Bridge";
return 0;
}
-static int reload(void)
-{
- return load_config();
-}
-
-static int load_module(void)
+int ast_features_init(void)
{
int res;
- ast_register_application(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip);
+ ast_register_application2(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip, NULL);
memset(parking_ext, 0, sizeof(parking_ext));
memset(parking_con, 0, sizeof(parking_con));
return res;
ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL);
- res = ast_register_application(parkedcall, park_exec, synopsis, descrip);
+ res = ast_register_application2(parkedcall, park_exec, synopsis, descrip, NULL);
if (!res)
- res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2);
+ res = ast_register_application2(parkcall, park_call_exec, synopsis2, descrip2, NULL);
if (!res) {
ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls");
ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park,
return res;
}
-
-
-static int unload_module(void)
-{
- struct ast_context *con;
- ast_manager_unregister("ParkedCalls");
- ast_manager_unregister("Bridge");
- ast_manager_unregister("Park");
- ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
- ast_unregister_application(parkcall);
- ast_unregister_application(app_bridge);
- ast_devstate_prov_del("Park");
- con = ast_context_find(parking_con);
- if (con)
- ast_context_destroy(con, registrar);
- con = ast_context_find(parking_con_dial);
- if (con)
- ast_context_destroy(con, registrar);
- return ast_unregister_application(parkedcall);
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource",
- .load = load_module,
- .unload = unload_module,
- .reload = reload,
- );