summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/app_dial.c28
-rw-r--r--bridges/bridge_builtin_features.c64
-rw-r--r--channels/chan_dahdi.c26
-rw-r--r--channels/chan_mgcp.c15
-rw-r--r--channels/chan_misdn.c30
-rw-r--r--channels/chan_sip.c42
-rw-r--r--channels/chan_unistim.c28
-rw-r--r--channels/sig_analog.c24
-rw-r--r--channels/sip/include/sip.h5
-rw-r--r--include/asterisk/channel.h3
-rw-r--r--include/asterisk/features.h50
-rw-r--r--include/asterisk/features_config.h234
-rw-r--r--main/bridging.c15
-rw-r--r--main/features.c2757
-rw-r--r--main/features_config.c1534
-rw-r--r--main/manager.c29
16 files changed, 2180 insertions, 2704 deletions
diff --git a/apps/app_dial.c b/apps/app_dial.c
index b6f1ce872..b0caa5c6b 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -69,6 +69,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/dial.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/bridging.h"
+#include "asterisk/features_config.h"
/*** DOCUMENTATION
<application name="Dial" language="en_US">
@@ -1074,7 +1075,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
int caller_entertained = outgoing
&& ast_test_flag64(outgoing, OPT_MUSICBACK | OPT_RINGBACK);
struct ast_party_connected_line connected_caller;
- struct ast_str *featurecode = ast_str_alloca(FEATURE_MAX_LEN + 1);
+ struct ast_str *featurecode = ast_str_alloca(AST_FEATURE_MAX_LEN + 1);
int cc_recall_core_id;
int is_cc_recall;
int cc_frame_received = 0;
@@ -1701,22 +1702,31 @@ skip_frame:;
static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str **featurecode)
{
- struct ast_flags features = { AST_FEATURE_DISCONNECT }; /* only concerned with disconnect feature */
- struct ast_call_feature feature = { 0, };
+ char disconnect_code[AST_FEATURE_MAX_LEN];
int res;
ast_str_append(featurecode, 1, "%c", code);
- res = ast_feature_detect(chan, &features, ast_str_buffer(*featurecode), &feature);
-
- if (res != AST_FEATURE_RETURN_STOREDIGITS) {
+ res = ast_get_builtin_feature(chan, "disconnect", disconnect_code, sizeof(disconnect_code));
+ if (res) {
ast_str_reset(*featurecode);
+ return 0;
}
- if (feature.feature_mask & AST_FEATURE_DISCONNECT) {
- return 1;
+
+ if (strlen(disconnect_code) > ast_str_strlen(*featurecode)) {
+ /* Could be a partial match, anyway */
+ if (strncmp(disconnect_code, ast_str_buffer(*featurecode), ast_str_strlen(*featurecode))) {
+ ast_str_reset(*featurecode);
+ }
+ return 0;
}
- return 0;
+ if (strcmp(disconnect_code, ast_str_buffer(*featurecode))) {
+ ast_str_reset(*featurecode);
+ return 0;
+ }
+
+ return 1;
}
/* returns true if there is a valid privacy reply */
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c
index 9574bb844..e11b280cd 100644
--- a/bridges/bridge_builtin_features.c
+++ b/bridges/bridge_builtin_features.c
@@ -49,6 +49,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/pbx.h"
#include "asterisk/parking.h"
+#include "asterisk/features_config.h"
/*!
* \brief Helper function that presents dialtone and grabs extension
@@ -59,6 +60,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context)
{
int res;
+ int digit_timeout;
+ RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
+
+ ast_channel_lock(chan);
+ xfer_cfg = ast_get_chan_features_xfer_config(chan);
+ if (!xfer_cfg) {
+ ast_log(LOG_ERROR, "Unable to get transfer configuration\n");
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ digit_timeout = xfer_cfg->transferdigittimeout;
+ ast_channel_unlock(chan);
/* Play the simple "transfer" prompt out and wait */
res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
@@ -73,8 +86,7 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
}
/* Drop to dialtone so they can enter the extension they want to transfer to */
-/* BUGBUG the timeout needs to be configurable from features.conf. */
- res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, 3000);
+ res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, digit_timeout);
if (res < 0) {
/* Hangup or error */
res = -1;
@@ -265,6 +277,11 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt;
const char *context;
enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
+ const char *atxfer_abort;
+ const char *atxfer_threeway;
+ const char *atxfer_complete;
+ const char *fail_sound;
+ RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
ast_bridge_channel_write_hold(bridge_channel, NULL);
@@ -273,6 +290,22 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
ast_channel_lock(bridge_channel->chan);
context = ast_strdupa(get_transfer_context(bridge_channel->chan,
attended_transfer ? attended_transfer->context : NULL));
+ xfer_cfg = ast_get_chan_features_xfer_config(bridge_channel->chan);
+ if (!xfer_cfg) {
+ ast_log(LOG_ERROR, "Unable to get transfer configuration options\n");
+ ast_channel_unlock(bridge_channel->chan);
+ return 0;
+ }
+ if (attended_transfer) {
+ atxfer_abort = ast_strdupa(S_OR(attended_transfer->abort, xfer_cfg->atxferabort));
+ atxfer_threeway = ast_strdupa(S_OR(attended_transfer->threeway, xfer_cfg->atxferthreeway));
+ atxfer_complete = ast_strdupa(S_OR(attended_transfer->complete, xfer_cfg->atxfercomplete));
+ } else {
+ atxfer_abort = ast_strdupa(xfer_cfg->atxferabort);
+ atxfer_threeway = ast_strdupa(xfer_cfg->atxferthreeway);
+ atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete);
+ }
+ fail_sound = ast_strdupa(xfer_cfg->xferfailsound);
ast_channel_unlock(bridge_channel->chan);
/* Grab the extension to transfer to */
@@ -288,36 +321,27 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
if (!peer) {
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
-/* BUGBUG beeperr needs to be configurable from features.conf */
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
-/* BUGBUG bridging API features does not support features.conf featuremap */
/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */
/* Setup a DTMF menu to control the transfer. */
if (ast_bridge_features_init(&caller_features)
|| ast_bridge_hangup_hook(&caller_features,
attended_transfer_complete, &transfer_code, NULL, 0)
- || ast_bridge_dtmf_hook(&caller_features,
- attended_transfer && !ast_strlen_zero(attended_transfer->abort)
- ? attended_transfer->abort : "*1",
+ || ast_bridge_dtmf_hook(&caller_features, atxfer_abort,
attended_transfer_abort, &transfer_code, NULL, 0)
- || ast_bridge_dtmf_hook(&caller_features,
- attended_transfer && !ast_strlen_zero(attended_transfer->complete)
- ? attended_transfer->complete : "*2",
+ || ast_bridge_dtmf_hook(&caller_features, atxfer_complete,
attended_transfer_complete, &transfer_code, NULL, 0)
- || ast_bridge_dtmf_hook(&caller_features,
- attended_transfer && !ast_strlen_zero(attended_transfer->threeway)
- ? attended_transfer->threeway : "*3",
+ || ast_bridge_dtmf_hook(&caller_features, atxfer_threeway,
attended_transfer_threeway, &transfer_code, NULL, 0)) {
ast_bridge_features_cleanup(&caller_features);
ast_hangup(peer);
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
-/* BUGBUG beeperr needs to be configurable from features.conf */
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
@@ -330,8 +354,7 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
ast_hangup(peer);
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
-/* BUGBUG beeperr needs to be configurable from features.conf */
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
@@ -345,8 +368,7 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
ast_hangup(peer);
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
-/* BUGBUG beeperr needs to be configurable from features.conf */
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
@@ -415,7 +437,7 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
if (xfer_failed) {
ast_hangup(peer);
if (!ast_check_hangup_locked(bridge_channel->chan)) {
- ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+ ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
}
ast_bridge_channel_write_unhold(bridge_channel);
}
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 00c25b9c1..92de02391 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -130,6 +130,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/paths.h"
#include "asterisk/ccss.h"
#include "asterisk/data.h"
+#include "asterisk/features_config.h"
/*** DOCUMENTATION
<application name="DAHDISendKeypadFacility" language="en_US">
@@ -10137,15 +10138,15 @@ static int dahdi_dnd(struct dahdi_pvt *dahdichan, int flag)
return 0;
}
-static int canmatch_featurecode(const char *exten)
+static int canmatch_featurecode(const char *pickupexten, const char *exten)
{
int extlen = strlen(exten);
- const char *pickup_ext;
+
if (!extlen) {
return 1;
}
- pickup_ext = ast_pickup_ext();
- if (extlen < strlen(pickup_ext) && !strncmp(pickup_ext, exten, extlen)) {
+
+ if (extlen < strlen(pickupexten) && !strncmp(pickupexten, exten, extlen)) {
return 1;
}
/* hardcoded features are *60, *67, *69, *70, *72, *73, *78, *79, *82, *0 */
@@ -10191,6 +10192,8 @@ static void *analog_ss_thread(void *data)
int res;
int idx;
struct ast_format tmpfmt;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickupexten;
ast_mutex_lock(&ss_thread_lock);
ss_thread_count++;
@@ -10210,6 +10213,17 @@ static void *analog_ss_thread(void *data)
ast_hangup(chan);
goto quit;
}
+
+ ast_channel_lock(chan);
+ pickup_cfg = ast_get_chan_features_pickup_config(chan);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
+ ast_channel_unlock(chan);
+
if (p->dsp)
ast_dsp_digitreset(p->dsp);
switch (p->sig) {
@@ -10576,7 +10590,7 @@ static void *analog_ss_thread(void *data)
memset(exten, 0, sizeof(exten));
timeout = firstdigittimeout;
- } else if (!strcmp(exten,ast_pickup_ext())) {
+ } else if (!strcmp(exten, pickupexten)) {
/* Scan all channels and see if there are any
* ringing channels that have call groups
* that equal this channels pickup group
@@ -10708,7 +10722,7 @@ static void *analog_ss_thread(void *data)
}
} else if (!ast_canmatch_extension(chan, ast_channel_context(chan), exten, 1,
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))
- && !canmatch_featurecode(exten)) {
+ && !canmatch_featurecode(pickupexten, exten)) {
ast_debug(1, "Can't match %s from '%s' in context %s\n", exten,
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<Unknown Caller>"),
ast_channel_context(chan));
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index df82061b8..9080dbaf0 100644
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -84,6 +84,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/pktccops.h"
#include "asterisk/stasis.h"
#include "asterisk/bridging.h"
+#include "asterisk/features_config.h"
/*
* Define to work around buggy dlink MGCP phone firmware which
@@ -2971,9 +2972,21 @@ static void *mgcp_ss(void *data)
int res= 0;
int getforward = 0;
int loop_pause = 100;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickupexten;
len = strlen(p->dtmf_buf);
+ ast_channel_lock(chan);
+ pickup_cfg = ast_get_chan_features_pickup_config(chan);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
+ ast_channel_unlock(chan);
+
while (len < AST_MAX_EXTENSION - 1) {
ast_debug(1, "Dtmf buffer '%s' for '%s@%s'\n", p->dtmf_buf, p->name, p->parent->name);
res = 1; /* Assume that we will get a digit */
@@ -3065,7 +3078,7 @@ static void *mgcp_ss(void *data)
len = 0;
memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
timeout = firstdigittimeout;
- } else if (!strcmp(p->dtmf_buf,ast_pickup_ext())) {
+ } else if (!strcmp(p->dtmf_buf, pickupexten)) {
/* Scan all channels and see if any there
* ringing channqels with that have call groups
* that equal this channels pickup group
diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c
index c68538e66..1a8f0d980 100644
--- a/channels/chan_misdn.c
+++ b/channels/chan_misdn.c
@@ -102,6 +102,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/causes.h"
#include "asterisk/format.h"
#include "asterisk/format_cap.h"
+#include "asterisk/features_config.h"
#include "chan_misdn_config.h"
#include "isdn_lib.h"
@@ -10071,6 +10072,9 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
}
if (ch->state == MISDN_WAITING4DIGS) {
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickupexten;
+
/* Ok, incomplete Setup, waiting till extension exists */
if (ast_strlen_zero(bc->info_dad) && ! ast_strlen_zero(bc->keypad)) {
chan_misdn_log(1, bc->port, " --> using keypad as info\n");
@@ -10080,8 +10084,18 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
strncat(bc->dialed.number, bc->info_dad, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1);
ast_channel_exten_set(ch->ast, bc->dialed.number);
+ ast_channel_lock(ch->ast);
+ pickup_cfg = ast_get_chan_features_pickup_config(ch->ast);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
+ ast_channel_unlock(ch->ast);
+
/* Check for Pickup Request first */
- if (!strcmp(ast_channel_exten(ch->ast), ast_pickup_ext())) {
+ if (!strcmp(ast_channel_exten(ch->ast), pickupexten)) {
if (ast_pickup_call(ch->ast)) {
hangup_chan(ch, bc);
} else {
@@ -10169,6 +10183,8 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
int ai;
int im;
int append_msn = 0;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickupexten;
if (ch) {
switch (ch->state) {
@@ -10224,6 +10240,16 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
return RESPONSE_RELEASE_SETUP;
}
+ ast_channel_lock(chan);
+ pickup_cfg = ast_get_chan_features_pickup_config(chan);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
+ ast_channel_unlock(chan);
+
if ((exceed = add_in_calls(bc->port))) {
char tmp[16];
snprintf(tmp, sizeof(tmp), "%d", exceed);
@@ -10315,7 +10341,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
}
/* Check for Pickup Request first */
- if (!strcmp(ast_channel_exten(chan), ast_pickup_ext())) {
+ if (!strcmp(ast_channel_exten(chan), pickupexten)) {
if (!ch->noautorespond_on_setup) {
/* Sending SETUP_ACK */
misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index fe67d28c7..eb79d237e 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -296,6 +296,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/app.h"
#include "asterisk/bridging.h"
#include "asterisk/stasis_endpoints.h"
+#include "asterisk/features_config.h"
/*** DOCUMENTATION
<application name="SIPDtmfMode" language="en_US">
@@ -17662,6 +17663,16 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re
char tmpf[256] = "", *from = NULL;
struct sip_request *req;
char *decoded_uri;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(p->owner), ao2_cleanup);
+ const char *pickupexten;
+
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ /* Don't need to duplicate since channel is locked for the duration of this function */
+ pickupexten = pickup_cfg->pickupexten;
+ }
req = oreq;
if (!req) {
@@ -17772,7 +17783,7 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re
return SIP_GET_DEST_EXTEN_FOUND;
}
if (ast_exists_extension(NULL, p->context, decoded_uri, 1, S_OR(p->cid_num, from))
- || !strcmp(decoded_uri, ast_pickup_ext())) {
+ || !strcmp(decoded_uri, pickupexten)) {
if (!oreq) {
ast_string_field_set(p, exten, decoded_uri);
}
@@ -17800,7 +17811,7 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re
if (ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP)
&& (ast_canmatch_extension(NULL, p->context, uri, 1, S_OR(p->cid_num, from))
|| ast_canmatch_extension(NULL, p->context, decoded_uri, 1, S_OR(p->cid_num, from))
- || !strncmp(decoded_uri, ast_pickup_ext(), strlen(decoded_uri)))) {
+ || !strncmp(decoded_uri, pickupexten, strlen(decoded_uri)))) {
/* Overlap dialing is enabled and we need more digits to match an extension. */
return SIP_GET_DEST_EXTEN_MATCHMORE;
}
@@ -21699,7 +21710,8 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req)
* on phone calls.
*/
- struct ast_call_feature *feat = NULL;
+ char feat[AST_FEATURE_MAX_LEN];
+ int feat_res = -1;
int j;
struct ast_frame f = { AST_FRAME_DTMF, };
int suppress_warning = 0; /* Supress warning if the feature is blank */
@@ -21711,43 +21723,40 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req)
}
/* first, get the feature string, if it exists */
- ast_rdlock_call_features();
if (p->relatedpeer) {
if (!strcasecmp(c, "on")) {
if (ast_strlen_zero(p->relatedpeer->record_on_feature)) {
suppress_warning = 1;
} else {
- feat = ast_find_call_feature(p->relatedpeer->record_on_feature);
+ feat_res = ast_get_feature(p->owner, p->relatedpeer->record_on_feature, feat, sizeof(feat));
}
} else if (!strcasecmp(c, "off")) {
if (ast_strlen_zero(p->relatedpeer->record_off_feature)) {
suppress_warning = 1;
} else {
- feat = ast_find_call_feature(p->relatedpeer->record_off_feature);
+ feat_res = ast_get_feature(p->owner, p->relatedpeer->record_off_feature, feat, sizeof(feat));
}
} else {
ast_log(LOG_ERROR, "Received INFO requesting to record with invalid value: %s\n", c);
}
}
- if (!feat || ast_strlen_zero(feat->exten)) {
+ if (feat_res || ast_strlen_zero(feat)) {
if (!suppress_warning) {
ast_log(LOG_WARNING, "Recording requested, but no One Touch Monitor registered. (See features.conf)\n");
}
/* 403 means that we don't support this feature, so don't request it again */
transmit_response(p, "403 Forbidden", req);
- ast_unlock_call_features();
return;
}
/* Send the feature code to the PBX as DTMF, just like the handset had sent it */
f.len = 100;
- for (j = 0; j < strlen(feat->exten); j++) {
- f.subclass.integer = feat->exten[j];
+ for (j = 0; j < strlen(feat); j++) {
+ f.subclass.integer = feat[j];
ast_queue_frame(p->owner, &f);
if (sipdebug) {
ast_verbose("* DTMF-relay event faked: %c\n", f.subclass.integer);
}
}
- ast_unlock_call_features();
ast_debug(1, "Got a Request to Record the channel, state %s\n", c);
transmit_response(p, "200 OK", req);
@@ -25650,6 +25659,15 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
if (c) { /* We have a call -either a new call or an old one (RE-INVITE) */
enum ast_channel_state c_state = ast_channel_state(c);
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(c), ao2_cleanup);
+ const char *pickupexten;
+
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
if (c_state != AST_STATE_UP && reinvite &&
(p->invitestate == INV_TERMINATED || p->invitestate == INV_CONFIRMED)) {
@@ -25671,7 +25689,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
transmit_provisional_response(p, "100 Trying", req, 0);
p->invitestate = INV_PROCEEDING;
ast_setstate(c, AST_STATE_RING);
- if (strcmp(p->exten, ast_pickup_ext())) { /* Call to extension -start pbx on this call */
+ if (strcmp(p->exten, pickupexten)) { /* Call to extension -start pbx on this call */
enum ast_pbx_result result;
result = ast_pbx_start(c);
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index 426e6ab10..28d332f62 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -76,6 +76,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/features.h"
#include "asterisk/astobj2.h"
#include "asterisk/astdb.h"
+#include "asterisk/features_config.h"
#define DEFAULTCONTEXT "default"
@@ -3086,11 +3087,25 @@ static void handle_call_outgoing(struct unistimsession *s)
send_favorite_short(sub->softkey, FAV_ICON_OFFHOOK_BLACK, s);
s->device->selected = -1;
if (!sub->owner) { /* A call is already in progress ? */
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickupexten;
+
c = unistim_new(sub, AST_STATE_DOWN, NULL); /* No, starting a new one */
if (!sub->rtp) { /* Need to start RTP before calling ast_pbx_run */
start_rtp(sub);
}
- if (c && !strcmp(s->device->phone_number, ast_pickup_ext())) {
+ if (c) {
+ ast_channel_lock(c);
+ pickup_cfg = ast_get_chan_features_pickup_config(c);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
+ ast_channel_unlock(c);
+ }
+ if (c && !strcmp(s->device->phone_number, pickupexten)) {
if (unistimdebug) {
ast_verb(0, "Try to pickup in unistim_new\n");
}
@@ -4099,8 +4114,17 @@ static void key_main_page(struct unistimsession *pte, char keycode)
ast_mutex_unlock(&devicelock);
show_extension_page(pte);
} else { /* Pickup function */
+ /* XXX Is there a way to get a specific channel here? */
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg,
+ ast_get_chan_features_pickup_config(NULL), ao2_cleanup);
+
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ break;
+ }
+
pte->device->selected = -1;
- ast_copy_string(pte->device->phone_number, ast_pickup_ext(),
+ ast_copy_string(pte->device->phone_number, pickup_cfg->pickupexten,
sizeof(pte->device->phone_number));
handle_call_outgoing(pte);
}
diff --git a/channels/sig_analog.c b/channels/sig_analog.c
index e3bd1d0c8..956c4a654 100644
--- a/channels/sig_analog.c
+++ b/channels/sig_analog.c
@@ -43,6 +43,7 @@
#include "asterisk/features.h"
#include "asterisk/cel.h"
#include "asterisk/causes.h"
+#include "asterisk/features_config.h"
#include "sig_analog.h"
@@ -1708,15 +1709,13 @@ static int analog_get_sub_fd(struct analog_pvt *p, enum analog_sub sub)
#define ANALOG_NEED_MFDETECT(p) (((p)->sig == ANALOG_SIG_FEATDMF) || ((p)->sig == ANALOG_SIG_FEATDMF_TA) || ((p)->sig == ANALOG_SIG_E911) || ((p)->sig == ANALOG_SIG_FGC_CAMA) || ((p)->sig == ANALOG_SIG_FGC_CAMAMF) || ((p)->sig == ANALOG_SIG_FEATB))
-static int analog_canmatch_featurecode(const char *exten)
+static int analog_canmatch_featurecode(const char *pickupexten, const char *exten)
{
int extlen = strlen(exten);
- const char *pickup_ext;
if (!extlen) {
return 1;
}
- pickup_ext = ast_pickup_ext();
- if (extlen < strlen(pickup_ext) && !strncmp(pickup_ext, exten, extlen)) {
+ if (extlen < strlen(pickupexten) && !strncmp(pickupexten, exten, extlen)) {
return 1;
}
/* hardcoded features are *60, *67, *69, *70, *72, *73, *78, *79, *82, *0 */
@@ -1756,6 +1755,8 @@ static void *__analog_ss_thread(void *data)
int res;
int idx;
struct ast_callid *callid;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickupexten;
analog_increase_ss_count();
@@ -1786,6 +1787,17 @@ static void *__analog_ss_thread(void *data)
ast_hangup(chan);
goto quit;
}
+
+ ast_channel_lock(chan);
+ pickup_cfg = ast_get_chan_features_pickup_config(chan);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
+ pickupexten = "";
+ } else {
+ pickupexten = ast_strdupa(pickup_cfg->pickupexten);
+ }
+ ast_channel_unlock(chan);
+
analog_dsp_reset_and_flush_digits(p);
switch (p->sig) {
case ANALOG_SIG_FEATD:
@@ -2190,7 +2202,7 @@ static void *__analog_ss_thread(void *data)
memset(exten, 0, sizeof(exten));
timeout = analog_firstdigittimeout;
- } else if (!strcmp(exten,ast_pickup_ext())) {
+ } else if (!strcmp(exten, pickupexten)) {
/* Scan all channels and see if there are any
* ringing channels that have call groups
* that equal this channels pickup group
@@ -2334,7 +2346,7 @@ static void *__analog_ss_thread(void *data)
}
} else if (!ast_canmatch_extension(chan, ast_channel_context(chan), exten, 1,
ast_channel_caller(chan)->id.number.valid ? ast_channel_caller(chan)->id.number.str : NULL)
- && !analog_canmatch_featurecode(exten)) {
+ && !analog_canmatch_featurecode(pickupexten, exten)) {
ast_debug(1, "Can't match %s from '%s' in context %s\n", exten,
ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str
? ast_channel_caller(chan)->id.number.str : "<Unknown Caller>",
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index 185f3935d..0adde37f2 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -38,6 +38,7 @@
#include "asterisk/http_websocket.h"
#include "asterisk/rtp_engine.h"
#include "asterisk/netsock2.h"
+#include "asterisk/features_config.h"
#ifndef FALSE
#define FALSE 0
@@ -762,8 +763,8 @@ struct sip_settings {
struct sip_proxy outboundproxy; /*!< Outbound proxy */
char default_context[AST_MAX_CONTEXT];
char default_subscribecontext[AST_MAX_CONTEXT];
- char default_record_on_feature[FEATURE_MAX_LEN];
- char default_record_off_feature[FEATURE_MAX_LEN];
+ char default_record_on_feature[AST_FEATURE_MAX_LEN];
+ char default_record_off_feature[AST_FEATURE_MAX_LEN];
struct ast_acl_list *contact_acl; /*! \brief Global list of addresses dynamic peers are not allowed to use */
struct ast_format_cap *caps; /*!< Supported codecs */
int tcp_enabled;
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index ec39ecf82..c2edc18e7 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -927,6 +927,9 @@ enum {
AST_FEATURE_AUTOMIXMON = (1 << 6),
};
+#define AST_FEATURE_DTMF_MASK (AST_FEATURE_REDIRECT | AST_FEATURE_DISCONNECT |\
+ AST_FEATURE_ATXFER | AST_FEATURE_AUTOMON | AST_FEATURE_PARKCALL | AST_FEATURE_AUTOMIXMON)
+
/*! \brief bridge configuration */
struct ast_bridge_config {
struct ast_flags features_caller;
diff --git a/include/asterisk/features.h b/include/asterisk/features.h
index 4f5561381..9b586506f 100644
--- a/include/asterisk/features.h
+++ b/include/asterisk/features.h
@@ -62,20 +62,6 @@ enum {
AST_FEATURE_FLAG_BYBOTH = (3 << 3),
};
-struct ast_call_feature {
- int feature_mask;
- char *fname;
- char sname[FEATURE_SNAME_LEN];
- char exten[FEATURE_MAX_LEN];
- char default_exten[FEATURE_MAX_LEN];
- ast_feature_operation operation;
- unsigned int flags;
- char app[FEATURE_APP_LEN];
- char app_args[FEATURE_APP_ARGS_LEN];
- char moh_class[FEATURE_MOH_LEN];
- AST_LIST_ENTRY(ast_call_feature) feature_entry;
-};
-
/*!
* \brief Park a call and read back parked location
*
@@ -166,9 +152,6 @@ int ast_masq_park_call_exten(struct ast_channel *park_me, struct ast_channel *pa
*/
int ast_parking_ext_valid(const char *exten_str, struct ast_channel *chan, const char *context);
-/*! \brief Determine system call pickup extension */
-const char *ast_pickup_ext(void);
-
/*!
* \brief Simulate a DTMF end on a broken bridge channel.
*
@@ -221,39 +204,6 @@ int ast_pickup_call(struct ast_channel *chan);
*/
int ast_do_pickup(struct ast_channel *chan, struct ast_channel *target);
-/*!
- * \brief register new feature into feature_set
- * \param feature an ast_call_feature object which contains a keysequence
- * and a callback function which is called when this keysequence is pressed
- * during a call.
-*/
-void ast_register_feature(struct ast_call_feature *feature);
-
-/*!
- * \brief unregister feature from feature_set
- * \param feature the ast_call_feature object which was registered before
-*/
-void ast_unregister_feature(struct ast_call_feature *feature);
-
-/*!
- * \brief detect a feature before bridging
- * \param chan
- * \param features an ast_flags ptr
- * \param code ptr of input code
- * \param feature
- * \retval ast_call_feature ptr to be set if found
-*/
-int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature);
-
-/*!
- * \brief look for a call feature entry by its sname
- * \param name a string ptr, should match "automon", "blindxfer", "atxfer", etc.
-*/
-struct ast_call_feature *ast_find_call_feature(const char *name);
-
-void ast_rdlock_call_features(void);
-void ast_unlock_call_features(void);
-
/*! \brief Reload call features from features.conf */
int ast_features_reload(void);
diff --git a/include/asterisk/features_config.h b/include/asterisk/features_config.h
new file mode 100644
index 000000000..a80fa7968
--- /dev/null
+++ b/include/asterisk/features_config.h
@@ -0,0 +1,234 @@
+/*
+* Asterisk -- An open source telephony toolkit.
+*
+* Copyright (C) 2013, Digium, Inc.
+*
+* Mark Michelson <mmichelson@digium.com>
+*
+* See http://www.asterisk.org for more information about
+* the Asterisk project. Please do not directly contact
+* any of the maintainers of this project for assistance;
+* the project provides a web site, mailing lists and IRC
+* channels for your use.
+*
+* This program is free software, distributed under the terms of
+* the GNU General Public License Version 2. See the LICENSE file
+* at the top of the source tree.
+*/
+
+#ifndef _FEATURES_CONFIG_H
+#define _FEATURES_CONFIG_H
+
+#include "asterisk/stringfields.h"
+
+struct ast_channel;
+
+/*!
+ * \brief General features configuration items
+ */
+struct ast_features_general_config {
+ AST_DECLARE_STRING_FIELDS(
+ /*! Sound played when automon or automixmon features are used */
+ AST_STRING_FIELD(courtesytone);
+ );
+ /*! Milliseconds allowed between digit presses when entering feature code */
+ unsigned int featuredigittimeout;
+};
+
+/*!
+ * \brief Get the general configuration options for a channel
+ *
+ * \note The channel should be locked before calling this function.
+ * \note The returned value has its reference count incremented.
+ *
+ * If no channel is provided, then the global features configuration is returned.
+ *
+ * \param chan The channel to get configuration options for
+ * \retval NULL Failed to get configuration
+ * \retval non-NULL The general features configuration
+ */
+struct ast_features_general_config *ast_get_chan_features_general_config(struct ast_channel *chan);
+
+/*!
+ * \brief Feature configuration relating to transfers
+ */
+struct ast_features_xfer_config {
+ AST_DECLARE_STRING_FIELDS (
+ /*! Sound to play when transfer succeeds */
+ AST_STRING_FIELD(xfersound);
+ /*! Sound to play when transfer fails */
+ AST_STRING_FIELD(xferfailsound);
+ /*! DTMF sequence used to abort an attempted atxfer */
+ AST_STRING_FIELD(atxferabort);
+ /*! DTMF sequence used to complete an attempted atxfer */
+ AST_STRING_FIELD(atxfercomplete);
+ /*! DTMF sequence used to turn an attempted atxfer into a three-way call */
+ AST_STRING_FIELD(atxferthreeway);
+ );
+ /*! Milliseconds allowed between digit presses when dialing transfer destination */
+ unsigned int transferdigittimeout;
+ /*! Milliseconds to wait for the transfer target to answer a transferred call */
+ unsigned int atxfernoanswertimeout;
+ /*! Milliseconds to wait before attempting to re-dial the transfer target */
+ unsigned int atxferloopdelay;
+ /*! Number of times to re-attempt dialing the transfer target */
+ unsigned int atxfercallbackretries;
+ /*! Determines if the call is dropped on attended transfer failure */
+ unsigned int atxferdropcall;
+};
+
+/*!
+ * \brief Get the transfer configuration options for a channel
+ *
+ * \note The channel should be locked before calling this function.
+ * \note The returned value has its reference count incremented.
+ *
+ * If no channel is provided, then the global transfer configuration is returned.
+ *
+ * \param chan The channel to get configuration options for
+ * \retval NULL Failed to get configuration
+ * \retval non-NULL The transfer features configuration
+ */
+struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan);
+
+/*!
+ * \brief Configuration relating to call pickup
+ */
+struct ast_features_pickup_config {
+ AST_DECLARE_STRING_FIELDS (
+ /*! Digit sequence to press to pick up a ringing call */
+ AST_STRING_FIELD(pickupexten);
+ /*! Sound to play to picker when pickup succeeds */
+ AST_STRING_FIELD(pickupsound);
+ /*! Sound to play to picker when pickup fails */
+ AST_STRING_FIELD(pickupfailsound);
+ );
+};
+
+/*!
+ * \brief Get the pickup configuration options for a channel
+ *
+ * \note The channel should be locked before calling this function.
+ * \note The returned value has its reference count incremented.
+ *
+ * If no channel is provided, then the global pickup configuration is returned.
+ *
+ * \param chan The channel to get configuration options for
+ * \retval NULL Failed to get configuration
+ * \retval non-NULL The pickup features configuration
+ */
+struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan);
+
+/*!
+ * \brief Configuration for the builtin features
+ */
+struct ast_featuremap_config {
+ AST_DECLARE_STRING_FIELDS (
+ /*! Blind transfer DTMF code */
+ AST_STRING_FIELD(blindxfer);
+ /*! Disconnect DTMF code */
+ AST_STRING_FIELD(disconnect);
+ /*! Automon DTMF code */
+ AST_STRING_FIELD(automon);
+ /*! Attended Transfer DTMF code */
+ AST_STRING_FIELD(atxfer);
+ /*! One-touch parking DTMF code */
+ AST_STRING_FIELD(parkcall);
+ /*! Automixmon DTMF code */
+ AST_STRING_FIELD(automixmon);
+ );
+};
+
+/*!
+ * \brief Get the featuremap configuration options for a channel
+ *
+ * \note The channel should be locked before calling this function.
+ * \note The returned value has its reference count incremented.
+ *
+ * If no channel is provided, then the global featuremap configuration is returned.
+ *
+ * \param chan The channel to get configuration options for
+ * \retval NULL Failed to get configuration
+ * \retval non-NULL The pickup features configuration
+ */
+struct ast_featuremap_config *ast_get_chan_featuremap_config(struct ast_channel *chan);
+
+/*!
+ * \brief Get the DTMF code for a builtin feature
+ *
+ * \note The channel should be locked before calling this function
+ *
+ * If no channel is provided, then the global setting for the option is returned.
+ *
+ * \param chan The channel to get the option from
+ * \param feature The short name of the feature (as it appears in features.conf)
+ * \param[out] buf The buffer to write the DTMF value into
+ * \param size The size of the buffer in bytes
+ * \retval 0 Success
+ * \retval non-zero Unrecognized builtin feature name
+ */
+int ast_get_builtin_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len);
+
+/*!
+ * \brief Get the DTMF code for a call feature
+ *
+ * \note The channel should be locked before calling this function
+ *
+ * If no channel is provided, then the global setting for the option is returned.
+ *
+ * This function is like \ref ast_get_builtin_feature except that it will
+ * also check the applicationmap in addition to the builtin features.
+ *
+ * \param chan The channel to get the option from
+ * \param feature The short name of the feature
+ * \param[out] buf The buffer to write the DTMF value into
+ * \param size The size of the buffer in bytes
+ * \retval 0 Success
+ * \retval non-zero Unrecognized feature name
+ */
+int ast_get_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len);
+
+#define AST_FEATURE_MAX_LEN 11
+
+/*!
+ * \brief An applicationmap configuration item
+ */
+struct ast_applicationmap_item {
+ AST_DECLARE_STRING_FIELDS (
+ /* Name of the item */
+ AST_STRING_FIELD(name);
+ /* Name Dialplan application that is invoked by the feature */
+ AST_STRING_FIELD(app);
+ /* Data to pass to the application */
+ AST_STRING_FIELD(app_data);
+ /* Music-on-hold class to play to party on which feature is not activated */
+ AST_STRING_FIELD(moh_class);
+ );
+ /* DTMF key sequence used to activate the feature */
+ char dtmf[AST_FEATURE_MAX_LEN];
+ /* If true, activate on party that input the sequence, otherwise activate on the other party */
+ unsigned int activate_on_self;
+};
+
+/*!
+ * \brief Get the applicationmap for a given channel.
+ *
+ * \note The channel should be locked before calling this function.
+ *
+ * This uses the value of the DYNAMIC_FEATURES channel variable to build a
+ * custom applicationmap for this channel. The returned container has
+ * applicationmap_items inside.
+ *
+ * \param chan The channel for which applicationmap is being retrieved.
+ * \retval NULL An error occurred or the channel has no dynamic features.
+ * \retval non-NULL A container of applicationmap_items pertaining to the channel.
+ */
+struct ao2_container *ast_get_chan_applicationmap(struct ast_channel *chan);
+
+void ast_features_config_shutdown(void);
+
+int ast_features_config_reload(void);
+
+int ast_features_config_init(void);
+
+#endif /* _FEATURES_CONFIG_H */
diff --git a/main/bridging.c b/main/bridging.c
index 93db3b0ef..c59638736 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/parking.h"
#include "asterisk/core_local.h"
+#include "asterisk/features_config.h"
/*! All bridges container. */
static struct ao2_container *bridges;
@@ -1914,6 +1915,18 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
struct ast_bridge_hook *hook = NULL;
char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
size_t dtmf_len = 0;
+ unsigned int digit_timeout;
+ RAII_VAR(struct ast_features_general_config *, gen_cfg, NULL, ao2_cleanup);
+
+ ast_channel_lock(bridge_channel->chan);
+ gen_cfg = ast_get_chan_features_general_config(bridge_channel->chan);
+ if (!gen_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve features configuration.\n");
+ ast_channel_unlock(bridge_channel->chan);
+ return;
+ }
+ digit_timeout = gen_cfg->featuredigittimeout;
+ ast_channel_unlock(bridge_channel->chan);
/* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */
ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
@@ -1923,7 +1936,7 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
int res;
/* If the above timed out simply exit */
- res = ast_waitfordigit(bridge_channel->chan, 3000);
+ res = ast_waitfordigit(bridge_channel->chan, digit_timeout);
if (!res) {
ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
bridge_channel, ast_channel_name(bridge_channel->chan));
diff --git a/main/features.c b/main/features.c
index 2aa49ff35..e0c6548a9 100644
--- a/main/features.c
+++ b/main/features.c
@@ -74,6 +74,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/test.h"
#include "asterisk/bridging.h"
#include "asterisk/bridging_basic.h"
+#include "asterisk/features_config.h"
+
+/* BUGBUG TEST_FRAMEWORK is disabled because parking tests no longer work. */
+#undef TEST_FRAMEWORK
/*
* Party A - transferee
@@ -299,58 +303,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Bridge together two channels already in the PBX.</para>
</description>
</manager>
- <function name="FEATURE" language="en_US">
- <synopsis>
- Get or set a feature option on a channel.
- </synopsis>
- <syntax>
- <parameter name="option_name" required="true">
- <para>The allowed values are:</para>
- <enumlist>
- <enum name="parkingtime"><para>Specified in seconds.</para></enum>
- <enum name="inherit"><para>Inherit feature settings made in FEATURE or FEATUREMAP to child channels.</para></enum>
- </enumlist>
- </parameter>
- </syntax>
- <description>
- <para>When this function is used as a read, it will get the current
- value of the specified feature option for this channel. It will be
- the value of this option configured in features.conf if a channel specific
- value has not been set. This function can also be used to set a channel
- specific value for the supported feature options.</para>
- </description>
- <see-also>
- <ref type="function">FEATUREMAP</ref>
- </see-also>
- </function>
- <function name="FEATUREMAP" language="en_US">
- <synopsis>
- Get or set a feature map to a given value on a specific channel.
- </synopsis>
- <syntax>
- <parameter name="feature_name" required="true">
- <para>The allowed values are:</para>
- <enumlist>
- <enum name="atxfer"><para>Attended Transfer</para></enum>
- <enum name="blindxfer"><para>Blind Transfer</para></enum>
- <enum name="automon"><para>Auto Monitor</para></enum>
- <enum name="disconnect"><para>Call Disconnect</para></enum>
- <enum name="parkcall"><para>Park Call</para></enum>
- <enum name="automixmon"><para>Auto MixMonitor</para></enum>
- </enumlist>
- </parameter>
- </syntax>
- <description>
- <para>When this function is used as a read, it will get the current
- digit sequence mapped to the specified feature for this channel. This
- value will be the one configured in features.conf if a channel specific
- value has not been set. This function can also be used to set a channel
- specific value for a feature mapping.</para>
- </description>
- <see-also>
- <ref type="function">FEATURE</ref>
- </see-also>
- </function>
<managerEvent language="en_US" name="ParkedCallTimeOut">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a parked call times out.</synopsis>
@@ -395,12 +347,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define DEFAULT_PARK_TIME 45000 /*!< ms */
#define DEFAULT_PARK_EXTENSION "700"
-#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 /*!< ms */
-#define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000 /*!< ms */
-#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 /*!< ms */
-#define DEFAULT_ATXFER_DROP_CALL 0 /*!< Do not drop call. */
-#define DEFAULT_ATXFER_LOOP_DELAY 10000 /*!< ms */
-#define DEFAULT_ATXFER_CALLBACK_RETRIES 2
#define DEFAULT_COMEBACK_CONTEXT "parkedcallstimeout"
#define DEFAULT_COMEBACK_TO_ORIGIN 1
#define DEFAULT_COMEBACK_DIAL_TIME 30
@@ -410,24 +356,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
/* TODO Scrape all of the parking stuff out of features.c */
-struct feature_group_exten {
- AST_LIST_ENTRY(feature_group_exten) entry;
- AST_DECLARE_STRING_FIELDS(
- AST_STRING_FIELD(exten);
- );
- struct ast_call_feature *feature;
-};
-
-struct feature_group {
- AST_LIST_ENTRY(feature_group) entry;
- AST_DECLARE_STRING_FIELDS(
- AST_STRING_FIELD(gname);
- );
- AST_LIST_HEAD_NOLOCK(, feature_group_exten) features;
-};
-
-static AST_RWLIST_HEAD_STATIC(feature_groups, feature_group);
-
typedef enum {
FEATURE_INTERPRET_DETECT, /* Used by ast_feature_detect */
FEATURE_INTERPRET_DO, /* Used by feature_interpret */
@@ -436,8 +364,6 @@ typedef enum {
static const char *parkedcall = "ParkedCall";
-static char pickup_ext[AST_MAX_EXTENSION]; /*!< Call pickup extension */
-
/*! Parking lot access ramp dialplan usage entry. */
struct parking_dp_ramp {
/*! Next node in the parking lot spaces dialplan list. */
@@ -606,15 +532,11 @@ static struct ao2_container *parkinglots;
static struct ast_parkinglot *default_parkinglot;
/*! Force a config reload to reload regardless of config file timestamp. */
+#ifdef TEST_FRAMEWORK
static int force_reload_load;
+#endif
-static int parkedplay = 0; /*!< Who to play courtesytone to when someone picks up a parked call. */
static int parkeddynamic = 0; /*!< Enable creation of parkinglots dynamically */
-static char courtesytone[256]; /*!< Courtesy tone used to pickup parked calls and on-touch-record */
-static char xfersound[256]; /*!< Call transfer sound */
-static char xferfailsound[256]; /*!< Call transfer failure sound */
-static char pickupsound[256]; /*!< Pickup sound */
-static char pickupfailsound[256]; /*!< Pickup failure sound */
/*!
* \brief Context for parking dialback to parker.
@@ -631,14 +553,6 @@ AST_MUTEX_DEFINE_STATIC(features_reload_lock);
static int adsipark;
-static int transferdigittimeout;
-static int featuredigittimeout;
-
-static int atxfernoanswertimeout;
-static unsigned int atxferdropcall;
-static unsigned int atxferloopdelay;
-static unsigned int atxfercallbackretries;
-
static char *registrar = "features"; /*!< Registrar for operations */
/*! PARK_APP_NAME application arguments */
@@ -848,11 +762,6 @@ int ast_parking_ext_valid(const char *exten_str, struct ast_channel *chan, const
return get_parking_exten(exten_str, chan, context) ? 1 : 0;
}
-const char *ast_pickup_ext(void)
-{
- return pickup_ext;
-}
-
struct ast_bridge_thread_obj
{
struct ast_bridge_config bconfig;
@@ -888,75 +797,19 @@ static void set_c_e_p(struct ast_channel *chan, const char *context, const char
ast_channel_priority_set(chan, pri);
}
-/*!
- * \brief Check goto on transfer
- * \param chan
- *
- * Check if channel has 'GOTO_ON_BLINDXFR' set, if not exit.
- * When found make sure the types are compatible. Check if channel is valid
- * if so start the new channel else hangup the call.
- */
-static void check_goto_on_transfer(struct ast_channel *chan)
-{
- struct ast_channel *xferchan;
- const char *val;
- char *goto_on_transfer;
- char *x;
-
- ast_channel_lock(chan);
- val = pbx_builtin_getvar_helper(chan, "GOTO_ON_BLINDXFR");
- if (ast_strlen_zero(val)) {
- ast_channel_unlock(chan);
- return;
- }
- goto_on_transfer = ast_strdupa(val);
- ast_channel_unlock(chan);
-
- ast_debug(1, "Attempting GOTO_ON_BLINDXFR=%s for %s.\n", val, ast_channel_name(chan));
-
- xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", ast_channel_linkedid(chan), 0,
- "%s", ast_channel_name(chan));
- if (!xferchan) {
- return;
- }
-
- /* Make formats okay */
- ast_format_copy(ast_channel_readformat(xferchan), ast_channel_readformat(chan));
- ast_format_copy(ast_channel_writeformat(xferchan), ast_channel_writeformat(chan));
-
- if (ast_channel_masquerade(xferchan, chan)) {
- /* Failed to setup masquerade. */
- ast_hangup(xferchan);
- return;
- }
-
- for (x = goto_on_transfer; *x; ++x) {
- if (*x == '^') {
- *x = ',';
- }
- }
- ast_parseable_goto(xferchan, goto_on_transfer);
- ast_channel_state_set(xferchan, AST_STATE_UP);
- ast_clear_flag(ast_channel_flags(xferchan), AST_FLAGS_ALL);
- ast_channel_clear_softhangup(xferchan, AST_SOFTHANGUP_ALL);
-
- ast_do_masquerade(xferchan);
- if (ast_pbx_start(xferchan)) {
- /* Failed to start PBX. */
- ast_hangup(xferchan);
- }
-}
-
+#if 0
static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
const char *caller_name, struct ast_channel *requestor,
struct ast_channel *transferee, const char *type, struct ast_format_cap *cap, const char *addr,
int timeout, int *outstate, const char *language);
+#endif
static const struct ast_datastore_info channel_app_data_datastore = {
.type = "Channel appdata datastore",
.destroy = ast_free_ptr,
};
+#if 0
static int set_chan_app_data(struct ast_channel *chan, const char *src_app_data)
{
struct ast_datastore *datastore;
@@ -978,7 +831,9 @@ static int set_chan_app_data(struct ast_channel *chan, const char *src_app_data)
ast_channel_datastore_add(chan, datastore);
return 0;
}
+#endif
+#if 0
/*!
* \brief bridge the call
* \param data thread bridge.
@@ -1021,7 +876,9 @@ static void *bridge_call_thread(void *data)
return NULL;
}
+#endif
+#if 0
/*!
* \brief create thread for the bridging call
* \param tobj
@@ -1041,6 +898,7 @@ static void bridge_call_thread_launch(struct ast_bridge_thread_obj *tobj)
ast_free(tobj);
}
}
+#endif
/*!
* \brief Announce call parking by ADSI
@@ -1456,8 +1314,6 @@ static struct parkeduser *park_space_reserve(struct ast_channel *park_me, struct
return pu;
}
-static unsigned int get_parkingtime(struct ast_channel *chan, struct ast_parkinglot *parkinglot);
-
/* Park a call */
static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, struct ast_park_call_args *args)
{
@@ -1491,7 +1347,10 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, st
}
pu->start = ast_tvnow();
- pu->parkingtime = (args->timeout > 0) ? args->timeout : get_parkingtime(chan, pu->parkinglot);
+ /* XXX This line was changed to not use get_parkingtime. This is just a placeholder message, because
+ * likely this entire function is going away.
+ */
+ pu->parkingtime = args->timeout;
if (args->extout)
*(args->extout) = pu->parkingnum;
@@ -1878,13 +1737,16 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
return masq_park_call(rchan, peer, &args);
}
+#if 0
static int finishup(struct ast_channel *chan)
{
ast_indicate(chan, AST_CONTROL_UNHOLD);
return ast_autoservice_stop(chan);
}
+#endif
+#if 0
/*!
* \internal
* \brief Builtin transfer park call helper.
@@ -1952,7 +1814,9 @@ static int xfer_park_call_helper(struct ast_channel *park_me, struct ast_channel
return res ? AST_FEATURE_RETURN_SUCCESS : -1;
}
+#endif
+#if 0
/*!
* \brief set caller and callee according to the direction
* \param caller, callee, peer, chan, sense
@@ -1970,7 +1834,9 @@ static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
*caller = chan;
}
}
+#endif
+#if 0
/*!
* \brief support routing for one touch call parking
* \param chan channel parking call
@@ -2019,6 +1885,7 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer,
set_peers(&parker, &parkee, peer, chan, sense);
return masq_park_call(parkee, parker, &args) ? AST_FEATURE_RETURN_SUCCESS : -1;
}
+#endif
/*!
* \internal
@@ -2051,6 +1918,7 @@ static int play_message_on_chan(struct ast_channel *play_to, struct ast_channel
return 0;
}
+#if 0
/*!
* \internal
* \brief Play file to specified channels.
@@ -2081,7 +1949,9 @@ static int play_message_to_chans(struct ast_channel *left, struct ast_channel *r
return 0;
}
+#endif
+#if 0
/*!
* \brief Play message to both caller and callee in bridged call, plays synchronously, autoservicing the
* other channel during the message, so please don't use this for very long messages
@@ -2091,7 +1961,9 @@ static int play_message_in_bridged_call(struct ast_channel *caller_chan, struct
return play_message_to_chans(caller_chan, callee_chan, 0, "automon message",
audiofile);
}
+#endif
+#if 0
/*!
* \brief Monitor a channel by DTMF
* \param chan channel requesting monitor
@@ -2194,7 +2066,9 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
return AST_FEATURE_RETURN_SUCCESS;
}
+#endif
+#if 0
static int builtin_automixmonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
{
char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL;
@@ -2294,13 +2168,17 @@ static int builtin_automixmonitor(struct ast_channel *chan, struct ast_channel *
pbx_builtin_setvar_helper(caller_chan, "TOUCH_MIXMONITOR_OUTPUT", touch_filename);
return AST_FEATURE_RETURN_SUCCESS;
}
+#endif
+#if 0
static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
{
ast_verb(4, "User hit '%s' to disconnect call.\n", code);
return AST_FEATURE_RETURN_HANGUP;
}
+#endif
+#if 0
/*!
* \brief Find the context for the transfer
* \param transferer
@@ -2323,118 +2201,9 @@ static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *
}
return s;
}
+#endif
-/*!
- * \brief Blind transfer user to another extension
- * \param chan channel to be transfered
- * \param peer channel initiated blind transfer
- * \param config
- * \param code
- * \param data
- * \param sense feature options
- *
- * Place chan on hold, check if transferred to parkinglot extension,
- * otherwise check extension exists and transfer caller.
- * \retval AST_FEATURE_RETURN_SUCCESS.
- * \retval -1 on failure.
- */
-static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
-{
- struct ast_channel *transferer;
- struct ast_channel *transferee;
- struct ast_exten *park_exten;
- const char *transferer_real_context;
- char xferto[256] = "";
- int res;
-
- ast_debug(1, "Executing Blind Transfer %s, %s (sense=%d) \n", ast_channel_name(chan), ast_channel_name(peer), sense);
- set_peers(&transferer, &transferee, peer, chan, sense);
- transferer_real_context = ast_strdupa(real_ctx(transferer, transferee));
-
- /* Start autoservice on transferee while we talk to the transferer */
- ast_autoservice_start(transferee);
- ast_indicate(transferee, AST_CONTROL_HOLD);
-
- /* Transfer */
- res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
- if (res < 0) {
- finishup(transferee);
- return -1; /* error ? */
- }
- if (res > 0) { /* If they've typed a digit already, handle it */
- xferto[0] = (char) res;
- }
-
- res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
- if (res < 0) { /* hangup or error, (would be 0 for invalid and 1 for valid) */
- finishup(transferee);
- return -1;
- }
- if (res == 0) {
- if (xferto[0]) {
- ast_log(LOG_WARNING, "Extension '%s' does not exist in context '%s'\n",
- xferto, transferer_real_context);
- } else {
- /* Does anyone care about this case? */
- ast_log(LOG_WARNING, "No digits dialed.\n");
- }
- ast_stream_and_wait(transferer, "pbx-invalid", "");
- finishup(transferee);
- return AST_FEATURE_RETURN_SUCCESS;
- }
-
- park_exten = get_parking_exten(xferto, transferer, transferer_real_context);
- if (park_exten) {
- /* We are transfering the transferee to a parking lot. */
- return xfer_park_call_helper(transferee, transferer, park_exten);
- }
-
- /* Do blind transfer. */
- ast_verb(3, "Blind transferring %s to '%s' (context %s) priority 1\n",
- ast_channel_name(transferee), xferto, transferer_real_context);
- ast_cel_report_event(transferer, AST_CEL_BLINDTRANSFER, NULL, xferto, transferee);
- pbx_builtin_setvar_helper(transferer, "BLINDTRANSFER", ast_channel_name(transferee));
- pbx_builtin_setvar_helper(transferee, "BLINDTRANSFER", ast_channel_name(transferer));
- finishup(transferee);
- ast_channel_lock(transferer);
- if (!ast_channel_cdr(transferer)) {
- /* this code should never get called (in a perfect world) */
- ast_channel_cdr_set(transferer, ast_cdr_alloc());
- if (ast_channel_cdr(transferer)) {
- ast_cdr_init(ast_channel_cdr(transferer), transferer); /* initialize our channel's cdr */
- ast_cdr_start(ast_channel_cdr(transferer));
- }
- }
- ast_channel_unlock(transferer);
- if (ast_channel_cdr(transferer)) {
- struct ast_cdr *swap = ast_channel_cdr(transferer);
-
- ast_debug(1,
- "transferer=%s; transferee=%s; lastapp=%s; lastdata=%s; chan=%s; dstchan=%s\n",
- ast_channel_name(transferer), ast_channel_name(transferee), ast_channel_cdr(transferer)->lastapp,
- ast_channel_cdr(transferer)->lastdata, ast_channel_cdr(transferer)->channel,
- ast_channel_cdr(transferer)->dstchannel);
- ast_debug(1, "TRANSFEREE; lastapp=%s; lastdata=%s, chan=%s; dstchan=%s\n",
- ast_channel_cdr(transferee)->lastapp, ast_channel_cdr(transferee)->lastdata, ast_channel_cdr(transferee)->channel,
- ast_channel_cdr(transferee)->dstchannel);
- ast_debug(1, "transferer_real_context=%s; xferto=%s\n",
- transferer_real_context, xferto);
- /* swap cdrs-- it will save us some time & work */
- ast_channel_cdr_set(transferer, ast_channel_cdr(transferee));
- ast_channel_cdr_set(transferee, swap);
- }
-
- res = ast_channel_pbx(transferee) ? AST_FEATURE_RETURN_SUCCESSBREAK : -1;
-
- /* Doh! Use our handy async_goto functions */
- if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) {
- ast_log(LOG_WARNING, "Async goto failed :-(\n");
- res = -1;
- }
- check_goto_on_transfer(transferer);
- return res;
-}
-
+#if 0
/*!
* \brief make channels compatible
* \param c
@@ -2452,7 +2221,9 @@ static int check_compat(struct ast_channel *c, struct ast_channel *newchan)
}
return 0;
}
+#endif
+#if 0
/*!
* \internal
* \brief Builtin attended transfer failed cleanup.
@@ -2482,7 +2253,9 @@ static void atxfer_fail_cleanup(struct ast_channel *transferee, struct ast_chann
}
ast_party_connected_line_free(connected_line);
}
+#endif
+#if 0
/*!
* \brief Attended transfer
* \param chan transfered user
@@ -2521,6 +2294,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
char *transferer_name;
char *transferer_name_orig;
char *dash;
+ RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
ast_debug(1, "Executing Attended Transfer %s, %s (sense=%d) \n", ast_channel_name(chan), ast_channel_name(peer), sense);
set_peers(&transferer, &transferee, peer, chan, sense);
@@ -2540,8 +2314,16 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
xferto[0] = (char) res;
}
+ ast_channel_lock(transferer);
+ xfer_cfg = ast_get_chan_features_xfer_config(transferer);
+ ast_channel_unlock(transferer);
+
+ /* XXX All accesses to the xfer_cfg structure after this point are not thread-safe,
+ * but I don't care because this is dead code.
+ */
+
/* this is specific of atxfer */
- res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
+ res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, xfer_cfg->transferdigittimeout);
if (res < 0) { /* hangup or error, (would be 0 for invalid and 1 for valid) */
finishup(transferee);
return -1;
@@ -2612,7 +2394,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
/* Dial party C */
newchan = feature_request_and_dial(transferer, transferer_name_orig, transferer,
transferee, "Local", ast_channel_nativeformats(transferer), xferto,
- atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
+ xfer_cfg->atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate);
if (!ast_check_hangup(transferer)) {
@@ -2629,12 +2411,12 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
case AST_CONTROL_UNHOLD:/* Caller requested cancel or party C answer timeout. */
case AST_CONTROL_BUSY:
case AST_CONTROL_CONGESTION:
- if (ast_stream_and_wait(transferer, xfersound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xfersound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
}
break;
default:
- if (ast_stream_and_wait(transferer, xferfailsound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xferfailsound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer failed sound!\n");
}
break;
@@ -2644,7 +2426,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
}
if (check_compat(transferer, newchan)) {
- if (ast_stream_and_wait(transferer, xferfailsound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xferfailsound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer failed sound!\n");
}
atxfer_fail_cleanup(transferee, transferer, &connected_line);
@@ -2662,7 +2444,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) {
ast_autoservice_chan_hangup_peer(transferer, newchan);
- if (ast_stream_and_wait(transferer, xfersound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xfersound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
}
atxfer_fail_cleanup(transferee, transferer, &connected_line);
@@ -2690,7 +2472,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
/* Transferer (party B) has hung up at this point. Doing blonde transfer. */
ast_debug(1, "Actually doing a blonde transfer.\n");
- if (!newchan && !atxferdropcall) {
+ if (!newchan && !xfer_cfg->atxferdropcall) {
/* Party C is not available, try to call party B back. */
unsigned int tries = 0;
@@ -2711,7 +2493,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
newchan = feature_request_and_dial(transferer, transferer_name_orig,
transferee, transferee, transferer_tech,
ast_channel_nativeformats(transferee), transferer_name,
- atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
+ xfer_cfg->atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
ast_debug(2, "Dial party B result: newchan:%d, outstate:%d\n",
!!newchan, outstate);
if (newchan) {
@@ -2743,16 +2525,16 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
}
++tries;
- if (atxfercallbackretries <= tries) {
+ if (xfer_cfg->atxfercallbackretries <= tries) {
/* No more callback tries remaining. */
break;
}
- if (atxferloopdelay) {
+ if (xfer_cfg->atxferloopdelay) {
/* Transfer failed, sleeping */
ast_debug(1, "Sleeping for %d ms before retrying atxfer.\n",
- atxferloopdelay);
- ast_safe_sleep(transferee, atxferloopdelay);
+ xfer_cfg->atxferloopdelay);
+ ast_safe_sleep(transferee, xfer_cfg->atxferloopdelay);
if (ast_check_hangup(transferee)) {
ast_party_connected_line_free(&connected_line);
return -1;
@@ -2764,7 +2546,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
newchan = feature_request_and_dial(transferer, transferer_name_orig,
transferer, transferee, "Local",
ast_channel_nativeformats(transferee), xferto,
- atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
+ xfer_cfg->atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
ast_debug(2, "Redial party C result: newchan:%d, outstate:%d\n",
!!newchan, outstate);
if (newchan || ast_check_hangup(transferee)) {
@@ -2925,351 +2707,14 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
ast_channel_update_connected_line(newchan, &connected_line, NULL);
}
- if (ast_stream_and_wait(newchan, xfersound, ""))
+ if (ast_stream_and_wait(newchan, xfer_cfg->xfersound, ""))
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
bridge_call_thread_launch(tobj);
ast_party_connected_line_free(&connected_line);
return -1;/* The transferee is masqueraded and the original bridged channels can be hungup. */
}
-
-/* add atxfer and automon as undefined so you can only use em if you configure them */
-#define FEATURES_COUNT ARRAY_LEN(builtin_features)
-
-AST_RWLOCK_DEFINE_STATIC(features_lock);
-
-/*! \note This is protected by features_lock. */
-static AST_LIST_HEAD_NOLOCK_STATIC(feature_list, ast_call_feature);
-
-static void ast_wrlock_call_features(void)
-{
- ast_rwlock_wrlock(&features_lock);
-}
-
-void ast_rdlock_call_features(void)
-{
- ast_rwlock_rdlock(&features_lock);
-}
-
-void ast_unlock_call_features(void)
-{
- ast_rwlock_unlock(&features_lock);
-}
-
-/*! \note This is protected by features_lock. */
-static struct ast_call_feature builtin_features[] = {
- { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_PARKCALL, "Park Call", "parkcall", "", "", builtin_parkcall, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_AUTOMIXMON, "One Touch MixMonitor", "automixmon", "", "", builtin_automixmonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
-};
-
-/*! \brief register new feature into feature_list */
-void ast_register_feature(struct ast_call_feature *feature)
-{
- if (!feature) {
- ast_log(LOG_NOTICE,"You didn't pass a feature!\n");
- return;
- }
-
- ast_wrlock_call_features();
- AST_LIST_INSERT_HEAD(&feature_list, feature, feature_entry);
- ast_unlock_call_features();
-
- ast_verb(2, "Registered Feature '%s'\n",feature->sname);
-}
-
-/*!
- * \brief Add new feature group
- * \param fgname feature group name.
- *
- * Add new feature group to the feature group list insert at head of list.
- * \note This function MUST be called while feature_groups is locked.
- */
-static struct feature_group *register_group(const char *fgname)
-{
- struct feature_group *fg;
-
- if (!fgname) {
- ast_log(LOG_NOTICE, "You didn't pass a new group name!\n");
- return NULL;
- }
-
- if (!(fg = ast_calloc_with_stringfields(1, struct feature_group, 128))) {
- return NULL;
- }
-
- ast_string_field_set(fg, gname, fgname);
-
- AST_LIST_INSERT_HEAD(&feature_groups, fg, entry);
-
- ast_verb(2, "Registered group '%s'\n", fg->gname);
-
- return fg;
-}
-
-/*!
- * \brief Add feature to group
- * \param fg feature group
- * \param exten
- * \param feature feature to add.
- *
- * Check fg and feature specified, add feature to list
- * \note This function MUST be called while feature_groups is locked.
- */
-static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature)
-{
- struct feature_group_exten *fge;
-
- if (!fg) {
- ast_log(LOG_NOTICE, "You didn't pass a group!\n");
- return;
- }
-
- if (!feature) {
- ast_log(LOG_NOTICE, "You didn't pass a feature!\n");
- return;
- }
-
- if (!(fge = ast_calloc_with_stringfields(1, struct feature_group_exten, 128))) {
- return;
- }
-
- ast_string_field_set(fge, exten, S_OR(exten, feature->exten));
-
- fge->feature = feature;
-
- AST_LIST_INSERT_HEAD(&fg->features, fge, entry);
-
- ast_verb(2, "Registered feature '%s' for group '%s' at exten '%s'\n",
- feature->sname, fg->gname, fge->exten);
-}
-
-void ast_unregister_feature(struct ast_call_feature *feature)
-{
- if (!feature) {
- return;
- }
-
- ast_wrlock_call_features();
- AST_LIST_REMOVE(&feature_list, feature, feature_entry);
- ast_unlock_call_features();
-
- ast_free(feature);
-}
-
-/*! \brief Remove all features in the list */
-static void ast_unregister_features(void)
-{
- struct ast_call_feature *feature;
-
- ast_wrlock_call_features();
- while ((feature = AST_LIST_REMOVE_HEAD(&feature_list, feature_entry))) {
- ast_free(feature);
- }
- ast_unlock_call_features();
-}
-
-/*!
- * \internal
- * \brief find a dynamic call feature by name
- * \pre Expects features_lock to be at least readlocked
- */
-static struct ast_call_feature *find_dynamic_feature(const char *name)
-{
- struct ast_call_feature *tmp;
-
- AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
- if (!strcasecmp(tmp->sname, name)) {
- break;
- }
- }
-
- return tmp;
-}
-
-/*! \brief Remove all feature groups in the list */
-static void ast_unregister_groups(void)
-{
- struct feature_group *fg;
- struct feature_group_exten *fge;
-
- AST_RWLIST_WRLOCK(&feature_groups);
- while ((fg = AST_LIST_REMOVE_HEAD(&feature_groups, entry))) {
- while ((fge = AST_LIST_REMOVE_HEAD(&fg->features, entry))) {
- ast_string_field_free_memory(fge);
- ast_free(fge);
- }
-
- ast_string_field_free_memory(fg);
- ast_free(fg);
- }
- AST_RWLIST_UNLOCK(&feature_groups);
-}
-
-/*!
- * \brief Find a group by name
- * \param name feature name
- * \retval feature group on success.
- * \retval NULL on failure.
- */
-static struct feature_group *find_group(const char *name)
-{
- struct feature_group *fg = NULL;
-
- AST_LIST_TRAVERSE(&feature_groups, fg, entry) {
- if (!strcasecmp(fg->gname, name))
- break;
- }
-
- return fg;
-}
-
-/*!
- * \internal
- * \pre Expects features_lock to be at least readlocked
- */
-struct ast_call_feature *ast_find_call_feature(const char *name)
-{
- int x;
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (!strcasecmp(name, builtin_features[x].sname))
- return &builtin_features[x];
- }
-
- return find_dynamic_feature(name);
-}
-
-struct feature_exten {
- char sname[FEATURE_SNAME_LEN];
- char exten[FEATURE_MAX_LEN];
-};
-
-struct feature_datastore {
- struct ao2_container *feature_map;
-
- /*!
- * \brief specified in seconds, stored in milliseconds
- *
- * \todo XXX This isn't pretty. At some point it would be nice to have all
- * of the global / [general] options in a config object that we store here
- * instead of handling each one manually.
- *
- * \note If anything gets added here, don't forget to update
- * feature_ds_duplicate, as well.
- * */
- unsigned int parkingtime;
- unsigned int parkingtime_is_set:1;
-};
-
-static int feature_exten_hash(const void *obj, int flags)
-{
- const struct feature_exten *fe = obj;
- const char *sname = obj;
-
- return ast_str_hash(flags & OBJ_KEY ? sname : fe->sname);
-}
-
-static int feature_exten_cmp(void *obj, void *arg, int flags)
-{
- const struct feature_exten *fe = obj, *fe2 = arg;
- const char *sname = arg;
-
- return !strcmp(fe->sname, flags & OBJ_KEY ? sname : fe2->sname) ?
- CMP_MATCH | CMP_STOP : 0;
-}
-
-static void feature_ds_destroy(void *data)
-{
- struct feature_datastore *feature_ds = data;
-
- if (feature_ds->feature_map) {
- ao2_ref(feature_ds->feature_map, -1);
- feature_ds->feature_map = NULL;
- }
-
- ast_free(feature_ds);
-}
-
-static void *feature_ds_duplicate(void *data)
-{
- struct feature_datastore *old_ds = data;
- struct feature_datastore *new_ds;
-
- if (!(new_ds = ast_calloc(1, sizeof(*new_ds)))) {
- return NULL;
- }
-
- if (old_ds->feature_map) {
- ao2_ref(old_ds->feature_map, +1);
- new_ds->feature_map = old_ds->feature_map;
- }
-
- new_ds->parkingtime = old_ds->parkingtime;
- new_ds->parkingtime_is_set = old_ds->parkingtime_is_set;
-
- return new_ds;
-}
-
-static const struct ast_datastore_info feature_ds_info = {
- .type = "FEATURE",
- .destroy = feature_ds_destroy,
- .duplicate = feature_ds_duplicate,
-};
-
-/*!
- * \internal
- * \brief Find or create feature datastore on a channel
- *
- * \pre chan is locked
- *
- * \return the data on the FEATURE datastore, or NULL on error
- */
-static struct feature_datastore *get_feature_ds(struct ast_channel *chan)
-{
- struct feature_datastore *feature_ds;
- struct ast_datastore *ds;
-
- if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
- feature_ds = ds->data;
- return feature_ds;
- }
-
- if (!(feature_ds = ast_calloc(1, sizeof(*feature_ds)))) {
- return NULL;
- }
-
- if (!(feature_ds->feature_map = ao2_container_alloc(7, feature_exten_hash, feature_exten_cmp))) {
- feature_ds_destroy(feature_ds);
- return NULL;
- }
-
- if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) {
- feature_ds_destroy(feature_ds);
- return NULL;
- }
-
- ds->data = feature_ds;
-
- ast_channel_datastore_add(chan, ds);
-
- return feature_ds;
-}
-
-static struct ast_datastore *get_feature_chan_ds(struct ast_channel *chan)
-{
- struct ast_datastore *ds;
-
- if (!(ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
- /* Hasn't been created yet. Trigger creation. */
- get_feature_ds(chan);
- ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL);
- }
-
- return ds;
-}
+#endif
/*!
* \internal
@@ -3283,414 +2728,42 @@ static struct ast_datastore *get_feature_chan_ds(struct ast_channel *chan)
static int builtin_feature_get_exten(struct ast_channel *chan, const char *feature_name,
char *buf, size_t len)
{
- struct ast_call_feature *feature;
- struct feature_datastore *feature_ds;
- struct feature_exten *fe = NULL;
-
- *buf = '\0';
-
- if (!(feature = ast_find_call_feature(feature_name))) {
- return -1;
- }
-
- ast_copy_string(buf, feature->exten, len);
-
- ast_unlock_call_features();
-
- ast_channel_lock(chan);
- if ((feature_ds = get_feature_ds(chan))) {
- fe = ao2_find(feature_ds->feature_map, feature_name, OBJ_KEY);
- }
- ast_channel_unlock(chan);
-
- ast_rdlock_call_features();
-
- if (fe) {
- ao2_lock(fe);
- ast_copy_string(buf, fe->exten, len);
- ao2_unlock(fe);
- ao2_ref(fe, -1);
- fe = NULL;
- }
-
- return 0;
-}
-
-/*!
- * \brief exec an app by feature
- * \param chan,peer,config,code,sense,data
- *
- * Find a feature, determine which channel activated
- * \retval AST_FEATURE_RETURN_NO_HANGUP_PEER
- * \retval -1 error.
- * \retval -2 when an application cannot be found.
- */
-static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
-{
- struct ast_app *app;
- struct ast_call_feature *feature = data;
- struct ast_channel *work, *idle;
- int res;
-
- if (!feature) { /* shouldn't ever happen! */
- ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n");
- return -1;
- }
-
- if (sense == FEATURE_SENSE_CHAN) {
- if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
- return AST_FEATURE_RETURN_KEEPTRYING;
- if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
- work = chan;
- idle = peer;
- } else {
- work = peer;
- idle = chan;
- }
- } else {
- if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
- return AST_FEATURE_RETURN_KEEPTRYING;
- if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
- work = peer;
- idle = chan;
- } else {
- work = chan;
- idle = peer;
- }
- }
-
- if (!(app = pbx_findapp(feature->app))) {
- ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app);
- return -2;
- }
-
- ast_autoservice_start(idle);
- ast_autoservice_ignore(idle, AST_FRAME_DTMF_END);
-
- pbx_builtin_setvar_helper(work, "DYNAMIC_PEERNAME", ast_channel_name(idle));
- pbx_builtin_setvar_helper(idle, "DYNAMIC_PEERNAME", ast_channel_name(work));
- pbx_builtin_setvar_helper(work, "DYNAMIC_FEATURENAME", feature->sname);
- pbx_builtin_setvar_helper(idle, "DYNAMIC_FEATURENAME", feature->sname);
-
- if (!ast_strlen_zero(feature->moh_class))
- ast_moh_start(idle, feature->moh_class, NULL);
-
- res = pbx_exec(work, app, feature->app_args);
-
- if (!ast_strlen_zero(feature->moh_class))
- ast_moh_stop(idle);
-
- ast_autoservice_stop(idle);
-
- if (res) {
- return AST_FEATURE_RETURN_SUCCESSBREAK;
- }
- return AST_FEATURE_RETURN_SUCCESS; /*! \todo XXX should probably return res */
-}
-
-static void unmap_features(void)
-{
- int x;
-
- ast_wrlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++)
- strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
- ast_unlock_call_features();
-}
-
-static int remap_feature(const char *name, const char *value)
-{
- int x, res = -1;
-
- ast_wrlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (strcasecmp(builtin_features[x].sname, name))
- continue;
-
- ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
- res = 0;
- break;
- }
- ast_unlock_call_features();
-
- return res;
-}
-
-/*!
- * \brief Helper function for feature_interpret and ast_feature_detect
- * \param chan,peer,config,code,sense,dynamic_features_buf,features,operation,feature
- *
- * Lock features list, browse for code, unlock list
- * If a feature is found and the operation variable is set, that feature's
- * operation is executed. The first feature found is copied to the feature parameter.
- * \retval res on success.
- * \retval -1 on failure.
- */
-static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel *peer,
- struct ast_bridge_config *config, const char *code, int sense, const struct ast_str *dynamic_features_buf,
- struct ast_flags *features, feature_interpret_op operation, struct ast_call_feature *feature)
-{
- int x;
- struct feature_group *fg = NULL;
- struct feature_group_exten *fge;
- struct ast_call_feature *tmpfeature;
- char *tmp, *tok;
- int res = AST_FEATURE_RETURN_PASSDIGITS;
- int feature_detected = 0;
-
- if (!(peer && chan && config) && operation == FEATURE_INTERPRET_DO) {
- return -1; /* can not run feature operation */
- }
-
- ast_rdlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- char feature_exten[FEATURE_MAX_LEN] = "";
-
- if (!ast_test_flag(features, builtin_features[x].feature_mask)) {
- continue;
- }
-
- if (builtin_feature_get_exten(chan, builtin_features[x].sname, feature_exten, sizeof(feature_exten))) {
- continue;
- }
-
- /* Feature is up for consideration */
-
- if (!strcmp(feature_exten, code)) {
- ast_debug(3, "Feature detected: fname=%s sname=%s exten=%s\n", builtin_features[x].fname, builtin_features[x].sname, feature_exten);
- if (operation == FEATURE_INTERPRET_CHECK) {
- res = AST_FEATURE_RETURN_SUCCESS; /* We found something */
- } else if (operation == FEATURE_INTERPRET_DO) {
- res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
- ast_test_suite_event_notify("FEATURE_DETECTION",
- "Result: success\r\n"
- "Feature: %s",
- builtin_features[x].sname);
- }
- if (feature) {
- memcpy(feature, &builtin_features[x], sizeof(*feature));
- }
- feature_detected = 1;
- break;
- } else if (!strncmp(feature_exten, code, strlen(code))) {
- if (res == AST_FEATURE_RETURN_PASSDIGITS) {
- res = AST_FEATURE_RETURN_STOREDIGITS;
- }
- }
- }
-
- if (operation == FEATURE_INTERPRET_CHECK && x == FEATURES_COUNT) {
- ast_test_suite_event_notify("FEATURE_DETECTION",
- "Result: fail");
- }
-
- ast_unlock_call_features();
-
- if (!dynamic_features_buf || !ast_str_strlen(dynamic_features_buf) || feature_detected) {
- return res;
- }
-
- tmp = ast_str_buffer(dynamic_features_buf);
-
- while ((tok = strsep(&tmp, "#"))) {
- AST_RWLIST_RDLOCK(&feature_groups);
- fg = find_group(tok);
- if (fg) {
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- if (!strcmp(fge->exten, code)) {
- if (operation) {
- res = fge->feature->operation(chan, peer, config, code, sense, fge->feature);
- }
- if (feature) {
- memcpy(feature, fge->feature, sizeof(*feature));
- }
- if (res != AST_FEATURE_RETURN_KEEPTRYING) {
- AST_RWLIST_UNLOCK(&feature_groups);
- break;
- }
- res = AST_FEATURE_RETURN_PASSDIGITS;
- } else if (!strncmp(fge->exten, code, strlen(code))) {
- res = AST_FEATURE_RETURN_STOREDIGITS;
- }
- }
- if (fge) {
- break;
- }
- }
- AST_RWLIST_UNLOCK(&feature_groups);
-
- ast_rdlock_call_features();
-
- if (!(tmpfeature = find_dynamic_feature(tok))) {
- ast_unlock_call_features();
- continue;
- }
-
- /* Feature is up for consideration */
- if (!strcmp(tmpfeature->exten, code)) {
- ast_verb(3, " Feature Found: %s exten: %s\n",tmpfeature->sname, tok);
- if (operation == FEATURE_INTERPRET_CHECK) {
- res = AST_FEATURE_RETURN_SUCCESS; /* We found something */
- } else if (operation == FEATURE_INTERPRET_DO) {
- res = tmpfeature->operation(chan, peer, config, code, sense, tmpfeature);
- }
- if (feature) {
- memcpy(feature, tmpfeature, sizeof(*feature));
- }
- if (res != AST_FEATURE_RETURN_KEEPTRYING) {
- ast_unlock_call_features();
- break;
- }
- res = AST_FEATURE_RETURN_PASSDIGITS;
- } else if (!strncmp(tmpfeature->exten, code, strlen(code)))
- res = AST_FEATURE_RETURN_STOREDIGITS;
-
- ast_unlock_call_features();
- }
-
- return res;
-}
-
-#if 0//BUGBUG
-/*!
- * \brief Check the dynamic features
- * \param chan,peer,config,code,sense
- *
- * \retval res on success.
- * \retval -1 on failure.
- */
-static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense) {
-
- struct ast_str *dynamic_features_buf;
- const char *peer_dynamic_features, *chan_dynamic_features;
- struct ast_flags features;
- struct ast_call_feature feature;
- int res;
-
- if (sense == FEATURE_SENSE_CHAN) {
- /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
- ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
- }
- else {
- /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
- ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
- }
-
- ast_channel_lock(peer);
- peer_dynamic_features = ast_strdupa(S_OR(pbx_builtin_getvar_helper(peer, "DYNAMIC_FEATURES"),""));
- ast_channel_unlock(peer);
-
- ast_channel_lock(chan);
- chan_dynamic_features = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"),""));
- ast_channel_unlock(chan);
-
- if (!(dynamic_features_buf = ast_str_create(128))) {
- return AST_FEATURE_RETURN_PASSDIGITS;
- }
-
- ast_str_set(&dynamic_features_buf, 0, "%s%s%s", S_OR(chan_dynamic_features, ""), chan_dynamic_features && peer_dynamic_features ? "#" : "", S_OR(peer_dynamic_features,""));
-
- ast_debug(3, "Feature interpret: chan=%s, peer=%s, code=%s, sense=%d, features=%d, dynamic=%s\n", ast_channel_name(chan), ast_channel_name(peer), code, sense, features.flags, ast_str_buffer(dynamic_features_buf));
-
- res = feature_interpret_helper(chan, peer, config, code, sense, dynamic_features_buf, &features, FEATURE_INTERPRET_DO, &feature);
-
- ast_free(dynamic_features_buf);
-
- return res;
-}
-#endif
+ SCOPED_CHANNELLOCK(lock, chan);
-
-int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) {
- return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature);
+ return ast_get_builtin_feature(chan, feature_name, buf, len);
}
-#if 0//BUGBUG
-/*! \brief Check if a feature exists */
-static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) {
- struct ast_str *chan_dynamic_features;
- int res;
-
- if (!(chan_dynamic_features = ast_str_create(128))) {
- return AST_FEATURE_RETURN_PASSDIGITS;
- }
- ast_channel_lock(chan);
- ast_str_set(&chan_dynamic_features, 0, "%s", S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"),""));
- ast_channel_unlock(chan);
-
- res = feature_interpret_helper(chan, NULL, NULL, code, 0, chan_dynamic_features, features, FEATURE_INTERPRET_CHECK, NULL);
-
- ast_free(chan_dynamic_features);
-
- return res;
-}
-#endif
-
static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
{
- int x;
-
/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */
/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */
/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */
ast_clear_flag(config, AST_FLAGS_ALL);
- ast_rdlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF))
- continue;
-
- if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
-
- if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
+ if (ast_test_flag(&config->features_caller, AST_FEATURE_DTMF_MASK)) {
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+ }
+ if (ast_test_flag(&config->features_callee, AST_FEATURE_DTMF_MASK)) {
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
}
- ast_unlock_call_features();
if (!(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
- const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
-
- if (dynamic_features) {
- char *tmp = ast_strdupa(dynamic_features);
- char *tok;
- struct ast_call_feature *feature;
-
- /* while we have a feature */
- while ((tok = strsep(&tmp, "#"))) {
- struct feature_group *fg;
-
- AST_RWLIST_RDLOCK(&feature_groups);
- fg = find_group(tok);
- if (fg) {
- struct feature_group_exten *fge;
+ RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- if (ast_test_flag(fge->feature, AST_FEATURE_FLAG_BYCALLER)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
- }
- if (ast_test_flag(fge->feature, AST_FEATURE_FLAG_BYCALLEE)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
- }
- }
- }
- AST_RWLIST_UNLOCK(&feature_groups);
+ ast_channel_lock(chan);
+ applicationmap = ast_get_chan_applicationmap(chan);
+ ast_channel_unlock(chan);
- ast_rdlock_call_features();
- if ((feature = find_dynamic_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
- if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
- }
- if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
- }
- }
- ast_unlock_call_features();
- }
+ if (!applicationmap) {
+ return;
}
+
+ /* If an applicationmap exists for this channel at all, then the channel needs the DTMF flag set */
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
}
}
+#if 0
/*!
* \internal
* \brief Get feature and dial.
@@ -3742,10 +2815,12 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
int ready = 0;
struct timeval started;
int x, len = 0;
- char *disconnect_code = NULL, *dialed_code = NULL;
+ char disconnect_code[AST_FEATURE_MAX_LEN];
+ char *dialed_code = NULL;
struct ast_format_cap *tmp_cap;
struct ast_format best_audio_fmt;
struct ast_frame *f;
+ int disconnect_res;
AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
tmp_cap = ast_format_cap_alloc_nolock();
@@ -3801,18 +2876,17 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
}
/* support dialing of the featuremap disconnect code while performing an attended tranfer */
- ast_rdlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (strcasecmp(builtin_features[x].sname, "disconnect"))
- continue;
+ ast_channel_lock(chan);
+ disconnect_res = ast_get_builtin_feature(chan, "disconnect",
+ disconnect_code, sizeof(disconnect_code));
+ ast_channel_unlock(chan);
- disconnect_code = builtin_features[x].exten;
+ if (!disconnect_res) {
len = strlen(disconnect_code) + 1;
dialed_code = ast_alloca(len);
memset(dialed_code, 0, len);
- break;
}
- ast_unlock_call_features();
+
x = 0;
started = ast_tvnow();
to = timeout;
@@ -3972,7 +3046,7 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
} else if (caller == active_channel) {
f = ast_read(caller);
if (f) {
- if (f->frametype == AST_FRAME_DTMF) {
+ if (f->frametype == AST_FRAME_DTMF && dialed_code) {
dialed_code[x++] = f->subclass.integer;
dialed_code[x] = '\0';
if (strlen(dialed_code) == len) {
@@ -4028,6 +3102,7 @@ done:
return chan;
}
+#endif
void ast_channel_log(char *title, struct ast_channel *chan);
@@ -4156,7 +3231,7 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st
static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
{
struct ast_flags *flags;
- char dtmf[FEATURE_MAX_LEN];
+ char dtmf[AST_FEATURE_MAX_LEN];
int res;
ast_channel_lock(chan);
@@ -4167,51 +3242,45 @@ static int setup_bridge_features_builtin(struct ast_bridge_features *features, s
}
res = 0;
- ast_rdlock_call_features();
if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
/* Add atxfer and blind transfer. */
- builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
+ if (!builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf))
+ && !ast_strlen_zero(dtmf)) {
/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
- builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
+ if (!builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
}
- if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) {
- builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
+ if (ast_test_flag(flags, AST_FEATURE_DISCONNECT) &&
+ !builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
- if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) {
- builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
+ if (ast_test_flag(flags, AST_FEATURE_PARKCALL) &&
+ !builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
- if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) {
- builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
+ if (ast_test_flag(flags, AST_FEATURE_AUTOMON) &&
+ !builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
- if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) {
- builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
+ if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON) &&
+ !builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
- ast_unlock_call_features();
#if 0 /* BUGBUG don't report errors untill all of the builtin features are supported. */
return res ? -1 : 0;
@@ -4310,6 +3379,20 @@ static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned
app_data, ast_free_ptr, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
+static int setup_dynamic_feature(void *obj, void *arg, void *data, int flags)
+{
+ struct ast_applicationmap_item *item = obj;
+ struct ast_bridge_features *features = arg;
+ int *res = data;
+
+ /* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (item->name) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
+ *res |= add_dynamic_dtmf_hook(features, item->activate_on_self ? AST_FEATURE_FLAG_ONSELF :
+ AST_FEATURE_FLAG_ONPEER, item->dtmf, item->app, item->app_data,
+ item->moh_class);
+
+ return 0;
+}
+
/*!
* \internal
* \brief Setup bridge dynamic features.
@@ -4323,47 +3406,18 @@ static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned
*/
static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
{
- const char *feat;
- char *dynamic_features = NULL;
- char *tok;
- int res;
+ RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
+ int res = 0;
ast_channel_lock(chan);
- feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
- if (!ast_strlen_zero(feat)) {
- dynamic_features = ast_strdupa(feat);
- }
+ applicationmap = ast_get_chan_applicationmap(chan);
ast_channel_unlock(chan);
- if (!dynamic_features) {
+ if (!applicationmap) {
return 0;
}
-/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
- res = 0;
- while ((tok = strsep(&dynamic_features, "#"))) {
- struct feature_group *fg;
- struct ast_call_feature *feature;
-
- AST_RWLIST_RDLOCK(&feature_groups);
- fg = find_group(tok);
- if (fg) {
- struct feature_group_exten *fge;
-
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten,
- fge->feature->app, fge->feature->app_args, fge->feature->moh_class);
- }
- }
- AST_RWLIST_UNLOCK(&feature_groups);
+ ao2_callback_data(applicationmap, 0, setup_dynamic_feature, features, &res);
- ast_rdlock_call_features();
- feature = find_dynamic_feature(tok);
- if (feature) {
- res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten,
- feature->app, feature->app_args, feature->moh_class);
- }
- ast_unlock_call_features();
- }
return res;
}
@@ -5205,130 +4259,6 @@ static const struct parkinglot_cfg parkinglot_cfg_default = {
/*!
* \internal
- * \brief Set parking lot feature flag configuration value.
- *
- * \param pl_name Parking lot name for diagnostic messages.
- * \param param Parameter value to set.
- * \param var Current configuration variable item.
- *
- * \return Nothing
- */
-static void parkinglot_feature_flag_cfg(const char *pl_name, int *param, struct ast_variable *var)
-{
- ast_debug(1, "Setting parking lot %s %s to %s\n", pl_name, var->name, var->value);
- if (!strcasecmp(var->value, "both")) {
- *param = AST_FEATURE_FLAG_BYBOTH;
- } else if (!strcasecmp(var->value, "caller")) {
- *param = AST_FEATURE_FLAG_BYCALLER;
- } else if (!strcasecmp(var->value, "callee")) {
- *param = AST_FEATURE_FLAG_BYCALLEE;
- }
-}
-
-/*!
- * \internal
- * \brief Read parking lot configuration.
- *
- * \param pl_name Parking lot name for diagnostic messages.
- * \param cfg Parking lot config to update that is already initialized with defaults.
- * \param var Config variable list.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int parkinglot_config_read(const char *pl_name, struct parkinglot_cfg *cfg, struct ast_variable *var)
-{
- int error = 0;
-
- while (var) {
- if (!strcasecmp(var->name, "context")) {
- ast_copy_string(cfg->parking_con, var->value, sizeof(cfg->parking_con));
- } else if (!strcasecmp(var->name, "parkext")) {
- ast_copy_string(cfg->parkext, var->value, sizeof(cfg->parkext));
- } else if (!strcasecmp(var->name, "parkext_exclusive")) {
- cfg->parkext_exclusive = ast_true(var->value);
- } else if (!strcasecmp(var->name, "parkinghints")) {
- cfg->parkaddhints = ast_true(var->value);
- } else if (!strcasecmp(var->name, "parkedmusicclass")) {
- ast_copy_string(cfg->mohclass, var->value, sizeof(cfg->mohclass));
- } else if (!strcasecmp(var->name, "parkingtime")) {
- unsigned int parkingtime = 0;
-
- if ((sscanf(var->value, "%30u", &parkingtime) != 1) || parkingtime < 1) {
- ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
- error = -1;
- } else {
- cfg->parkingtime = parkingtime * 1000;
- }
- } else if (!strcasecmp(var->name, "parkpos")) {
- int start = 0;
- int end = 0;
-
- if (sscanf(var->value, "%30d-%30d", &start, &end) != 2) {
- ast_log(LOG_WARNING,
- "Format for parking positions is a-b, where a and b are numbers at line %d of %s\n",
- var->lineno, var->file);
- error = -1;
- } else if (end < start || start <= 0 || end <= 0) {
- ast_log(LOG_WARNING, "Parking range is invalid. Must be a <= b, at line %d of %s\n",
- var->lineno, var->file);
- error = -1;
- } else {
- cfg->parking_start = start;
- cfg->parking_stop = end;
- }
- } else if (!strcasecmp(var->name, "findslot")) {
- cfg->parkfindnext = (!strcasecmp(var->value, "next"));
- } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcalltransfers, var);
- } else if (!strcasecmp(var->name, "parkedcallreparking")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcallreparking, var);
- } else if (!strcasecmp(var->name, "parkedcallhangup")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcallhangup, var);
- } else if (!strcasecmp(var->name, "parkedcallrecording")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcallrecording, var);
- } else if (!strcasecmp(var->name, "comebackcontext")) {
- ast_copy_string(cfg->comebackcontext, var->value, sizeof(cfg->comebackcontext));
- } else if (!strcasecmp(var->name, "comebacktoorigin")) {
- cfg->comebacktoorigin = ast_true(var->value);
- } else if (!strcasecmp(var->name, "comebackdialtime")) {
- if ((sscanf(var->value, "%30u", &cfg->comebackdialtime) != 1)
- || (cfg->comebackdialtime < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid comebackdialtime\n", var->value);
- cfg->parkingtime = DEFAULT_COMEBACK_DIAL_TIME;
- }
- }
- var = var->next;
- }
-
- /* Check for configuration errors */
- if (ast_strlen_zero(cfg->parking_con)) {
- ast_log(LOG_WARNING, "Parking lot %s needs context\n", pl_name);
- error = -1;
- }
- if (ast_strlen_zero(cfg->parkext)) {
- ast_log(LOG_WARNING, "Parking lot %s needs parkext\n", pl_name);
- error = -1;
- }
- if (!cfg->parking_start) {
- ast_log(LOG_WARNING, "Parking lot %s needs parkpos\n", pl_name);
- error = -1;
- }
- if (!cfg->comebacktoorigin && ast_strlen_zero(cfg->comebackcontext)) {
- ast_log(LOG_WARNING, "Parking lot %s has comebacktoorigin set false"
- "but has no comebackcontext.\n",
- pl_name);
- error = -1;
- }
- if (error) {
- cfg->is_invalid = 1;
- }
-
- return error;
-}
-
-/*!
- * \internal
* \brief Activate the given parkinglot.
*
* \param parkinglot Parking lot to activate.
@@ -5388,1181 +4318,6 @@ static int parkinglot_activate(struct ast_parkinglot *parkinglot)
return disabled ? -1 : 0;
}
-/*! \brief Build parkinglot from configuration and chain it in if it doesn't already exist */
-static struct ast_parkinglot *build_parkinglot(const char *pl_name, struct ast_variable *var)
-{
- struct ast_parkinglot *parkinglot;
- const struct parkinglot_cfg *cfg_defaults;
- struct parkinglot_cfg new_cfg;
- int cfg_error;
- int oldparkinglot = 0;
-
- parkinglot = find_parkinglot(pl_name);
- if (parkinglot) {
- oldparkinglot = 1;
- } else {
- parkinglot = create_parkinglot(pl_name);
- if (!parkinglot) {
- return NULL;
- }
- }
- if (!strcmp(parkinglot->name, DEFAULT_PARKINGLOT)) {
- cfg_defaults = &parkinglot_cfg_default_default;
- } else {
- cfg_defaults = &parkinglot_cfg_default;
- }
- new_cfg = *cfg_defaults;
-
- ast_debug(1, "Building parking lot %s\n", parkinglot->name);
-
- ao2_lock(parkinglot);
-
- /* Do some config stuff */
- cfg_error = parkinglot_config_read(parkinglot->name, &new_cfg, var);
- if (oldparkinglot) {
- if (cfg_error) {
- /* Bad configuration read. Keep using the original config. */
- ast_log(LOG_WARNING, "Changes to parking lot %s are discarded.\n",
- parkinglot->name);
- cfg_error = 0;
- } else if (!AST_LIST_EMPTY(&parkinglot->parkings)
- && memcmp(&new_cfg, &parkinglot->cfg, sizeof(parkinglot->cfg))) {
- /* Try reloading later when parking lot is empty. */
- ast_log(LOG_WARNING,
- "Parking lot %s has parked calls. Parking lot changes discarded.\n",
- parkinglot->name);
- force_reload_load = 1;
- } else {
- /* Accept the new config */
- parkinglot->cfg = new_cfg;
- }
- } else {
- /* Load the initial parking lot config. */
- parkinglot->cfg = new_cfg;
- }
- parkinglot->the_mark = 0;
-
- ao2_unlock(parkinglot);
-
- if (cfg_error) {
- /* Only new parking lots could have config errors here. */
- ast_log(LOG_WARNING, "New parking lot %s is discarded.\n", parkinglot->name);
- parkinglot_unref(parkinglot);
- return NULL;
- }
-
- /* Move it into the list, if it wasn't already there */
- if (!oldparkinglot) {
- ao2_link(parkinglots, parkinglot);
- }
- parkinglot_unref(parkinglot);
-
- return parkinglot;
-}
-
-/*!
- * \internal
- * \brief Process an applicationmap section config line.
- *
- * \param var Config variable line.
- *
- * \return Nothing
- */
-static void process_applicationmap_line(struct ast_variable *var)
-{
- char *tmp_val = ast_strdupa(var->value);
- char *activateon, *new_syn;
- struct ast_call_feature *feature;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(exten);
- AST_APP_ARG(activatedby);
- AST_APP_ARG(app);
- AST_APP_ARG(app_args);
- AST_APP_ARG(moh_class);
- );
-
- AST_STANDARD_APP_ARGS(args, tmp_val);
- if ((new_syn = strchr(args.app, '('))) {
- /* New syntax */
- args.moh_class = args.app_args;
- args.app_args = new_syn;
- *args.app_args++ = '\0';
- if (args.app_args[strlen(args.app_args) - 1] == ')') {
- args.app_args[strlen(args.app_args) - 1] = '\0';
- }
- }
-
- activateon = strsep(&args.activatedby, "/");
-
- /*! \todo XXX var_name or app_args ? */
- if (ast_strlen_zero(args.app)
- || ast_strlen_zero(args.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",
- args.app, args.exten, activateon, var->name);
- return;
- }
-
- ast_rdlock_call_features();
- if (find_dynamic_feature(var->name)) {
- ast_unlock_call_features();
- ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n",
- var->name);
- return;
- }
- ast_unlock_call_features();
-
- if (!(feature = ast_calloc(1, sizeof(*feature)))) {
- return;
- }
-
- ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN);
- ast_copy_string(feature->app, args.app, FEATURE_APP_LEN);
- ast_copy_string(feature->exten, args.exten, FEATURE_EXTEN_LEN);
-
- if (args.app_args) {
- ast_copy_string(feature->app_args, args.app_args, FEATURE_APP_ARGS_LEN);
- }
-
- if (args.moh_class) {
- ast_copy_string(feature->moh_class, args.moh_class, FEATURE_MOH_LEN);
- }
-
- ast_copy_string(feature->exten, args.exten, sizeof(feature->exten));
- feature->operation = feature_exec_app;
- ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF);
-
- /* Allow caller and callee 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);
- ast_free(feature);
- return;
- }
-
- /*
- * We will parse and require correct syntax for the ActivatedBy
- * option, but the ActivatedBy option is not honored anymore.
- */
- if (ast_strlen_zero(args.activatedby)) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
- } else if (!strcasecmp(args.activatedby, "caller")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER);
- } else if (!strcasecmp(args.activatedby, "callee")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE);
- } else if (!strcasecmp(args.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);
- ast_free(feature);
- return;
- }
-
- ast_register_feature(feature);
-
- ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n",
- var->name, args.app, args.app_args, args.exten);
-}
-
-static int process_config(struct ast_config *cfg)
-{
- int i;
- struct ast_variable *var = NULL;
- struct feature_group *fg = NULL;
- char *ctg;
- static const char * const categories[] = {
- /* Categories in features.conf that are not
- * to be parsed as group categories
- */
- "general",
- "featuremap",
- "applicationmap"
- };
-
- /* Set general features global defaults. */
- featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
-
- /* Set global call pickup defaults. */
- strcpy(pickup_ext, "*8");
- pickupsound[0] = '\0';
- pickupfailsound[0] = '\0';
-
- /* Set global call transfer defaults. */
- strcpy(xfersound, "beep");
- strcpy(xferfailsound, "beeperr");
- transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
- atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
- atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
- atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
- atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
-
- /* Set global call parking defaults. */
- courtesytone[0] = '\0';
- parkedplay = 0;
- adsipark = 0;
- parkeddynamic = 0;
-
- var = ast_variable_browse(cfg, "general");
- build_parkinglot(DEFAULT_PARKINGLOT, var);
- for (; var; var = var->next) {
- if (!strcasecmp(var->name, "parkeddynamic")) {
- parkeddynamic = ast_true(var->value);
- } else if (!strcasecmp(var->name, "adsipark")) {
- adsipark = ast_true(var->value);
- } else if (!strcasecmp(var->name, "transferdigittimeout")) {
- if ((sscanf(var->value, "%30d", &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, "%30d", &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, "%30d", &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, "%30u", &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, "%30u", &atxfercallbackretries) != 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, "pickupsound")) {
- ast_copy_string(pickupsound, var->value, sizeof(pickupsound));
- } else if (!strcasecmp(var->name, "pickupfailsound")) {
- ast_copy_string(pickupfailsound, var->value, sizeof(pickupfailsound));
- }
- }
-
- 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) {
- process_applicationmap_line(var);
- }
-
- ast_unregister_groups();
- AST_RWLIST_WRLOCK(&feature_groups);
-
- ctg = NULL;
- while ((ctg = ast_category_browse(cfg, ctg))) {
- /* Is this a parkinglot definition ? */
- if (!strncasecmp(ctg, "parkinglot_", strlen("parkinglot_"))) {
- ast_debug(2, "Found configuration section %s, assume parking context\n", ctg);
- if (!build_parkinglot(ctg, ast_variable_browse(cfg, ctg))) {
- ast_log(LOG_ERROR, "Could not build parking lot %s. Configuration error.\n", ctg);
- } else {
- ast_debug(1, "Configured parking context %s\n", ctg);
- }
- continue;
- }
-
- /* No, check if it's a group */
- 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_rdlock_call_features();
- feature = ast_find_call_feature(var->name);
- ast_unlock_call_features();
- if (!feature) {
- ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
- continue;
- }
-
- register_group_feature(fg, var->value, feature);
- }
- }
-
- AST_RWLIST_UNLOCK(&feature_groups);
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Destroy the given dialplan usage context.
- *
- * \param doomed Parking lot usage context to destroy.
- *
- * \return Nothing
- */
-static void destroy_dialplan_usage_context(struct parking_dp_context *doomed)
-{
- struct parking_dp_ramp *ramp;
- struct parking_dp_spaces *spaces;
-
- while ((ramp = AST_LIST_REMOVE_HEAD(&doomed->access_extens, node))) {
- ast_free(ramp);
- }
- while ((spaces = AST_LIST_REMOVE_HEAD(&doomed->spaces, node))) {
- ast_free(spaces);
- }
- while ((spaces = AST_LIST_REMOVE_HEAD(&doomed->hints, node))) {
- ast_free(spaces);
- }
- ast_free(doomed);
-}
-
-/*!
- * \internal
- * \brief Destroy the given dialplan usage map.
- *
- * \param doomed Parking lot usage map to destroy.
- *
- * \return Nothing
- */
-static void destroy_dialplan_usage_map(struct parking_dp_map *doomed)
-{
- struct parking_dp_context *item;
-
- while ((item = AST_LIST_REMOVE_HEAD(doomed, node))) {
- destroy_dialplan_usage_context(item);
- }
-}
-
-/*!
- * \internal
- * \brief Create a new parking lot ramp dialplan usage node.
- *
- * \param exten Parking lot access ramp extension.
- * \param exclusive TRUE if the parking lot access ramp extension is exclusive.
- *
- * \retval New usage ramp node on success.
- * \retval NULL on error.
- */
-static struct parking_dp_ramp *build_dialplan_useage_ramp(const char *exten, int exclusive)
-{
- struct parking_dp_ramp *ramp_node;
-
- ramp_node = ast_calloc(1, sizeof(*ramp_node) + strlen(exten));
- if (!ramp_node) {
- return NULL;
- }
- ramp_node->exclusive = exclusive;
- strcpy(ramp_node->exten, exten);
- return ramp_node;
-}
-
-/*!
- * \internal
- * \brief Add parking lot access ramp to the context ramp usage map.
- *
- * \param ramp_map Current parking lot context ramp usage map.
- * \param exten Parking lot access ramp extension to add.
- * \param exclusive TRUE if the parking lot access ramp extension is exclusive.
- * \param lot Parking lot supplying reference data.
- * \param complain TRUE if to complain of parking lot ramp conflicts.
- *
- * \retval 0 on success. The ramp_map is updated.
- * \retval -1 on failure.
- */
-static int usage_context_add_ramp(struct parking_dp_ramp_map *ramp_map, const char *exten, int exclusive, struct ast_parkinglot *lot, int complain)
-{
- struct parking_dp_ramp *cur_ramp;
- struct parking_dp_ramp *new_ramp;
- int cmp;
-
- /* Make sure that exclusive is only 0 or 1 */
- if (exclusive) {
- exclusive = 1;
- }
-
- AST_LIST_TRAVERSE_SAFE_BEGIN(ramp_map, cur_ramp, node) {
- cmp = strcmp(exten, cur_ramp->exten);
- if (cmp > 0) {
- /* The parking lot ramp goes after this node. */
- continue;
- }
- if (cmp == 0) {
- /* The ramp is already in the map. */
- if (complain && (cur_ramp->exclusive || exclusive)) {
- ast_log(LOG_WARNING,
- "Parking lot '%s' parkext %s@%s used by another parking lot.\n",
- lot->name, exten, lot->cfg.parking_con);
- }
- return 0;
- }
- /* The new parking lot ramp goes before this node. */
- new_ramp = build_dialplan_useage_ramp(exten, exclusive);
- if (!new_ramp) {
- return -1;
- }
- AST_LIST_INSERT_BEFORE_CURRENT(new_ramp, node);
- return 0;
- }
- AST_LIST_TRAVERSE_SAFE_END;
-
- /* New parking lot access ramp goes on the end. */
- new_ramp = build_dialplan_useage_ramp(exten, exclusive);
- if (!new_ramp) {
- return -1;
- }
- AST_LIST_INSERT_TAIL(ramp_map, new_ramp, node);
- return 0;
-}
-
-/*!
- * \internal
- * \brief Create a new parking lot spaces dialplan usage node.
- *
- * \param start First parking lot space to add.
- * \param stop Last parking lot space to add.
- *
- * \retval New usage ramp node on success.
- * \retval NULL on error.
- */
-static struct parking_dp_spaces *build_dialplan_useage_spaces(int start, int stop)
-{
- struct parking_dp_spaces *spaces_node;
-
- spaces_node = ast_calloc(1, sizeof(*spaces_node));
- if (!spaces_node) {
- return NULL;
- }
- spaces_node->start = start;
- spaces_node->stop = stop;
- return spaces_node;
-}
-
-/*!
- * \internal
- * \brief Add parking lot spaces to the context space usage map.
- *
- * \param space_map Current parking lot context space usage map.
- * \param start First parking lot space to add.
- * \param stop Last parking lot space to add.
- * \param lot Parking lot supplying reference data.
- * \param complain TRUE if to complain of parking lot spaces conflicts.
- *
- * \retval 0 on success. The space_map is updated.
- * \retval -1 on failure.
- */
-static int usage_context_add_spaces(struct parking_dp_space_map *space_map, int start, int stop, struct ast_parkinglot *lot, int complain)
-{
- struct parking_dp_spaces *cur_node;
- struct parking_dp_spaces *expand_node;
- struct parking_dp_spaces *new_node;
-
- expand_node = NULL;
- AST_LIST_TRAVERSE_SAFE_BEGIN(space_map, cur_node, node) {
- /* NOTE: stop + 1 to combine immediately adjacent nodes into one. */
- if (expand_node) {
- /* The previous node is expanding to possibly eat following nodes. */
- if (expand_node->stop + 1 < cur_node->start) {
- /* Current node is completely after expanding node. */
- return 0;
- }
-
- if (complain
- && ((cur_node->start <= start && start <= cur_node->stop)
- || (cur_node->start <= stop && stop <= cur_node->stop)
- || (start < cur_node->start && cur_node->stop < stop))) {
- /* Only complain once per range add. */
- complain = 0;
- ast_log(LOG_WARNING,
- "Parking lot '%s' parkpos %d-%d@%s overlaps another parking lot.\n",
- lot->name, start, stop, lot->cfg.parking_con);
- }
-
- /* Current node is eaten by the expanding node. */
- if (expand_node->stop < cur_node->stop) {
- expand_node->stop = cur_node->stop;
- }
- AST_LIST_REMOVE_CURRENT(node);
- ast_free(cur_node);
- continue;
- }
-
- if (cur_node->stop + 1 < start) {
- /* New range is completely after current node. */
- continue;
- }
- if (stop + 1 < cur_node->start) {
- /* New range is completely before current node. */
- new_node = build_dialplan_useage_spaces(start, stop);
- if (!new_node) {
- return -1;
- }
- AST_LIST_INSERT_BEFORE_CURRENT(new_node, node);
- return 0;
- }
-
- if (complain
- && ((cur_node->start <= start && start <= cur_node->stop)
- || (cur_node->start <= stop && stop <= cur_node->stop)
- || (start < cur_node->start && cur_node->stop < stop))) {
- /* Only complain once per range add. */
- complain = 0;
- ast_log(LOG_WARNING,
- "Parking lot '%s' parkpos %d-%d@%s overlaps another parking lot.\n",
- lot->name, start, stop, lot->cfg.parking_con);
- }
-
- /* Current node range overlaps or is immediately adjacent to new range. */
- if (start < cur_node->start) {
- /* Expand the current node in the front. */
- cur_node->start = start;
- }
- if (stop <= cur_node->stop) {
- /* Current node is not expanding in the rear. */
- return 0;
- }
- cur_node->stop = stop;
- expand_node = cur_node;
- }
- AST_LIST_TRAVERSE_SAFE_END;
-
- if (expand_node) {
- /*
- * The previous node expanded and either ate all following nodes
- * or it was the last node.
- */
- return 0;
- }
-
- /* New range goes on the end. */
- new_node = build_dialplan_useage_spaces(start, stop);
- if (!new_node) {
- return -1;
- }
- AST_LIST_INSERT_TAIL(space_map, new_node, node);
- return 0;
-}
-
-/*!
- * \internal
- * \brief Add parking lot spaces to the context dialplan usage node.
- *
- * \param ctx_node Usage node to add parking lot spaces.
- * \param lot Parking lot to add data to ctx_node.
- * \param complain TRUE if to complain of parking lot ramp and spaces conflicts.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int dialplan_usage_add_parkinglot_data(struct parking_dp_context *ctx_node, struct ast_parkinglot *lot, int complain)
-{
- if (usage_context_add_ramp(&ctx_node->access_extens, lot->cfg.parkext,
- lot->cfg.parkext_exclusive, lot, complain)) {
- return -1;
- }
- if (usage_context_add_spaces(&ctx_node->spaces, lot->cfg.parking_start,
- lot->cfg.parking_stop, lot, complain)) {
- return -1;
- }
- if (lot->cfg.parkaddhints
- && usage_context_add_spaces(&ctx_node->hints, lot->cfg.parking_start,
- lot->cfg.parking_stop, lot, 0)) {
- return -1;
- }
- return 0;
-}
-
-/*!
- * \internal
- * \brief Create a new parking lot context dialplan usage node.
- *
- * \param lot Parking lot to create a new dialplan usage from.
- *
- * \retval New usage context node on success.
- * \retval NULL on error.
- */
-static struct parking_dp_context *build_dialplan_useage_context(struct ast_parkinglot *lot)
-{
- struct parking_dp_context *ctx_node;
-
- ctx_node = ast_calloc(1, sizeof(*ctx_node) + strlen(lot->cfg.parking_con));
- if (!ctx_node) {
- return NULL;
- }
- if (dialplan_usage_add_parkinglot_data(ctx_node, lot, 0)) {
- destroy_dialplan_usage_context(ctx_node);
- return NULL;
- }
- strcpy(ctx_node->context, lot->cfg.parking_con);
- return ctx_node;
-}
-
-/*!
- * \internal
- * \brief Add the given parking lot dialplan usage to the dialplan usage map.
- *
- * \param usage_map Parking lot usage map to add the given parking lot.
- * \param lot Parking lot to add dialplan usage.
- * \param complain TRUE if to complain of parking lot ramp and spaces conflicts.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int dialplan_usage_add_parkinglot(struct parking_dp_map *usage_map, struct ast_parkinglot *lot, int complain)
-{
- struct parking_dp_context *cur_ctx;
- struct parking_dp_context *new_ctx;
- int cmp;
-
- AST_LIST_TRAVERSE_SAFE_BEGIN(usage_map, cur_ctx, node) {
- cmp = strcmp(lot->cfg.parking_con, cur_ctx->context);
- if (cmp > 0) {
- /* The parking lot context goes after this node. */
- continue;
- }
- if (cmp == 0) {
- /* This is the node we will add parking lot spaces to the map. */
- return dialplan_usage_add_parkinglot_data(cur_ctx, lot, complain);
- }
- /* The new parking lot context goes before this node. */
- new_ctx = build_dialplan_useage_context(lot);
- if (!new_ctx) {
- return -1;
- }
- AST_LIST_INSERT_BEFORE_CURRENT(new_ctx, node);
- return 0;
- }
- AST_LIST_TRAVERSE_SAFE_END;
-
- /* New parking lot context goes on the end. */
- new_ctx = build_dialplan_useage_context(lot);
- if (!new_ctx) {
- return -1;
- }
- AST_LIST_INSERT_TAIL(usage_map, new_ctx, node);
- return 0;
-}
-
-/*!
- * \internal
- * \brief Build the dialplan usage map of the current parking lot container.
- *
- * \param usage_map Parking lot usage map. Must already be initialized.
- * \param complain TRUE if to complain of parking lot ramp and spaces conflicts.
- *
- * \retval 0 on success. The usage_map is filled in.
- * \retval -1 on failure. Built usage_map is incomplete.
- */
-static int build_dialplan_useage_map(struct parking_dp_map *usage_map, int complain)
-{
- int status = 0;
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
-
- /* For all parking lots */
- iter = ao2_iterator_init(parkinglots, 0);
- for (; (curlot = ao2_iterator_next(&iter)); ao2_ref(curlot, -1)) {
- /* Add the parking lot to the map. */
- if (dialplan_usage_add_parkinglot(usage_map, curlot, complain)) {
- ao2_ref(curlot, -1);
- status = -1;
- break;
- }
- }
- ao2_iterator_destroy(&iter);
-
- return status;
-}
-
-/*!
- * \internal
- * \brief Remove the given extension if it exists.
- *
- * \param context Dialplan database context name.
- * \param exten Extension to remove.
- * \param priority Extension priority to remove.
- *
- * \return Nothing
- */
-static void remove_exten_if_exist(const char *context, const char *exten, int priority)
-{
- struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
-
- if (pbx_find_extension(NULL, NULL, &q, context, exten, priority, NULL, NULL,
- E_MATCH)) {
- ast_debug(1, "Removing unneeded parking lot exten: %s@%s priority:%d\n",
- context, exten, priority);
- ast_context_remove_extension(context, exten, priority, registrar);
- }
-}
-
-/*!
- * \internal
- * \brief Remove unused parking lot access ramp items.
- *
- * \param context Dialplan database context name.
- * \param old_ramps Before configuration reload access ramp usage map.
- * \param new_ramps After configuration reload access ramp usage map.
- *
- * \details
- * Remove access ramp items that were in the old context but not in the
- * new context.
- *
- * \return Nothing
- */
-static void remove_dead_ramp_usage(const char *context, struct parking_dp_ramp_map *old_ramps, struct parking_dp_ramp_map *new_ramps)
-{
- struct parking_dp_ramp *old_ramp;
- struct parking_dp_ramp *new_ramp;
- int cmp;
-
- old_ramp = AST_LIST_FIRST(old_ramps);
- new_ramp = AST_LIST_FIRST(new_ramps);
-
- while (new_ramp) {
- if (!old_ramp) {
- /* No old ramps left, so no dead ramps can remain. */
- return;
- }
- cmp = strcmp(old_ramp->exten, new_ramp->exten);
- if (cmp < 0) {
- /* New map does not have old ramp. */
- remove_exten_if_exist(context, old_ramp->exten, 1);
- old_ramp = AST_LIST_NEXT(old_ramp, node);
- continue;
- }
- if (cmp == 0) {
- /* Old and new map have this ramp. */
- old_ramp = AST_LIST_NEXT(old_ramp, node);
- } else {
- /* Old map does not have new ramp. */
- }
- new_ramp = AST_LIST_NEXT(new_ramp, node);
- }
-
- /* Any old ramps left must be dead. */
- for (; old_ramp; old_ramp = AST_LIST_NEXT(old_ramp, node)) {
- remove_exten_if_exist(context, old_ramp->exten, 1);
- }
-}
-
-/*!
- * \internal
- * \brief Destroy the given parking space.
- *
- * \param context Dialplan database context name.
- * \param space Parking space.
- *
- * \return Nothing
- */
-static void destroy_space(const char *context, int space)
-{
- char exten[AST_MAX_EXTENSION];
-
- /* Destroy priorities of the parking space that we registered. */
- snprintf(exten, sizeof(exten), "%d", space);
- remove_exten_if_exist(context, exten, PRIORITY_HINT);
- remove_exten_if_exist(context, exten, 1);
-}
-
-/*!
- * \internal
- * \brief Remove unused parking lot space items.
- *
- * \param context Dialplan database context name.
- * \param old_spaces Before configuration reload parking space usage map.
- * \param new_spaces After configuration reload parking space usage map.
- * \param destroy_space Function to destroy parking space item.
- *
- * \details
- * Remove parking space items that were in the old context but
- * not in the new context.
- *
- * \return Nothing
- */
-static void remove_dead_spaces_usage(const char *context,
- struct parking_dp_space_map *old_spaces, struct parking_dp_space_map *new_spaces,
- void (*destroy_space)(const char *context, int space))
-{
- struct parking_dp_spaces *old_range;
- struct parking_dp_spaces *new_range;
- int space;/*!< Current position in the current old range. */
- int stop;
-
- old_range = AST_LIST_FIRST(old_spaces);
- new_range = AST_LIST_FIRST(new_spaces);
- space = -1;
-
- while (old_range) {
- if (space < old_range->start) {
- space = old_range->start;
- }
- if (new_range) {
- if (space < new_range->start) {
- /* Current position in old range starts before new range. */
- if (old_range->stop < new_range->start) {
- /* Old range ends before new range. */
- stop = old_range->stop;
- old_range = AST_LIST_NEXT(old_range, node);
- } else {
- /* Tail of old range overlaps new range. */
- stop = new_range->start - 1;
- }
- } else if (/* new_range->start <= space && */ space <= new_range->stop) {
- /* Current position in old range overlaps new range. */
- if (old_range->stop <= new_range->stop) {
- /* Old range ends at or before new range. */
- old_range = AST_LIST_NEXT(old_range, node);
- } else {
- /* Old range extends beyond end of new range. */
- space = new_range->stop + 1;
- new_range = AST_LIST_NEXT(new_range, node);
- }
- continue;
- } else /* if (new_range->stop < space) */ {
- /* Current position in old range starts after new range. */
- new_range = AST_LIST_NEXT(new_range, node);
- continue;
- }
- } else {
- /* No more new ranges. All remaining old spaces are dead. */
- stop = old_range->stop;
- old_range = AST_LIST_NEXT(old_range, node);
- }
-
- /* Destroy dead parking spaces. */
- for (; space <= stop; ++space) {
- destroy_space(context, space);
- }
- }
-}
-
-/*!
- * \internal
- * \brief Remove unused parking lot context items.
- *
- * \param context Dialplan database context name.
- * \param old_ctx Before configuration reload context usage map.
- * \param new_ctx After configuration reload context usage map.
- *
- * \details
- * Remove context usage items that were in the old context but not in the
- * new context.
- *
- * \return Nothing
- */
-static void remove_dead_context_usage(const char *context, struct parking_dp_context *old_ctx, struct parking_dp_context *new_ctx)
-{
- remove_dead_ramp_usage(context, &old_ctx->access_extens, &new_ctx->access_extens);
- remove_dead_spaces_usage(context, &old_ctx->spaces, &new_ctx->spaces, destroy_space);
-#if 0
- /* I don't think we should destroy hints if the parking space still exists. */
- remove_dead_spaces_usage(context, &old_ctx->hints, &new_ctx->hints, destroy_space_hint);
-#endif
-}
-
-/*!
- * \internal
- * \brief Remove unused parking lot dialplan items.
- *
- * \param old_map Before configuration reload dialplan usage map.
- * \param new_map After configuration reload dialplan usage map.
- *
- * \details
- * Remove dialplan items that were in the old map but not in the
- * new map.
- *
- * \return Nothing
- */
-static void remove_dead_dialplan_useage(struct parking_dp_map *old_map, struct parking_dp_map *new_map)
-{
- struct parking_dp_context *old_ctx;
- struct parking_dp_context *new_ctx;
- struct ast_context *con;
- int cmp;
-
- old_ctx = AST_LIST_FIRST(old_map);
- new_ctx = AST_LIST_FIRST(new_map);
-
- while (new_ctx) {
- if (!old_ctx) {
- /* No old contexts left, so no dead stuff can remain. */
- return;
- }
- cmp = strcmp(old_ctx->context, new_ctx->context);
- if (cmp < 0) {
- /* New map does not have old map context. */
- con = ast_context_find(old_ctx->context);
- if (con) {
- ast_context_destroy(con, registrar);
- }
- old_ctx = AST_LIST_NEXT(old_ctx, node);
- continue;
- }
- if (cmp == 0) {
- /* Old and new map have this context. */
- remove_dead_context_usage(old_ctx->context, old_ctx, new_ctx);
- old_ctx = AST_LIST_NEXT(old_ctx, node);
- } else {
- /* Old map does not have new map context. */
- }
- new_ctx = AST_LIST_NEXT(new_ctx, node);
- }
-
- /* Any old contexts left must be dead. */
- for (; old_ctx; old_ctx = AST_LIST_NEXT(old_ctx, node)) {
- con = ast_context_find(old_ctx->context);
- if (con) {
- ast_context_destroy(con, registrar);
- }
- }
-}
-
-static int parkinglot_markall_cb(void *obj, void *arg, int flags)
-{
- struct ast_parkinglot *parkinglot = obj;
-
- parkinglot->the_mark = 1;
- return 0;
-}
-
-static int parkinglot_is_marked_cb(void *obj, void *arg, int flags)
-{
- struct ast_parkinglot *parkinglot = obj;
-
- if (parkinglot->the_mark) {
- if (AST_LIST_EMPTY(&parkinglot->parkings)) {
- /* This parking lot can actually be deleted. */
- return CMP_MATCH;
- }
- /* Try reloading later when parking lot is empty. */
- ast_log(LOG_WARNING,
- "Parking lot %s has parked calls. Could not remove.\n",
- parkinglot->name);
- parkinglot->disabled = 1;
- force_reload_load = 1;
- }
-
- return 0;
-}
-
-static int parkinglot_activate_cb(void *obj, void *arg, int flags)
-{
- struct ast_parkinglot *parkinglot = obj;
-
- if (parkinglot->the_mark) {
- /*
- * Don't activate a parking lot that still bears the_mark since
- * it is effectively deleted.
- */
- return 0;
- }
-
- if (parkinglot_activate(parkinglot)) {
- /*
- * The parking lot failed to activate. Allow reloading later to
- * see if that fixes it.
- */
- force_reload_load = 1;
- ast_log(LOG_WARNING, "Parking lot %s not open for business.\n", parkinglot->name);
- } else {
- ast_debug(1, "Parking lot %s now open for business. (parkpos %d-%d)\n",
- parkinglot->name, parkinglot->cfg.parking_start,
- parkinglot->cfg.parking_stop);
- }
-
- return 0;
-}
-
-static int load_config(int reload)
-{
- struct ast_flags config_flags = {
- reload && !force_reload_load ? CONFIG_FLAG_FILEUNCHANGED : 0 };
- struct ast_config *cfg;
- struct parking_dp_map old_usage_map = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
- struct parking_dp_map new_usage_map = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
-
- /* We are reloading now and have already determined if we will force the reload. */
- force_reload_load = 0;
-
- if (!default_parkinglot) {
- /* Must create the default default parking lot */
- default_parkinglot = build_parkinglot(DEFAULT_PARKINGLOT, NULL);
- if (!default_parkinglot) {
- ast_log(LOG_ERROR, "Configuration of default default parking lot failed.\n");
- return -1;
- }
- ast_debug(1, "Configuration of default default parking lot done.\n");
- }
-
- cfg = ast_config_load2("features.conf", "features", config_flags);
- if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
- /* No sense in asking for reload trouble if nothing changed. */
- ast_debug(1, "features.conf did not change.\n");
- return 0;
- }
- if (cfg == CONFIG_STATUS_FILEMISSING
- || cfg == CONFIG_STATUS_FILEINVALID) {
- ast_log(LOG_WARNING, "Could not load features.conf\n");
- return 0;
- }
-
- /* Save current parking lot dialplan needs. */
- if (build_dialplan_useage_map(&old_usage_map, 0)) {
- destroy_dialplan_usage_map(&old_usage_map);
-
- /* Allow reloading later to see if conditions have improved. */
- force_reload_load = 1;
- return -1;
- }
-
- ao2_t_callback(parkinglots, OBJ_NODATA, parkinglot_markall_cb, NULL,
- "callback to mark all parking lots");
- process_config(cfg);
- ast_config_destroy(cfg);
- ao2_t_callback(parkinglots, OBJ_NODATA | OBJ_UNLINK, parkinglot_is_marked_cb, NULL,
- "callback to remove marked parking lots");
-
- /* Save updated parking lot dialplan needs. */
- if (build_dialplan_useage_map(&new_usage_map, 1)) {
- /*
- * Yuck, if this failure caused any parking lot dialplan items
- * to be lost, they will likely remain lost until Asterisk is
- * restarted.
- */
- destroy_dialplan_usage_map(&old_usage_map);
- destroy_dialplan_usage_map(&new_usage_map);
- return -1;
- }
-
- /* Remove no longer needed parking lot dialplan usage. */
- remove_dead_dialplan_useage(&old_usage_map, &new_usage_map);
-
- destroy_dialplan_usage_map(&old_usage_map);
- destroy_dialplan_usage_map(&new_usage_map);
-
- ao2_t_callback(parkinglots, OBJ_NODATA, parkinglot_activate_cb, NULL,
- "callback to activate all parking lots");
-
- return 0;
-}
-
-/*!
- * \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;
-#define HFS_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, HFS_FORMAT, "Builtin Feature", "Default", "Current");
- ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
-
- ast_cli(a->fd, HFS_FORMAT, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
-
- ast_rdlock_call_features();
- for (i = 0; i < FEATURES_COUNT; i++)
- ast_cli(a->fd, HFS_FORMAT, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
- ast_unlock_call_features();
-
- ast_cli(a->fd, "\n");
- ast_cli(a->fd, HFS_FORMAT, "Dynamic Feature", "Default", "Current");
- ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
- ast_rdlock_call_features();
- if (AST_LIST_EMPTY(&feature_list)) {
- ast_cli(a->fd, "(none)\n");
- } else {
- AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
- ast_cli(a->fd, HFS_FORMAT, feature->sname, "no def", feature->exten);
- }
- }
- ast_unlock_call_features();
-
- ast_cli(a->fd, "\nFeature Groups:\n");
- ast_cli(a->fd, "---------------\n");
- AST_RWLIST_RDLOCK(&feature_groups);
- if (AST_RWLIST_EMPTY(&feature_groups)) {
- ast_cli(a->fd, "(none)\n");
- } else {
- struct feature_group *fg;
- struct feature_group_exten *fge;
-
- AST_RWLIST_TRAVERSE(&feature_groups, fg, entry) {
- ast_cli(a->fd, "===> Group: %s\n", fg->gname);
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- ast_cli(a->fd, "===> --> %s (%s)\n", fge->feature->sname, fge->exten);
- }
- }
- }
- AST_RWLIST_UNLOCK(&feature_groups);
-
- ast_cli(a->fd, "\n");
-
- return CLI_SUCCESS;
-}
-
int ast_features_reload(void)
{
struct ast_context *con;
@@ -6583,7 +4338,7 @@ int ast_features_reload(void)
ast_context_destroy(con, registrar);
}
- res = load_config(1);
+ res = ast_features_config_reload();
ast_mutex_unlock(&features_reload_lock);
return res;
@@ -6633,10 +4388,18 @@ static int add_to_bridge(struct ast_bridge *bridge, struct ast_channel *chan,
struct ast_bridge_features *features, int play_tone)
{
RAII_VAR(struct ast_bridge *, chan_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
struct ast_channel *bridge_chan = NULL;
+ const char *tone = NULL;
ast_channel_lock(chan);
chan_bridge = ast_channel_get_bridge(chan);
+ xfer_cfg = ast_get_chan_features_xfer_config(chan);
+ if (!xfer_cfg) {
+ ast_log(LOG_ERROR, "Unable to determine what tone to play to channel.\n");
+ } else {
+ tone = ast_strdupa(xfer_cfg->xfersound);
+ }
ast_channel_unlock(chan);
if (chan_bridge) {
@@ -6661,7 +4424,7 @@ static int add_to_bridge(struct ast_bridge *bridge, struct ast_channel *chan,
}
}
- if (play_tone && !ast_strlen_zero(xfersound)) {
+ if (play_tone && !ast_strlen_zero(tone)) {
struct ast_channel *play_chan = bridge_chan ?: chan;
RAII_VAR(struct ast_bridge_channel *, play_bridge_channel, NULL, ao2_cleanup);
@@ -6673,7 +4436,7 @@ static int add_to_bridge(struct ast_bridge *bridge, struct ast_channel *chan,
ast_log(LOG_WARNING, "Unable to play tone for channel %s. Unable to get bridge channel\n",
ast_channel_name(play_chan));
} else {
- ast_bridge_channel_queue_playfile(play_bridge_channel, NULL, xfersound, NULL);
+ ast_bridge_channel_queue_playfile(play_bridge_channel, NULL, tone, NULL);
}
}
return 0;
@@ -6822,7 +4585,6 @@ static int action_bridge(struct mansession *s, const struct message *m)
}
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"),
};
@@ -7057,8 +4819,19 @@ int ast_pickup_call(struct ast_channel *chan)
{
struct ast_channel *target;/*!< Potential pickup target */
int res = -1;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickup_sound;
+ const char *fail_sound;
ast_debug(1, "pickup attempt by %s\n", ast_channel_name(chan));
+ ast_channel_lock(chan);
+ pickup_cfg = ast_get_chan_features_pickup_config(chan);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration. Unable to play pickup sounds\n");
+ }
+ pickup_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupsound : "");
+ fail_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupfailsound : "");
+ ast_channel_unlock(chan);
/* The found channel is already locked. */
target = ast_pickup_find_by_group(chan);
@@ -7068,8 +4841,8 @@ int ast_pickup_call(struct ast_channel *chan)
res = ast_do_pickup(chan, target);
ast_channel_unlock(target);
if (!res) {
- if (!ast_strlen_zero(pickupsound)) {
- pbx_builtin_setvar_helper(target, "BRIDGE_PLAY_SOUND", pickupsound);
+ if (!ast_strlen_zero(pickup_sound)) {
+ pbx_builtin_setvar_helper(target, "BRIDGE_PLAY_SOUND", pickup_sound);
}
} else {
ast_log(LOG_WARNING, "pickup %s failed by %s\n", ast_channel_name(target), ast_channel_name(chan));
@@ -7079,9 +4852,9 @@ int ast_pickup_call(struct ast_channel *chan)
if (res < 0) {
ast_debug(1, "No call pickup possible... for %s\n", ast_channel_name(chan));
- if (!ast_strlen_zero(pickupfailsound)) {
+ if (!ast_strlen_zero(fail_sound)) {
ast_answer(chan);
- ast_stream_and_wait(chan, pickupfailsound, "");
+ ast_stream_and_wait(chan, fail_sound, "");
}
}
@@ -7993,189 +5766,13 @@ exit_features_test:
}
#endif /* defined(TEST_FRAMEWORK) */
-/*!
- * \internal
- * \brief Get parkingtime for a channel
- */
-static unsigned int get_parkingtime(struct ast_channel *chan, struct ast_parkinglot *parkinglot)
-{
- const char *parkinglot_name;
- struct feature_datastore *feature_ds;
- unsigned int parkingtime;
-
- ast_channel_lock(chan);
-
- feature_ds = get_feature_ds(chan);
- if (feature_ds && feature_ds->parkingtime_is_set) {
- parkingtime = feature_ds->parkingtime;
- ast_channel_unlock(chan);
- return parkingtime;
- }
-
- parkinglot_name = ast_strdupa(S_OR(ast_channel_parkinglot(chan), ""));
-
- ast_channel_unlock(chan);
-
- if (!parkinglot) {
- if (!ast_strlen_zero(parkinglot_name)) {
- parkinglot = find_parkinglot(parkinglot_name);
- }
-
- if (!parkinglot) {
- parkinglot = parkinglot_addref(default_parkinglot);
- }
- } else {
- /* Just to balance the unref below */
- parkinglot_addref(parkinglot);
- }
-
- parkingtime = parkinglot->cfg.parkingtime;
-
- parkinglot_unref(parkinglot);
-
- return parkingtime;
-}
-
-static int feature_read(struct ast_channel *chan, const char *cmd, char *data,
- char *buf, size_t len)
-{
- int res = 0;
-
- if (!strcasecmp(data, "parkingtime")) {
- snprintf(buf, len, "%u", get_parkingtime(chan, NULL) / 1000);
- } else if (!strcasecmp(data, "inherit")) {
- struct ast_datastore *ds;
- unsigned int inherit;
-
- ast_channel_lock(chan);
- ds = get_feature_chan_ds(chan);
- inherit = ds ? ds->inheritance : 0;
- ast_channel_unlock(chan);
-
- snprintf(buf, len, "%s", inherit ? "yes" : "no");
- } else {
- ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
- res = -1;
- }
-
- return res;
-}
-
-static int feature_write(struct ast_channel *chan, const char *cmd, char *data,
- const char *value)
-{
- int res = 0;
- struct feature_datastore *feature_ds;
-
- ast_channel_lock(chan);
-
- if (!(feature_ds = get_feature_ds(chan))) {
- res = -1;
- goto return_cleanup;
- }
-
- if (!strcasecmp(data, "parkingtime")) {
- feature_ds->parkingtime_is_set = 1;
- if (sscanf(value, "%30u", &feature_ds->parkingtime) == 1) {
- feature_ds->parkingtime *= 1000; /* stored in ms */
- } else {
- ast_log(LOG_WARNING, "'%s' is not a valid parkingtime\n", value);
- feature_ds->parkingtime_is_set = 0;
- res = -1;
- }
- } else if (!strcasecmp(data, "inherit")) {
- struct ast_datastore *ds;
- if ((ds = get_feature_chan_ds(chan))) {
- ds->inheritance = ast_true(value) ? DATASTORE_INHERIT_FOREVER : 0;
- }
- } else {
- ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
- res = -1;
- }
-
-return_cleanup:
- ast_channel_unlock(chan);
-
- return res;
-}
-
-static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data,
- char *buf, size_t len)
-{
- int res;
-
- ast_rdlock_call_features();
-
- if ((res = builtin_feature_get_exten(chan, data, buf, len))) {
- ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
- }
-
- ast_unlock_call_features();
-
- return res;
-}
-
-static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data,
- const char *value)
-{
- struct feature_datastore *feature_ds;
- struct feature_exten *fe;
- struct ast_call_feature *feat;
-
- ast_rdlock_call_features();
- feat = ast_find_call_feature(data);
- ast_unlock_call_features();
- if (!feat) {
- ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
- return -1;
- }
-
- ast_channel_lock(chan);
-
- if (!(feature_ds = get_feature_ds(chan))) {
- ast_channel_unlock(chan);
- return -1;
- }
-
- if (!(fe = ao2_find(feature_ds->feature_map, data, OBJ_KEY))) {
- if (!(fe = ao2_alloc(sizeof(*fe), NULL))) {
- ast_channel_unlock(chan);
- return -1;
- }
- ast_copy_string(fe->sname, data, sizeof(fe->sname));
- ao2_link(feature_ds->feature_map, fe);
- }
-
- ast_channel_unlock(chan);
-
- ao2_lock(fe);
- ast_copy_string(fe->exten, value, sizeof(fe->exten));
- ao2_unlock(fe);
- ao2_ref(fe, -1);
- fe = NULL;
-
- return 0;
-}
-
-static struct ast_custom_function feature_function = {
- .name = "FEATURE",
- .read = feature_read,
- .write = feature_write
-};
-
-static struct ast_custom_function featuremap_function = {
- .name = "FEATUREMAP",
- .read = featuremap_read,
- .write = featuremap_write
-};
-
/*! \internal \brief Clean up resources on Asterisk shutdown */
static void features_shutdown(void)
{
+ ast_features_config_shutdown();
+
ast_cli_unregister_multiple(cli_features, ARRAY_LEN(cli_features));
ast_devstate_prov_del("Park");
- ast_custom_function_unregister(&featuremap_function);
- ast_custom_function_unregister(&feature_function);
ast_manager_unregister("Bridge");
ast_manager_unregister("Park");
@@ -8197,28 +5794,30 @@ int ast_features_init(void)
return -1;
}
- res = load_config(0);
+ res = ast_features_config_init();
if (res) {
return res;
}
ast_cli_register_multiple(cli_features, ARRAY_LEN(cli_features));
if (ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL)) {
+ ast_features_config_shutdown();
+ ast_cli_unregister_multiple(cli_features, ARRAY_LEN(cli_features));
return -1;
}
- ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL);
- if (!res) {
- ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
- ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
- }
- res |= __ast_custom_function_register(&feature_function, NULL);
- res |= __ast_custom_function_register(&featuremap_function, NULL);
+ res |= ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL);
+ res |= ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
+ res |= ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
res |= ast_devstate_prov_add("Park", metermaidstate);
#if defined(TEST_FRAMEWORK)
res |= AST_TEST_REGISTER(features_test);
#endif /* defined(TEST_FRAMEWORK) */
- ast_register_atexit(features_shutdown);
+ if (res) {
+ features_shutdown();
+ } else {
+ ast_register_atexit(features_shutdown);
+ }
return res;
}
diff --git a/main/features_config.c b/main/features_config.c
new file mode 100644
index 000000000..f8bdb1c3c
--- /dev/null
+++ b/main/features_config.c
@@ -0,0 +1,1534 @@
+/*
+* Asterisk -- An open source telephony toolkit.
+*
+* Copyright (C) 2013, Digium, Inc.
+*
+* Mark Michelson <mmichelson@digium.com>
+*
+* See http://www.asterisk.org for more information about
+* the Asterisk project. Please do not directly contact
+* any of the maintainers of this project for assistance;
+* the project provides a web site, mailing lists and IRC
+* channels for your use.
+*
+* This program is free software, distributed under the terms of
+* the GNU General Public License Version 2. See the LICENSE file
+* at the top of the source tree.
+*/
+
+#include "asterisk.h"
+
+#include "asterisk/features_config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/datastore.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/cli.h"
+
+/* BUGBUG XML Documentation is still needed for configuration options */
+/*** DOCUMENTATION
+ <function name="FEATURE" language="en_US">
+ <synopsis>
+ Get or set a feature option on a channel.
+ </synopsis>
+ <syntax>
+ <parameter name="option_name" required="true">
+ <para>The allowed values are:</para>
+ <enumlist>
+ <enum name="inherit"><para>Inherit feature settings made in FEATURE or FEATUREMAP to child channels.</para></enum>
+ <enum name="featuredigittimeout"><para>Milliseconds allowed between digits when entering a feature code.</para></enum>
+ <enum name="transferdigittimeout"><para>Milliseconds allowed between digits when dialing a transfer destination</para></enum>
+ <enum name="atxfernoanswertimeout"><para>Milliseconds to wait for transfer destination to answer</para></enum>
+ <enum name="atxferdropcall"><para>Hang up the call entirely if the attended transfer fails</para></enum>
+ <enum name="atxferloopdelay"><para>Milliseconds to wait between attempts to re-dial transfer destination</para></enum>
+ <enum name="atxfercallbackretries"><para>Number of times to re-attempt dialing a transfer destination</para></enum>
+ <enum name="xfersound"><para>Sound to play to a transferee when a transfer completes</para></enum>
+ <enum name="xferfailsound"><para>Sound to play to a transferee when a transfer fails</para></enum>
+ <enum name="atxferabort"><para>Digits to dial to abort an attended transfer attempt</para></enum>
+ <enum name="atxfercomplete"><para>Digits to dial to complete an attended transfer</para></enum>
+ <enum name="atxferthreeway"><para>Digits to dial to change an attended transfer into a three-way call</para></enum>
+ <enum name="pickupexten"><para>Digits used for picking up ringing calls</para></enum>
+ <enum name="pickupsound"><para>Sound to play to picker when a call is picked up</para></enum>
+ <enum name="pickupfailsound"><para>Sound to play to picker when a call cannot be picked up</para></enum>
+ <enum name="courtesytone"><para>Sound to play when automon or automixmon is activated</para></enum>
+ </enumlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>When this function is used as a read, it will get the current
+ value of the specified feature option for this channel. It will be
+ the value of this option configured in features.conf if a channel specific
+ value has not been set. This function can also be used to set a channel
+ specific value for the supported feature options.</para>
+ </description>
+ <see-also>
+ <ref type="function">FEATUREMAP</ref>
+ </see-also>
+ </function>
+ <function name="FEATUREMAP" language="en_US">
+ <synopsis>
+ Get or set a feature map to a given value on a specific channel.
+ </synopsis>
+ <syntax>
+ <parameter name="feature_name" required="true">
+ <para>The allowed values are:</para>
+ <enumlist>
+ <enum name="atxfer"><para>Attended Transfer</para></enum>
+ <enum name="blindxfer"><para>Blind Transfer</para></enum>
+ <enum name="automon"><para>Auto Monitor</para></enum>
+ <enum name="disconnect"><para>Call Disconnect</para></enum>
+ <enum name="parkcall"><para>Park Call</para></enum>
+ <enum name="automixmon"><para>Auto MixMonitor</para></enum>
+ </enumlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>When this function is used as a read, it will get the current
+ digit sequence mapped to the specified feature for this channel. This
+ value will be the one configured in features.conf if a channel specific
+ value has not been set. This function can also be used to set a channel
+ specific value for a feature mapping.</para>
+ </description>
+ <see-also>
+ <ref type="function">FEATURE</ref>
+ </see-also>
+ </function>
+ ***/
+/*! Default general options */
+#define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000
+
+/*! Default xfer options */
+#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
+#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000
+#define DEFAULT_ATXFER_DROP_CALL 0
+#define DEFAULT_ATXFER_LOOP_DELAY 10000
+#define DEFAULT_ATXFER_CALLBACK_RETRIES 2
+#define DEFAULT_XFERSOUND "beep"
+#define DEFAULT_XFERFAILSOUND "beeperr"
+#define DEFAULT_ATXFER_ABORT "*1"
+#define DEFAULT_ATXFER_COMPLETE "*2"
+#define DEFAULT_ATXFER_THREEWAY "*3"
+
+/*! Default pickup options */
+#define DEFAULT_PICKUPEXTEN "*8"
+#define DEFAULT_PICKUPSOUND ""
+#define DEFAULT_PICKUPFAILSOUND ""
+
+/*! Default featuremap options */
+#define DEFAULT_FEATUREMAP_BLINDXFER "#"
+#define DEFAULT_FEATUREMAP_DISCONNECT "*"
+#define DEFAULT_FEATUREMAP_AUTOMON ""
+#define DEFAULT_FEATUREMAP_ATXFER ""
+#define DEFAULT_FEATUREMAP_PARKCALL ""
+#define DEFAULT_FEATUREMAP_AUTOMIXMON ""
+
+/*!
+ * \brief Configuration from the "general" section of features.conf
+ */
+struct features_global_config {
+ struct ast_features_general_config *general;
+ struct ast_features_xfer_config *xfer;
+ struct ast_features_pickup_config *pickup;
+};
+
+static void ast_applicationmap_item_destructor(void *obj)
+{
+ struct ast_applicationmap_item *item = obj;
+
+ ast_string_field_free_memory(item);
+}
+
+static int applicationmap_sort(const void *obj, const void *arg, int flags)
+{
+ const struct ast_applicationmap_item *item1 = obj;
+ const struct ast_applicationmap_item *item2;
+ const char *key2;
+
+ switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_KEY:
+ key2 = arg;
+ return strcasecmp(item1->name, key2);
+ case OBJ_PARTIAL_KEY:
+ key2 = arg;
+ return strncasecmp(item1->name, key2, strlen(key2));
+ default:
+ case OBJ_POINTER:
+ item2 = arg;
+ return strcasecmp(item1->name, item2->name);
+ }
+}
+
+/*!
+ * \brief Entry in the container of featuregroups
+ */
+struct featuregroup_item {
+ AST_DECLARE_STRING_FIELDS(
+ /*! The name of the applicationmap item that we are referring to */
+ AST_STRING_FIELD(appmap_item_name);
+ /*! Custom DTMF override to use instead of the default for the applicationmap item */
+ AST_STRING_FIELD(dtmf_override);
+ );
+ /*! The applicationmap item that is being referred to */
+ struct ast_applicationmap_item *appmap_item;
+};
+
+static void featuregroup_item_destructor(void *obj)
+{
+ struct featuregroup_item *item = obj;
+
+ ast_string_field_free_memory(item);
+ ao2_cleanup(item->appmap_item);
+}
+
+static int group_item_sort(const void *obj, const void *arg, int flags)
+{
+ const struct featuregroup_item *item1 = obj;
+ const struct featuregroup_item *item2;
+ const char *key2;
+
+ switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_KEY:
+ key2 = arg;
+ return strcasecmp(item1->appmap_item_name, key2);
+ case OBJ_PARTIAL_KEY:
+ key2 = arg;
+ return strncasecmp(item1->appmap_item_name, key2, strlen(key2));
+ case OBJ_POINTER:
+ item2 = arg;
+ return strcasecmp(item1->appmap_item_name, item2->appmap_item_name);
+ default:
+ return CMP_STOP;
+ }
+}
+
+/*!
+ * \brief Featuregroup representation
+ */
+struct featuregroup {
+ /*! The name of the featuregroup */
+ const char *name;
+ /*! A container of featuregroup_items */
+ struct ao2_container *items;
+};
+
+static int featuregroup_hash(const void *obj, int flags)
+{
+ const struct featuregroup *group;
+ const char *key;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_KEY:
+ key = obj;
+ return ast_str_case_hash(key);
+ case OBJ_PARTIAL_KEY:
+ ast_assert(0);
+ return 0;
+ case OBJ_POINTER:
+ default:
+ group = obj;
+ return ast_str_case_hash(group->name);
+ }
+}
+
+static int featuregroup_cmp(void *obj, void *arg, int flags)
+{
+ struct featuregroup *group1 = obj;
+ struct featuregroup *group2;
+ const char *key2;
+
+ switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_KEY:
+ key2 = arg;
+ return strcasecmp(group1->name, key2) ? 0 : CMP_MATCH;
+ case OBJ_PARTIAL_KEY:
+ key2 = arg;
+ return strncasecmp(group1->name, key2, strlen(key2)) ? 0 : CMP_MATCH;
+ case OBJ_POINTER:
+ group2 = arg;
+ return strcasecmp(group1->name, group2->name) ? 0 : CMP_MATCH;
+ default:
+ return CMP_STOP;
+ }
+}
+
+static void *featuregroup_find(struct ao2_container *group_container, const char *category)
+{
+ return ao2_find(group_container, category, OBJ_KEY);
+}
+
+static void featuregroup_destructor(void *obj)
+{
+ struct featuregroup *group = obj;
+
+ ast_free((char *) group->name);
+ ao2_cleanup(group->items);
+}
+
+static void *featuregroup_alloc(const char *cat)
+{
+ struct featuregroup *group;
+
+ group = ao2_alloc(sizeof(*group), featuregroup_destructor);
+ if (!group) {
+ return NULL;
+ }
+
+ group->name = ast_strdup(cat);
+ if (!group->name) {
+ ao2_cleanup(group);
+ return NULL;
+ }
+
+ group->items = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, group_item_sort, NULL);
+ if (!group->items) {
+ ao2_cleanup(group);
+ return NULL;
+ }
+
+ return group;
+}
+
+struct features_config {
+ struct features_global_config *global;
+ struct ast_featuremap_config *featuremap;
+ struct ao2_container *applicationmap;
+ struct ao2_container *featuregroups;
+};
+
+static struct aco_type global_option = {
+ .type = ACO_GLOBAL,
+ .name = "globals",
+ .category_match = ACO_WHITELIST,
+ .category = "^general$",
+ .item_offset = offsetof(struct features_config, global),
+};
+
+static struct aco_type featuremap_option = {
+ .type = ACO_GLOBAL,
+ .name = "featuremap",
+ .category_match = ACO_WHITELIST,
+ .category = "^featuremap$",
+ .item_offset = offsetof(struct features_config, featuremap),
+};
+
+static struct aco_type applicationmap_option = {
+ .type = ACO_GLOBAL,
+ .name = "applicationmap",
+ .category_match = ACO_WHITELIST,
+ .category = "^applicationmap$",
+ .item_offset = offsetof(struct features_config, applicationmap),
+};
+
+static struct aco_type featuregroup_option = {
+ .type = ACO_ITEM,
+ .name = "featuregroup",
+ .category_match = ACO_BLACKLIST,
+ .category = "^(general|featuremap|applicationmap|parkinglot_.*)$",
+ .item_offset = offsetof(struct features_config, featuregroups),
+ .item_alloc = featuregroup_alloc,
+ .item_find = featuregroup_find,
+};
+
+static struct aco_type *global_options[] = ACO_TYPES(&global_option);
+static struct aco_type *featuremap_options[] = ACO_TYPES(&featuremap_option);
+static struct aco_type *applicationmap_options[] = ACO_TYPES(&applicationmap_option);
+static struct aco_type *featuregroup_options[] = ACO_TYPES(&featuregroup_option);
+
+static struct aco_file features_conf = {
+ .filename = "features.conf",
+ .types = ACO_TYPES(&global_option, &featuremap_option, &applicationmap_option, &featuregroup_option),
+};
+
+AO2_GLOBAL_OBJ_STATIC(globals);
+
+static void features_config_destructor(void *obj)
+{
+ struct features_config *cfg = obj;
+
+ ao2_cleanup(cfg->global);
+ ao2_cleanup(cfg->featuremap);
+ ao2_cleanup(cfg->applicationmap);
+ ao2_cleanup(cfg->featuregroups);
+}
+
+static void featuremap_config_destructor(void *obj)
+{
+ struct ast_featuremap_config *cfg = obj;
+
+ ast_string_field_free_memory(cfg);
+}
+
+static void global_config_destructor(void *obj)
+{
+ struct features_global_config *cfg = obj;
+
+ ao2_cleanup(cfg->general);
+ ao2_cleanup(cfg->xfer);
+ ao2_cleanup(cfg->pickup);
+}
+
+static void general_destructor(void *obj)
+{
+ struct ast_features_general_config *cfg = obj;
+
+ ast_string_field_free_memory(cfg);
+}
+
+static void xfer_destructor(void *obj)
+{
+ struct ast_features_xfer_config *cfg = obj;
+
+ ast_string_field_free_memory(cfg);
+}
+
+static void pickup_destructor(void *obj)
+{
+ struct ast_features_pickup_config *cfg = obj;
+
+ ast_string_field_free_memory(cfg);
+}
+
+static struct features_global_config *global_config_alloc(void)
+{
+ RAII_VAR(struct features_global_config *, cfg, NULL, ao2_cleanup);
+
+ cfg = ao2_alloc(sizeof(*cfg), global_config_destructor);
+ if (!cfg) {
+ return NULL;
+ }
+
+ cfg->general = ao2_alloc(sizeof(*cfg->general), general_destructor);
+ if (!cfg->general || ast_string_field_init(cfg->general, 32)) {
+ return NULL;
+ }
+
+ cfg->xfer = ao2_alloc(sizeof(*cfg->xfer), xfer_destructor);
+ if (!cfg->xfer || ast_string_field_init(cfg->xfer, 32)) {
+ return NULL;
+ }
+
+ cfg->pickup = ao2_alloc(sizeof(*cfg->pickup), pickup_destructor);
+ if (!cfg->pickup || ast_string_field_init(cfg->pickup, 32)) {
+ return NULL;
+ }
+
+ ao2_ref(cfg, +1);
+ return cfg;
+}
+
+static struct ao2_container *applicationmap_alloc(void)
+{
+ return ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, applicationmap_sort, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Allocate the major configuration structure
+ *
+ * The parameter is used to determine if the applicationmap and featuregroup
+ * structures should be allocated. We only want to allocate these structures for
+ * the global features_config structure. For the datastores on channels, we don't
+ * need to allocate these structures because they are not used.
+ *
+ * \param allocate_applicationmap See previous explanation
+ * \retval NULL Failed to alloate configuration
+ * \retval non-NULL Allocated configuration
+ */
+static struct features_config *__features_config_alloc(int allocate_applicationmap)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ cfg = ao2_alloc(sizeof(*cfg), features_config_destructor);
+ if (!cfg) {
+ return NULL;
+ }
+
+ cfg->global = global_config_alloc();;
+ if (!cfg->global) {
+ return NULL;
+ }
+
+ cfg->featuremap = ao2_alloc(sizeof(*cfg->featuremap), featuremap_config_destructor);
+ if (!cfg->featuremap || ast_string_field_init(cfg->featuremap, 32)) {
+ return NULL;
+ }
+
+ if (allocate_applicationmap) {
+ cfg->applicationmap = applicationmap_alloc();
+ if (!cfg->applicationmap) {
+ return NULL;
+ }
+
+ cfg->featuregroups = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 11, featuregroup_hash,
+ featuregroup_cmp);
+ if (!cfg->featuregroups) {
+ return NULL;
+ }
+ }
+
+ ao2_ref(cfg, +1);
+ return cfg;
+
+}
+
+static void *features_config_alloc(void)
+{
+ return __features_config_alloc(1);
+}
+
+static void general_copy(struct ast_features_general_config *dest, const struct ast_features_general_config *src)
+{
+ ast_string_fields_copy(dest, src);
+ dest->featuredigittimeout = src->featuredigittimeout;
+}
+
+static void xfer_copy(struct ast_features_xfer_config *dest, const struct ast_features_xfer_config *src)
+{
+ ast_string_fields_copy(dest, src);
+ dest->transferdigittimeout = src->transferdigittimeout;
+ dest->atxfernoanswertimeout = src->atxfernoanswertimeout;
+ dest->atxferloopdelay = src->atxferloopdelay;
+ dest->atxfercallbackretries = src->atxfercallbackretries;
+ dest->atxferdropcall = src->atxferdropcall;
+}
+
+static void pickup_copy(struct ast_features_pickup_config *dest, const struct ast_features_pickup_config *src)
+{
+ ast_string_fields_copy(dest, src);
+}
+
+static void global_copy(struct features_global_config *dest, const struct features_global_config *src)
+{
+ general_copy(dest->general, src->general);
+ xfer_copy(dest->xfer, src->xfer);
+ pickup_copy(dest->pickup, src->pickup);
+}
+
+static void featuremap_copy(struct ast_featuremap_config *dest, const struct ast_featuremap_config *src)
+{
+ ast_string_fields_copy(dest, src);
+}
+
+static void features_copy(struct features_config *dest, const struct features_config *src)
+{
+ global_copy(dest->global, src->global);
+ featuremap_copy(dest->featuremap, src->featuremap);
+
+ /* applicationmap and featuregroups are purposely not copied. A channel's applicationmap
+ * is produced on the fly when ast_get_chan_applicationmap() is called
+ */
+}
+
+static struct features_config *features_config_dup(const struct features_config *orig)
+{
+ struct features_config *dup;
+
+ dup = __features_config_alloc(0);
+ if (!dup) {
+ return NULL;
+ }
+
+ features_copy(dup, orig);
+
+ return dup;
+}
+
+static int general_set(struct ast_features_general_config *general, const char *name,
+ const char *value)
+{
+ int res = 0;
+
+ if (!strcasecmp(name, "featuredigittimeout")) {
+ res = ast_parse_arg(value, PARSE_INT32, &general->featuredigittimeout);
+ } else if (!strcasecmp(name, "courtesytone")) {
+ ast_string_field_set(general, courtesytone, value);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int general_get(struct ast_features_general_config *general, const char *field,
+ char *buf, size_t len)
+{
+ int res = 0;
+
+ if (!strcasecmp(field, "featuredigittimeout")) {
+ snprintf(buf, len, "%u", general->featuredigittimeout);
+ } else if (!strcasecmp(field, "courtesytone")) {
+ ast_copy_string(buf, general->courtesytone, len);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int xfer_set(struct ast_features_xfer_config *xfer, const char *name,
+ const char *value)
+{
+ int res = 0;
+
+ if (!strcasecmp(name, "transferdigittimeout")) {
+ res = ast_parse_arg(value, PARSE_INT32, &xfer->transferdigittimeout);
+ } else if (!strcasecmp(name, "atxfernoanswertimeout")) {
+ res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfernoanswertimeout);
+ } else if (!strcasecmp(name, "atxferloopdelay")) {
+ res = ast_parse_arg(value, PARSE_INT32, &xfer->atxferloopdelay);
+ } else if (!strcasecmp(name, "atxfercallbackretries")) {
+ res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfercallbackretries);
+ } else if (!strcasecmp(name, "atxferdropcall")) {
+ xfer->atxferdropcall = ast_true(value);
+ } else if (!strcasecmp(name, "xfersound")) {
+ ast_string_field_set(xfer, xfersound, value);
+ } else if (!strcasecmp(name, "xferfailsound")) {
+ ast_string_field_set(xfer, xferfailsound, value);
+ } else if (!strcasecmp(name, "atxferabort")) {
+ ast_string_field_set(xfer, atxferabort, value);
+ } else if (!strcasecmp(name, "atxfercomplete")) {
+ ast_string_field_set(xfer, atxfercomplete, value);
+ } else if (!strcasecmp(name, "atxferthreeway")) {
+ ast_string_field_set(xfer, atxferthreeway, value);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int xfer_get(struct ast_features_xfer_config *xfer, const char *field,
+ char *buf, size_t len)
+{
+ int res = 0;
+
+ if (!strcasecmp(field, "transferdigittimeout")) {
+ snprintf(buf, len, "%u", xfer->transferdigittimeout);
+ } else if (!strcasecmp(field, "atxfernoanswertimeout")) {
+ snprintf(buf, len, "%u", xfer->atxfernoanswertimeout);
+ } else if (!strcasecmp(field, "atxferloopdelay")) {
+ snprintf(buf, len, "%u", xfer->atxferloopdelay);
+ } else if (!strcasecmp(field, "atxfercallbackretries")) {
+ snprintf(buf, len, "%u", xfer->atxfercallbackretries);
+ } else if (!strcasecmp(field, "atxferdropcall")) {
+ snprintf(buf, len, "%u", xfer->atxferdropcall);
+ } else if (!strcasecmp(field, "xfersound")) {
+ ast_copy_string(buf, xfer->xfersound, len);
+ } else if (!strcasecmp(field, "xferfailsound")) {
+ ast_copy_string(buf, xfer->xferfailsound, len);
+ } else if (!strcasecmp(field, "atxferabort")) {
+ ast_copy_string(buf, xfer->atxferabort, len);
+ } else if (!strcasecmp(field, "atxfercomplete")) {
+ ast_copy_string(buf, xfer->atxfercomplete, len);
+ } else if (!strcasecmp(field, "atxferthreeway")) {
+ ast_copy_string(buf, xfer->atxferthreeway, len);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int pickup_set(struct ast_features_pickup_config *pickup, const char *name,
+ const char *value)
+{
+ int res = 0;
+
+ if (!strcasecmp(name, "pickupsound")) {
+ ast_string_field_set(pickup, pickupsound, value);
+ } else if (!strcasecmp(name, "pickupfailsound")) {
+ ast_string_field_set(pickup, pickupfailsound, value);
+ } else if (!strcasecmp(name, "pickupexten")) {
+ ast_string_field_set(pickup, pickupexten, value);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int pickup_get(struct ast_features_pickup_config *pickup, const char *field,
+ char *buf, size_t len)
+{
+ int res = 0;
+
+ if (!strcasecmp(field, "pickupsound")) {
+ ast_copy_string(buf, pickup->pickupsound, len);
+ } else if (!strcasecmp(field, "pickupfailsound")) {
+ ast_copy_string(buf, pickup->pickupfailsound, len);
+ } else if (!strcasecmp(field, "pickupexten")) {
+ ast_copy_string(buf, pickup->pickupexten, len);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int featuremap_set(struct ast_featuremap_config *featuremap, const char *name,
+ const char *value)
+{
+ int res = 0;
+
+ if (!strcasecmp(name, "blindxfer")) {
+ ast_string_field_set(featuremap, blindxfer, value);
+ } else if (!strcasecmp(name, "disconnect")) {
+ ast_string_field_set(featuremap, disconnect, value);
+ } else if (!strcasecmp(name, "automon")) {
+ ast_string_field_set(featuremap, automon, value);
+ } else if (!strcasecmp(name, "atxfer")) {
+ ast_string_field_set(featuremap, atxfer, value);
+ } else if (!strcasecmp(name, "automixmon")) {
+ ast_string_field_set(featuremap, automixmon, value);
+ } else if (!strcasecmp(name, "parkcall")) {
+ ast_string_field_set(featuremap, parkcall, value);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int featuremap_get(struct ast_featuremap_config *featuremap, const char *field,
+ char *buf, size_t len)
+{
+ int res = 0;
+
+ if (!strcasecmp(field, "blindxfer")) {
+ ast_copy_string(buf, featuremap->blindxfer, len);
+ } else if (!strcasecmp(field, "disconnect")) {
+ ast_copy_string(buf, featuremap->disconnect, len);
+ } else if (!strcasecmp(field, "automon")) {
+ ast_copy_string(buf, featuremap->automon, len);
+ } else if (!strcasecmp(field, "atxfer")) {
+ ast_copy_string(buf, featuremap->atxfer, len);
+ } else if (!strcasecmp(field, "automixmon")) {
+ ast_copy_string(buf, featuremap->automixmon, len);
+ } else if (!strcasecmp(field, "parkcall")) {
+ ast_copy_string(buf, featuremap->parkcall, len);
+ } else {
+ /* Unrecognized option */
+ res = -1;
+ }
+
+ return res;
+}
+
+static void feature_ds_destroy(void *data)
+{
+ struct features_config *cfg = data;
+ ao2_cleanup(cfg);
+}
+
+static void *feature_ds_duplicate(void *data)
+{
+ struct features_config *old_cfg = data;
+
+ return features_config_dup(old_cfg);
+}
+
+static const struct ast_datastore_info feature_ds_info = {
+ .type = "FEATURE",
+ .destroy = feature_ds_destroy,
+ .duplicate = feature_ds_duplicate,
+};
+
+/*!
+ * \internal
+ * \brief Find or create feature datastore on a channel
+ *
+ * \pre chan is locked
+ *
+ * \return the data on the FEATURE datastore, or NULL on error
+ */
+static struct features_config *get_feature_ds(struct ast_channel *chan)
+{
+ RAII_VAR(struct features_config *, orig, NULL, ao2_cleanup);
+ struct features_config *cfg;
+ struct ast_datastore *ds;
+
+ if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
+ cfg = ds->data;
+ ao2_ref(cfg, +1);
+ return cfg;
+ }
+
+ orig = ao2_global_obj_ref(globals);
+ if (!orig) {
+ return NULL;
+ }
+
+ cfg = features_config_dup(orig);
+ if (!cfg) {
+ return NULL;
+ }
+
+ if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) {
+ ao2_cleanup(cfg);
+ return NULL;
+ }
+
+ /* Give the datastore a reference to the config */
+ ao2_ref(cfg, +1);
+ ds->data = cfg;
+
+ ast_channel_datastore_add(chan, ds);
+
+ return cfg;
+}
+
+static struct ast_datastore *get_feature_chan_ds(struct ast_channel *chan)
+{
+ struct ast_datastore *ds;
+
+ if (!(ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
+ /* Hasn't been created yet. Trigger creation. */
+ RAII_VAR(struct features_config *, cfg, get_feature_ds(chan), ao2_cleanup);
+ ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL);
+ }
+
+ return ds;
+}
+
+struct ast_features_general_config *ast_get_chan_features_general_config(struct ast_channel *chan)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ if (chan) {
+ cfg = get_feature_ds(chan);
+ } else {
+ cfg = ao2_global_obj_ref(globals);
+ }
+
+ if (!cfg) {
+ return NULL;
+ }
+
+ ast_assert(cfg->global && cfg->global->general);
+
+ ao2_ref(cfg->global->general, +1);
+ return cfg->global->general;
+}
+
+struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ if (chan) {
+ cfg = get_feature_ds(chan);
+ } else {
+ cfg = ao2_global_obj_ref(globals);
+ }
+
+ if (!cfg) {
+ return NULL;
+ }
+
+ ast_assert(cfg->global && cfg->global->xfer);
+
+ ao2_ref(cfg->global->xfer, +1);
+ return cfg->global->xfer;
+}
+
+struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ if (chan) {
+ cfg = get_feature_ds(chan);
+ } else {
+ cfg = ao2_global_obj_ref(globals);
+ }
+
+ if (!cfg) {
+ return NULL;
+ }
+
+ ast_assert(cfg->global && cfg->global->pickup);
+
+ ao2_ref(cfg->global->pickup, +1);
+ return cfg->global->pickup;
+}
+
+struct ast_featuremap_config *ast_get_chan_featuremap_config(struct ast_channel *chan)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ if (chan) {
+ cfg = get_feature_ds(chan);
+ } else {
+ cfg = ao2_global_obj_ref(globals);
+ }
+
+ if (!cfg) {
+ return NULL;
+ }
+
+ ast_assert(cfg->featuremap != NULL);
+
+ ao2_ref(cfg->featuremap, +1);
+ return cfg->featuremap;
+}
+
+int ast_get_builtin_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ if (chan) {
+ cfg = get_feature_ds(chan);
+ } else {
+ cfg = ao2_global_obj_ref(globals);
+ }
+
+ if (!cfg) {
+ return -1;
+ }
+
+ return featuremap_get(cfg->featuremap, feature, buf, len);
+}
+
+int ast_get_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len)
+{
+ RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup);
+ if (!ast_get_builtin_feature(chan, feature, buf, len)) {
+ return 0;
+ }
+
+ /* Dang, must be in the application map */
+ applicationmap = ast_get_chan_applicationmap(chan);
+
+ if (!applicationmap) {
+ return -1;
+ }
+
+ item = ao2_find(applicationmap, feature, OBJ_KEY);
+ if (!item) {
+ return -1;
+ }
+
+ ast_copy_string(buf, item->dtmf, len);
+ return 0;
+}
+
+static struct ast_applicationmap_item *applicationmap_item_alloc(const char *name,
+ const char *app, const char *app_data, const char *moh_class, const char *dtmf,
+ unsigned int activate_on_self)
+{
+ struct ast_applicationmap_item *item;
+
+ item = ao2_alloc(sizeof(*item), ast_applicationmap_item_destructor);
+
+ if (!item || ast_string_field_init(item, 64)) {
+ return NULL;
+ }
+
+ ast_string_field_set(item, name, name);
+ ast_string_field_set(item, app, app);
+ ast_string_field_set(item, app_data, app_data);
+ ast_string_field_set(item, moh_class, moh_class);
+ ast_copy_string(item->dtmf, dtmf, sizeof(item->dtmf));
+ item->activate_on_self = activate_on_self;
+
+ return item;
+}
+
+static int add_item(void *obj, void *arg, int flags)
+{
+ struct featuregroup_item *fg_item = obj;
+ struct ao2_container *applicationmap = arg;
+ RAII_VAR(struct ast_applicationmap_item *, appmap_item, NULL, ao2_cleanup);
+
+ /* If there's no DTMF override, then we can just link
+ * the applicationmap item directly. Otherwise, we need
+ * to create a copy with the DTMF override in place and
+ * link that instead
+ */
+ if (ast_strlen_zero(fg_item->dtmf_override)) {
+ ao2_ref(fg_item->appmap_item, +1);
+ appmap_item = fg_item->appmap_item;
+ } else {
+ appmap_item = applicationmap_item_alloc(fg_item->appmap_item_name,
+ fg_item->appmap_item->app, fg_item->appmap_item->app_data,
+ fg_item->appmap_item->moh_class, fg_item->dtmf_override,
+ fg_item->appmap_item->activate_on_self);
+ }
+
+ if (!appmap_item) {
+ return 0;
+ }
+
+ if (!ao2_link(applicationmap, appmap_item)) {
+ ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate\n",
+ fg_item->appmap_item_name);
+ }
+ return 0;
+}
+
+struct ao2_container *ast_get_chan_applicationmap(struct ast_channel *chan)
+{
+ RAII_VAR(struct features_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+ struct ao2_container *applicationmap;
+ char *group_names;
+ char *name;
+
+ if (!cfg) {
+ return NULL;
+ }
+
+ if (!chan) {
+ if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) {
+ return NULL;
+ }
+ ao2_ref(cfg->applicationmap, +1);
+ return cfg->applicationmap;
+ }
+
+ group_names = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"), ""));
+ if (ast_strlen_zero(group_names)) {
+ return NULL;
+ }
+
+ applicationmap = applicationmap_alloc();
+ if (!applicationmap) {
+ return NULL;
+ }
+
+ while ((name = strsep(&group_names, "#"))) {
+ RAII_VAR(struct featuregroup *, group, ao2_find(cfg->featuregroups, name, OBJ_KEY), ao2_cleanup);
+ if (!group) {
+ RAII_VAR(struct ast_applicationmap_item *, item, ao2_find(cfg->applicationmap, name, OBJ_KEY), ao2_cleanup);
+ if (item && !ao2_link(applicationmap, item)) {
+ ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate.\n", item->name);
+ }
+ } else {
+ ao2_callback(group->items, 0, add_item, applicationmap);
+ }
+ }
+
+ if (ao2_container_count(applicationmap) == 0) {
+ ao2_cleanup(applicationmap);
+ return NULL;
+ }
+
+ return applicationmap;
+}
+
+static int applicationmap_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup);
+ struct ao2_container *applicationmap = obj;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(dtmf);
+ AST_APP_ARG(activate_on);
+ AST_APP_ARG(app);
+ AST_APP_ARG(app_data);
+ AST_APP_ARG(moh_class);
+ );
+ char *parse = ast_strdupa(var->value);
+ char *slash;
+ char *paren;
+ unsigned int activate_on_self;
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.dtmf) ||
+ ast_strlen_zero(args.activate_on) ||
+ ast_strlen_zero(args.app)) {
+ ast_log(LOG_WARNING, "Invalid applicationmap syntax for '%s'. Missing required argument\n", var->name);
+ return -1;
+ }
+
+ /* features.conf used to have an "activated_by" portion
+ * in addition to activate_on. Get rid of whatever may be
+ * there
+ */
+ slash = strchr(args.activate_on, '/');
+ if (slash) {
+ *slash = '\0';
+ }
+
+ /* Two syntaxes allowed for applicationmap:
+ * Old: foo = *1,self,NoOp,Boo!,default
+ * New: foo = *1,self,NoOp(Boo!),default
+ *
+ * We need to handle both
+ */
+ paren = strchr(args.app, '(');
+ if (paren) {
+ /* New syntax */
+ char *close_paren;
+
+ args.moh_class = args.app_data;
+ *paren++ = '\0';
+ close_paren = strrchr(paren, ')');
+ if (close_paren) {
+ *close_paren = '\0';
+ }
+ args.app_data = paren;
+
+ /* Re-check that the application is not empty */
+ if (ast_strlen_zero(args.app)) {
+ ast_log(LOG_WARNING, "Applicationmap item '%s' does not contain an application name.\n", var->name);
+ return -1;
+ }
+ } else if (strchr(args.app_data, '"')) {
+ args.app_data = ast_strip_quoted(args.app_data, "\"", "\"");
+ }
+
+ /* Allow caller and callee to be specified for backwards compatibility */
+ if (!strcasecmp(args.activate_on, "self") || !strcasecmp(args.activate_on, "caller")) {
+ activate_on_self = 1;
+ } else if (!strcasecmp(args.activate_on, "peer") || !strcasecmp(args.activate_on, "callee")) {
+ activate_on_self = 0;
+ } else {
+ ast_log(LOG_WARNING, "Invalid 'activate_on' value %s for applicationmap item %s\n",
+ args.activate_on, var->name);
+ return -1;
+ }
+
+ ast_debug(1, "Allocating applicationmap item: dtmf = %s, app = %s, app_data = %s, moh_class = %s\n",
+ args.dtmf, args.app, args.app_data, args.moh_class);
+
+ item = applicationmap_item_alloc(var->name, args.app, args.app_data,
+ args.moh_class, args.dtmf, activate_on_self);
+
+ if (!item) {
+ return -1;
+ }
+
+ if (!ao2_link(applicationmap, item)) {
+ ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate\n", item->name);
+ }
+
+ return 0;
+}
+
+static int featuregroup_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ RAII_VAR(struct featuregroup_item *, item, NULL, ao2_cleanup);
+ struct featuregroup *group = obj;
+
+ item = ao2_alloc(sizeof(*item), featuregroup_item_destructor);
+ if (!item || ast_string_field_init(item, 32)) {
+ return -1;
+ }
+
+ ast_string_field_set(item, appmap_item_name, var->name);
+ ast_string_field_set(item, dtmf_override, var->value);
+
+ if (!ao2_link(group->items, item)) {
+ ast_log(LOG_WARNING, "Unable to add featuregroup item %s. Possible duplicate\n", item->appmap_item_name);
+ }
+
+ /* We wait to look up the application map item in the preapply callback */
+
+ return 0;
+}
+
+static int general_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct features_global_config *global = obj;
+ struct ast_features_general_config *general = global->general;
+
+ return general_set(general, var->name, var->value);
+}
+
+static int xfer_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct features_global_config *global = obj;
+ struct ast_features_xfer_config *xfer = global->xfer;
+
+ return xfer_set(xfer, var->name, var->value);
+}
+
+static int pickup_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct features_global_config *global = obj;
+ struct ast_features_pickup_config *pickup = global->pickup;
+
+ return pickup_set(pickup, var->name, var->value);
+}
+
+static int featuremap_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ast_featuremap_config *featuremap = obj;
+
+ return featuremap_set(featuremap, var->name, var->value);
+}
+
+static int check_featuregroup_item(void *obj, void *arg, void *data, int flags)
+{
+ struct ast_applicationmap_item *appmap_item;
+ struct featuregroup_item *fg_item = obj;
+ int *err = arg;
+ struct ao2_container *applicationmap = data;
+
+ appmap_item = ao2_find(applicationmap, fg_item->appmap_item_name, OBJ_KEY);
+ if (!appmap_item) {
+ *err = 1;
+ return CMP_STOP;
+ }
+
+ fg_item->appmap_item = appmap_item;
+
+ return 0;
+}
+
+static int check_featuregroup(void *obj, void *arg, void *data, int flags)
+{
+ struct featuregroup *group = obj;
+ int *err = arg;
+
+ ao2_callback_data(group->items, 0, check_featuregroup_item, arg, data);
+
+ if (*err) {
+ ast_log(LOG_WARNING, "Featuregroup %s refers to non-existent applicationmap item\n",
+ group->name);
+ }
+
+ return *err ? CMP_STOP : 0;
+}
+
+static int features_pre_apply_config(void);
+
+CONFIG_INFO_CORE("features", cfg_info, globals, features_config_alloc,
+ .files = ACO_FILES(&features_conf),
+ .pre_apply_config = features_pre_apply_config,
+);
+
+static int features_pre_apply_config(void)
+{
+ struct features_config *cfg = aco_pending_config(&cfg_info);
+ int err = 0;
+
+ /* Now that the entire config has been processed, we can check that the featuregroup
+ * items refer to actual applicationmap items.
+ */
+
+ ao2_callback_data(cfg->featuregroups, 0, check_featuregroup, &err, cfg->applicationmap);
+
+ return err;
+}
+
+static int feature_read(struct ast_channel *chan, const char *cmd, char *data,
+ char *buf, size_t len)
+{
+ int res;
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+ SCOPED_CHANNELLOCK(lock, chan);
+
+ if (!strcasecmp(data, "inherit")) {
+ struct ast_datastore *ds = get_feature_chan_ds(chan);
+ unsigned int inherit = ds ? ds->inheritance : 0;
+
+ snprintf(buf, len, "%s", inherit ? "yes" : "no");
+ return 0;
+ }
+
+ cfg = get_feature_ds(chan);
+ if (!cfg) {
+ return -1;
+ }
+
+ res = general_get(cfg->global->general, data, buf, len) &&
+ xfer_get(cfg->global->xfer, data, buf, len) &&
+ pickup_get(cfg->global->pickup, data, buf, len);
+
+ if (res) {
+ ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
+ }
+
+ return res;
+}
+
+static int feature_write(struct ast_channel *chan, const char *cmd, char *data,
+ const char *value)
+{
+ int res;
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+ SCOPED_CHANNELLOCK(lock, chan);
+
+ if (!strcasecmp(data, "inherit")) {
+ struct ast_datastore *ds = get_feature_chan_ds(chan);
+ if (ds) {
+ ds->inheritance = ast_true(value) ? DATASTORE_INHERIT_FOREVER : 0;
+ }
+ return 0;
+ }
+
+ if (!(cfg = get_feature_ds(chan))) {
+ return -1;
+ }
+
+ res = general_set(cfg->global->general, data, value) &&
+ xfer_set(cfg->global->xfer, data, value) &&
+ pickup_set(cfg->global->pickup, data, value);
+
+ if (res) {
+ ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
+ }
+
+ return res;
+}
+
+static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data,
+ char *buf, size_t len)
+{
+ int res;
+ SCOPED_CHANNELLOCK(lock, chan);
+
+ res = ast_get_builtin_feature(chan, data, buf, len);
+
+ if (res) {
+ ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
+ }
+
+ return res;
+}
+
+static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data,
+ const char *value)
+{
+ int res;
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+ SCOPED_CHANNELLOCK(lock, chan);
+
+ if (!(cfg = get_feature_ds(chan))) {
+ return -1;
+ }
+
+ res = featuremap_set(cfg->featuremap, data, value);
+ if (res) {
+ ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct ast_custom_function feature_function = {
+ .name = "FEATURE",
+ .read = feature_read,
+ .write = feature_write
+};
+
+static struct ast_custom_function featuremap_function = {
+ .name = "FEATUREMAP",
+ .read = featuremap_read,
+ .write = featuremap_write
+};
+
+static int load_config(int reload)
+{
+ if (!reload && aco_info_init(&cfg_info)) {
+ ast_log(LOG_ERROR, "Unable to initialize configuration info for features\n");
+ return -1;
+ }
+
+ aco_option_register_custom(&cfg_info, "featuredigittimeout", ACO_EXACT, global_options,
+ __stringify(DEFAULT_FEATURE_DIGIT_TIMEOUT), general_handler, 0);
+ aco_option_register_custom(&cfg_info, "courtesytone", ACO_EXACT, global_options,
+ __stringify(DEFAULT_COURTESY_TONE), general_handler, 0);
+
+ aco_option_register_custom(&cfg_info, "transferdigittimeout", ACO_EXACT, global_options,
+ __stringify(DEFAULT_TRANSFER_DIGIT_TIMEOUT), xfer_handler, 0)
+ aco_option_register_custom(&cfg_info, "atxfernoanswertimeout", ACO_EXACT, global_options,
+ __stringify(DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER), xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxferdropcall", ACO_EXACT, global_options,
+ __stringify(DEFAULT_ATXFER_DROP_CALL), xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxferloopdelay", ACO_EXACT, global_options,
+ __stringify(DEFAULT_ATXFER_LOOP_DELAY), xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxfercallbackretries", ACO_EXACT, global_options,
+ __stringify(DEFAULT_ATXFER_CALLBACK_RETRIES), xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "xfersound", ACO_EXACT, global_options,
+ DEFAULT_XFERSOUND, xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "xferfailsound", ACO_EXACT, global_options,
+ DEFAULT_XFERFAILSOUND, xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxferabort", ACO_EXACT, global_options,
+ DEFAULT_ATXFER_ABORT, xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxfercomplete", ACO_EXACT, global_options,
+ DEFAULT_ATXFER_COMPLETE, xfer_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxferthreeway", ACO_EXACT, global_options,
+ DEFAULT_ATXFER_THREEWAY, xfer_handler, 0);
+
+ aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options,
+ DEFAULT_PICKUPEXTEN, pickup_handler, 0);
+ aco_option_register_custom(&cfg_info, "pickupsound", ACO_EXACT, global_options,
+ DEFAULT_PICKUPSOUND, pickup_handler, 0);
+ aco_option_register_custom(&cfg_info, "pickupfailsound", ACO_EXACT, global_options,
+ DEFAULT_PICKUPFAILSOUND, pickup_handler, 0);
+
+ aco_option_register_custom(&cfg_info, "blindxfer", ACO_EXACT, featuremap_options,
+ DEFAULT_FEATUREMAP_BLINDXFER, featuremap_handler, 0);
+ aco_option_register_custom(&cfg_info, "disconnect", ACO_EXACT, featuremap_options,
+ DEFAULT_FEATUREMAP_DISCONNECT, featuremap_handler, 0);
+ aco_option_register_custom(&cfg_info, "automon", ACO_EXACT, featuremap_options,
+ DEFAULT_FEATUREMAP_AUTOMON, featuremap_handler, 0);
+ aco_option_register_custom(&cfg_info, "atxfer", ACO_EXACT, featuremap_options,
+ DEFAULT_FEATUREMAP_ATXFER, featuremap_handler, 0);
+ aco_option_register_custom(&cfg_info, "parkcall", ACO_EXACT, featuremap_options,
+ DEFAULT_FEATUREMAP_PARKCALL, featuremap_handler, 0);
+ aco_option_register_custom(&cfg_info, "automixmon", ACO_EXACT, featuremap_options,
+ DEFAULT_FEATUREMAP_AUTOMIXMON, featuremap_handler, 0);
+
+ aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, applicationmap_options,
+ "", applicationmap_handler, 0);
+
+ aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, featuregroup_options,
+ "", featuregroup_handler, 0);
+
+ if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
+ ast_log(LOG_ERROR, "Failed to process features.conf configuration!\n");
+ if (!reload) {
+ aco_info_destroy(&cfg_info);
+ ao2_global_obj_release(globals);
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static int print_featuregroup(void *obj, void *arg, int flags)
+{
+ struct featuregroup_item *item = obj;
+ struct ast_cli_args *a = arg;
+
+ ast_cli(a->fd, "===> --> %s (%s)\n", item->appmap_item_name,
+ S_OR(item->dtmf_override, item->appmap_item->dtmf));
+
+ return 0;
+}
+
+static int print_featuregroups(void *obj, void *arg, int flags)
+{
+ struct featuregroup *group = obj;
+ struct ast_cli_args *a = arg;
+
+ ast_cli(a->fd, "===> Group: %s\n", group->name);
+
+ ao2_callback(group->items, 0, print_featuregroup, a);
+ return 0;
+}
+
+#define HFS_FORMAT "%-25s %-7s %-7s\n"
+
+static int print_applicationmap(void *obj, void *arg, int flags)
+{
+ struct ast_applicationmap_item *item = obj;
+ struct ast_cli_args *a = arg;
+
+ ast_cli(a->fd, HFS_FORMAT, item->name, "no def", item->dtmf);
+ return 0;
+}
+
+/*!
+ * \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)
+{
+ RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
+
+ 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;
+ }
+
+ cfg = ao2_global_obj_ref(globals);
+
+ ast_cli(a->fd, HFS_FORMAT, "Builtin Feature", "Default", "Current");
+ ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
+
+ ast_cli(a->fd, HFS_FORMAT, "Pickup", DEFAULT_PICKUPEXTEN, cfg->global->pickup->pickupexten);
+ ast_cli(a->fd, HFS_FORMAT, "Blind Transfer", DEFAULT_FEATUREMAP_BLINDXFER, cfg->featuremap->blindxfer);
+ ast_cli(a->fd, HFS_FORMAT, "Attended Transfer", DEFAULT_FEATUREMAP_ATXFER, cfg->featuremap->atxfer);
+ ast_cli(a->fd, HFS_FORMAT, "One Touch Monitor", DEFAULT_FEATUREMAP_AUTOMON, cfg->featuremap->automon);
+ ast_cli(a->fd, HFS_FORMAT, "Disconnect Call", DEFAULT_FEATUREMAP_DISCONNECT, cfg->featuremap->disconnect);
+ ast_cli(a->fd, HFS_FORMAT, "Park Call", DEFAULT_FEATUREMAP_PARKCALL, cfg->featuremap->parkcall);
+ ast_cli(a->fd, HFS_FORMAT, "One Touch MixMonitor", DEFAULT_FEATUREMAP_AUTOMIXMON, cfg->featuremap->automixmon);
+
+ ast_cli(a->fd, "\n");
+ ast_cli(a->fd, HFS_FORMAT, "Dynamic Feature", "Default", "Current");
+ ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
+ if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) {
+ ast_cli(a->fd, "(none)\n");
+ } else {
+ ao2_callback(cfg->applicationmap, 0, print_applicationmap, a);
+ }
+
+ ast_cli(a->fd, "\nFeature Groups:\n");
+ ast_cli(a->fd, "---------------\n");
+ if (!cfg->featuregroups || ao2_container_count(cfg->featuregroups) == 0) {
+ ast_cli(a->fd, "(none)\n");
+ } else {
+ ao2_callback(cfg->featuregroups, 0, print_featuregroups, a);
+ }
+
+ ast_cli(a->fd, "\n");
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_features_config[] = {
+ AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
+};
+
+void ast_features_config_shutdown(void)
+{
+ ast_custom_function_unregister(&featuremap_function);
+ ast_custom_function_unregister(&feature_function);
+ ast_cli_unregister_multiple(cli_features_config, ARRAY_LEN(cli_features_config));
+ aco_info_destroy(&cfg_info);
+ ao2_global_obj_release(globals);
+}
+
+int ast_features_config_reload(void)
+{
+ return load_config(1);
+}
+
+int ast_features_config_init(void)
+{
+ int res;
+
+ res = load_config(0);
+ res |= __ast_custom_function_register(&feature_function, NULL);
+ res |= __ast_custom_function_register(&featuremap_function, NULL);
+ res |= ast_cli_register_multiple(cli_features_config, ARRAY_LEN(cli_features_config));
+
+ if (res) {
+ ast_features_config_shutdown();
+ }
+
+ return res;
+}
diff --git a/main/manager.c b/main/manager.c
index 229b83b4e..da5a98490 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -96,6 +96,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/test.h"
#include "asterisk/json.h"
#include "asterisk/bridging.h"
+#include "asterisk/features_config.h"
/*** DOCUMENTATION
<manager name="Ping" language="en_US">
@@ -4048,8 +4049,8 @@ static int action_atxfer(struct mansession *s, const struct message *m)
const char *exten = astman_get_header(m, "Exten");
const char *context = astman_get_header(m, "Context");
struct ast_channel *chan = NULL;
- struct ast_call_feature *atxfer_feature = NULL;
- char *feature_code = NULL;
+ char feature_code[AST_FEATURE_MAX_LEN];
+ const char *digit;
if (ast_strlen_zero(name)) {
astman_send_error(s, m, "No channel specified");
@@ -4060,31 +4061,33 @@ static int action_atxfer(struct mansession *s, const struct message *m)
return 0;
}
- ast_rdlock_call_features();
- atxfer_feature = ast_find_call_feature("atxfer");
- ast_unlock_call_features();
- if (!atxfer_feature) {
- astman_send_error(s, m, "No attended transfer feature found");
+ if (!(chan = ast_channel_get_by_name(name))) {
+ astman_send_error(s, m, "Channel specified does not exist");
return 0;
}
- if (!(chan = ast_channel_get_by_name(name))) {
- astman_send_error(s, m, "Channel specified does not exist");
+ ast_channel_lock(chan);
+ if (ast_get_builtin_feature(chan, "atxfer", feature_code, sizeof(feature_code)) ||
+ ast_strlen_zero(feature_code)) {
+ ast_channel_unlock(chan);
+ astman_send_error(s, m, "No attended transfer feature code found");
+ ast_channel_unref(chan);
return 0;
}
+ ast_channel_unlock(chan);
if (!ast_strlen_zero(context)) {
pbx_builtin_setvar_helper(chan, "TRANSFER_CONTEXT", context);
}
/* BUGBUG action_atxfer() is broken because the bridge DTMF hooks need both begin and end events to match correctly. */
- for (feature_code = atxfer_feature->exten; feature_code && *feature_code; ++feature_code) {
- struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *feature_code };
+ for (digit = feature_code; *digit; ++digit) {
+ struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit };
ast_queue_frame(chan, &f);
}
- for (feature_code = (char *)exten; feature_code && *feature_code; ++feature_code) {
- struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *feature_code };
+ for (digit = exten; *digit; ++digit) {
+ struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit };
ast_queue_frame(chan, &f);
}