|
|
|
@ -1,7 +1,7 @@
|
|
|
|
|
/*
|
|
|
|
|
* Asterisk -- An open source telephony toolkit.
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) 1999 - 2006, Digium, Inc.
|
|
|
|
|
* Copyright (C) 1999 - 2008, Digium, Inc.
|
|
|
|
|
*
|
|
|
|
|
* Mark Spencer <markster@digium.com>
|
|
|
|
|
*
|
|
|
|
@ -31,6 +31,8 @@
|
|
|
|
|
|
|
|
|
|
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|
|
|
|
|
|
|
|
|
#include "asterisk/_private.h"
|
|
|
|
|
|
|
|
|
|
#include <pthread.h>
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
#include <sys/signal.h>
|
|
|
|
@ -126,7 +128,7 @@ static unsigned int atxferdropcall;
|
|
|
|
|
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";
|
|
|
|
@ -599,9 +601,6 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer,
|
|
|
|
|
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
|
|
|
|
@ -615,8 +614,6 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer,
|
|
|
|
|
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;
|
|
|
|
@ -675,7 +672,7 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -2512,72 +2509,406 @@ static int park_exec(struct ast_channel *chan, void *data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \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) {
|
|
|
|
|
* \brief Add parking hints for all defined parking lots
|
|
|
|
|
* \param context
|
|
|
|
|
* \param start starting parkinglot number
|
|
|
|
|
* \param stop ending parkinglot number
|
|
|
|
|
*/
|
|
|
|
|
static void park_add_hints(char *context, int start, int stop)
|
|
|
|
|
{
|
|
|
|
|
int numext;
|
|
|
|
|
char device[AST_MAX_EXTENSION];
|
|
|
|
|
char exten[10];
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int load_config(void)
|
|
|
|
|
{
|
|
|
|
|
int start = 0, end = 0;
|
|
|
|
|
int res;
|
|
|
|
|
int i;
|
|
|
|
|
struct ast_call_feature *feature;
|
|
|
|
|
char format[] = "%-25s %-7s %-7s\n";
|
|
|
|
|
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"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
if (!ast_strlen_zero(parking_con)) {
|
|
|
|
|
strcpy(old_parking_ext, parking_ext);
|
|
|
|
|
strcpy(old_parking_con, parking_con);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case CLI_INIT:
|
|
|
|
|
e->command = "features show";
|
|
|
|
|
e->usage =
|
|
|
|
|
"Usage: features show\n"
|
|
|
|
|
" Lists configured features\n";
|
|
|
|
|
return NULL;
|
|
|
|
|
case CLI_GENERATE:
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
ast_cli(a->fd, format, "Builtin Feature", "Default", "Current");
|
|
|
|
|
ast_cli(a->fd, format, "---------------", "-------", "-------");
|
|
|
|
|
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, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
|
|
|
|
|
cfg = ast_config_load("features.conf", config_flags);
|
|
|
|
|
if (!cfg) {
|
|
|
|
|
ast_log(LOG_WARNING,"Could not load features.conf\n");
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
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_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);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (!(feature = ast_calloc(1, sizeof(*feature))))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Actual bridge
|
|
|
|
|
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 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ast_features_reload(void)
|
|
|
|
|
{
|
|
|
|
|
load_config();
|
|
|
|
|
|
|
|
|
|
return RESULT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
*
|
|
|
|
@ -2769,6 +3100,7 @@ static struct ast_cli_entry cli_show_parkedcalls_deprecated = AST_CLI_DEFINE(han
|
|
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -2930,316 +3262,6 @@ int ast_pickup_call(struct ast_channel *chan)
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Add parking hints for all defined parking lots
|
|
|
|
|
* \param context
|
|
|
|
|
* \param start starting parkinglot number
|
|
|
|
|
* \param stop ending parkinglot number
|
|
|
|
|
*/
|
|
|
|
|
static void park_add_hints(char *context, int start, int stop)
|
|
|
|
|
{
|
|
|
|
|
int numext;
|
|
|
|
|
char device[AST_MAX_EXTENSION];
|
|
|
|
|
char exten[10];
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *app_bridge = "Bridge";
|
|
|
|
|
static char *bridge_synopsis = "Bridge two channels";
|
|
|
|
|
static char *bridge_descrip =
|
|
|
|
@ -3388,16 +3410,11 @@ static int bridge_exec(struct ast_channel *chan, void *data)
|
|
|
|
|
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));
|
|
|
|
@ -3406,9 +3423,9 @@ static int load_module(void)
|
|
|
|
|
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,
|
|
|
|
@ -3420,29 +3437,3 @@ static int load_module(void)
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
);
|