diff --git a/apps/app_dial.c b/apps/app_dial.c
index b6f1ce8723a30d53c65b4a7d92f225acc3fc07d9..b0caa5c6be4535bce95ed68a8f66bf3852f1e29a 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 9574bb844587200d1ec22c9d75d9824a9587f640..e11b280cdb67dc77f8ae2166fb6fb655185c0b1f 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 00c25b9c12ce1991440a7ac554bf7992f49bc274..92de023919906d38740d451cca59f40980ac05dc 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 df82061b8e59d4aa2eb1c2bc63a0116a6a861536..9080dbaf0380741fd371ab52195b2a0997eed7b4 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 c68538e662864db621c7d43ba2e9c89cd565c25c..1a8f0d9802ed3700334a741a1ff01fdfe5430ca6 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 fe67d28c7672abff564996515dba3af46e0effe0..eb79d237ef8c9dd2be84e7f317398189e629d41b 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 426e6ab10b351175e03ea157c2d0fd28663eb804..28d332f62507016219ee9902bd871e2e16afc343 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 e3bd1d0c895766929c39ac67fc2257b224878124..956c4a654a86636ed25cb1cac2d2bc44dc447854 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 185f3935d058f21cfb32e6fa5f744211e8c9e095..0adde37f2024e6a8c91d4beb935b13b5624f565b 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 ec39ecf82322891c7f2479b34efea0744b033b00..c2edc18e7a2ff2a9cc73c0c8e28a78bf475ea24c 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 4f556138138bc8aade90d189a9d5473b6c50adee..9b586506fe9ed68025d762c4b4f45b342e4f06a8 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 0000000000000000000000000000000000000000..a80fa7968d15e625395cae4a36fe8dca2cf1bd80
--- /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 93db3b0ef49cbf6fc0a54a97385e860b9a1c12df..c59638736f71b647062a4a0af06a99efe39b0cfa 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 2aa49ff353c4036890425f670d72bc43a6223c6c..e0c6548a96ed4c0e597f21e2d5e04a472dc0ab0b 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,3642 +2707,1615 @@ 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);
-}
+#endif
 
 /*!
- * \brief Add new feature group
- * \param fgname feature group name.
+ * \internal
+ * \brief Get the extension for a given builtin feature
  *
- * 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.
+ * \pre expects features_lock to be readlocked
  *
- * Check fg and feature specified, add feature to list
- * \note This function MUST be called while feature_groups is locked.
+ * \retval 0 success
+ * \retval non-zero failiure
  */
-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)
+static int builtin_feature_get_exten(struct ast_channel *chan, const char *feature_name,
+		char *buf, size_t len)
 {
-	if (!feature) {
-		return;
-	}
+	SCOPED_CHANNELLOCK(lock, chan);
 
-	ast_wrlock_call_features();
-	AST_LIST_REMOVE(&feature_list, feature, feature_entry);
-	ast_unlock_call_features();
-
-	ast_free(feature);
+	return ast_get_builtin_feature(chan, feature_name, buf, len);
 }
 
-/*! \brief Remove all features in the list */
-static void ast_unregister_features(void)
+static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
 {
-	struct ast_call_feature *feature;
+/* 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_wrlock_call_features();
-	while ((feature = AST_LIST_REMOVE_HEAD(&feature_list, feature_entry))) {
-		ast_free(feature);
+	if (ast_test_flag(&config->features_caller, AST_FEATURE_DTMF_MASK)) {
+		ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
 	}
-	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;
-		}
+	if (ast_test_flag(&config->features_callee, AST_FEATURE_DTMF_MASK)) {
+		ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
 	}
 
-	return tmp;
-}
+	if (!(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
+		RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
 
-/*! \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_channel_lock(chan);
+		applicationmap = ast_get_chan_applicationmap(chan);
+		ast_channel_unlock(chan);
+
+		if (!applicationmap) {
+			return;
 		}
 
-		ast_string_field_free_memory(fg);
-		ast_free(fg);
+		/* 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);
 	}
-	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,
-};
-
+#if 0
 /*!
  * \internal
- * \brief Find or create feature datastore on a channel
+ * \brief Get feature and dial.
+ *
+ * \param caller Channel to represent as the calling channel for the dialed channel.
+ * \param caller_name Original caller channel name.
+ * \param requestor Channel to say is requesting the dial (usually the caller).
+ * \param transferee Channel that the dialed channel will be transferred to.
+ * \param type Channel technology type to dial.
+ * \param format Codec formats for dialed channel.
+ * \param addr destination of the call
+ * \param timeout Time limit for dialed channel to answer in ms. Must be greater than zero.
+ * \param outstate Status of dialed channel if unsuccessful.
+ * \param language Language of the caller.
+ *
+ * \note
+ * outstate can be:
+ * 0, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION,
+ * AST_CONTROL_ANSWER, or AST_CONTROL_UNHOLD.  If
+ * AST_CONTROL_UNHOLD then the caller channel cancelled the
+ * transfer or the dialed channel did not answer before the
+ * timeout.
+ *
+ * \details
+ * Request channel, set channel variables, initiate call,
+ * check if they want to disconnect, go into loop, check if timeout has elapsed,
+ * check if person to be transfered hung up, check for answer break loop,
+ * set cdr return channel.
  *
- * \pre chan is locked
+ * \retval Channel Connected channel for transfer.
+ * \retval NULL on failure to get third party connected.
  *
- * \return the data on the FEATURE datastore, or NULL on error
+ * \note This is similar to __ast_request_and_dial() in channel.c
  */
-static struct feature_datastore *get_feature_ds(struct ast_channel *chan)
+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)
 {
-	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;
-	}
+	int state = 0;
+	int cause = 0;
+	int to;
+	int caller_hungup;
+	int transferee_hungup;
+	struct ast_channel *chan;
+	struct ast_channel *monitor_chans[3];
+	struct ast_channel *active_channel;
+	int res;
+	int ready = 0;
+	struct timeval started;
+	int x, len = 0;
+	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;
 
-	if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) {
-		feature_ds_destroy(feature_ds);
+	tmp_cap = ast_format_cap_alloc_nolock();
+	if (!tmp_cap) {
+		if (outstate) {
+			*outstate = 0;
+		}
 		return NULL;
 	}
+	ast_best_codec(cap, &best_audio_fmt);
+	ast_format_cap_add(tmp_cap, &best_audio_fmt);
 
-	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;
-}
-
-/*!
- * \internal
- * \brief Get the extension for a given builtin feature
- *
- * \pre expects features_lock to be readlocked
- *
- * \retval 0 success
- * \retval non-zero failiure
- */
-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';
+	caller_hungup = ast_check_hangup(caller);
 
-	if (!(feature = ast_find_call_feature(feature_name))) {
-		return -1;
+	if (!(chan = ast_request(type, tmp_cap, requestor, addr, &cause))) {
+		ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, addr);
+		switch (cause) {
+		case AST_CAUSE_BUSY:
+			state = AST_CONTROL_BUSY;
+			break;
+		case AST_CAUSE_CONGESTION:
+			state = AST_CONTROL_CONGESTION;
+			break;
+		default:
+			state = 0;
+			break;
+		}
+		goto done;
 	}
 
-	ast_copy_string(buf, feature->exten, len);
-
-	ast_unlock_call_features();
+	ast_channel_language_set(chan, language);
+	ast_channel_inherit_variables(caller, chan);
+	pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller_name);
 
 	ast_channel_lock(chan);
-	if ((feature_ds = get_feature_ds(chan))) {
-		fe = ao2_find(feature_ds->feature_map, feature_name, OBJ_KEY);
-	}
+	ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(requestor));
 	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 (ast_call(chan, addr, timeout)) {
+		ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, addr);
+		switch (ast_channel_hangupcause(chan)) {
+		case AST_CAUSE_BUSY:
+			state = AST_CONTROL_BUSY;
+			break;
+		case AST_CAUSE_CONGESTION:
+			state = AST_CONTROL_CONGESTION;
+			break;
+		default:
+			state = 0;
+			break;
 		}
+		goto done;
 	}
 
-	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);
+	/* support dialing of the featuremap disconnect code while performing an attended tranfer */
+	ast_channel_lock(chan);
+	disconnect_res = ast_get_builtin_feature(chan, "disconnect",
+			disconnect_code, sizeof(disconnect_code));
+	ast_channel_unlock(chan);
 
-	if (res) {
-		return AST_FEATURE_RETURN_SUCCESSBREAK;
+	if (!disconnect_res) {
+		len = strlen(disconnect_code) + 1;
+		dialed_code = ast_alloca(len);
+		memset(dialed_code, 0, len);
 	}
-	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();
+	x = 0;
+	started = ast_tvnow();
+	to = timeout;
+	AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
 
-	return res;
-}
+	ast_poll_channel_add(caller, chan);
 
-/*!
- * \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;
+	transferee_hungup = 0;
+	while (!ast_check_hangup(transferee) && (ast_channel_state(chan) != AST_STATE_UP)) {
+		int num_chans = 0;
 
-	if (!(peer && chan && config) && operation == FEATURE_INTERPRET_DO) {
-		return -1; /* can not run feature operation */
-	}
+		monitor_chans[num_chans++] = transferee;
+		monitor_chans[num_chans++] = chan;
+		if (!caller_hungup) {
+			if (ast_check_hangup(caller)) {
+				caller_hungup = 1;
 
-	ast_rdlock_call_features();
-	for (x = 0; x < FEATURES_COUNT; x++) {
-		char feature_exten[FEATURE_MAX_LEN] = "";
+#if defined(ATXFER_NULL_TECH)
+				/* Change caller's name to ensure that it will remain unique. */
+				set_new_chan_name(caller);
 
-		if (!ast_test_flag(features, builtin_features[x].feature_mask)) {
-			continue;
+				/*
+				 * Get rid of caller's physical technology so it is free for
+				 * other calls.
+				 */
+				set_kill_chan_tech(caller);
+#endif	/* defined(ATXFER_NULL_TECH) */
+			} else {
+				/* caller is not hungup so monitor it. */
+				monitor_chans[num_chans++] = caller;
+			}
 		}
 
-		if (builtin_feature_get_exten(chan, builtin_features[x].sname, feature_exten, sizeof(feature_exten))) {
-			continue;
+		/* see if the timeout has been violated */
+		if (ast_tvdiff_ms(ast_tvnow(), started) > timeout) {
+			state = AST_CONTROL_UNHOLD;
+			ast_log(LOG_NOTICE, "We exceeded our AT-timeout for %s\n", ast_channel_name(chan));
+			break; /*doh! timeout*/
 		}
 
-		/* 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);
+		active_channel = ast_waitfor_n(monitor_chans, num_chans, &to);
+		if (!active_channel)
+			continue;
+
+		f = NULL;
+		if (transferee == active_channel) {
+			struct ast_frame *dup_f;
+
+			f = ast_read(transferee);
+			if (f == NULL) { /*doh! where'd he go?*/
+				transferee_hungup = 1;
+				state = 0;
+				break;
 			}
-			if (feature) {
-				memcpy(feature, &builtin_features[x], sizeof(*feature));
+			if (ast_is_deferrable_frame(f)) {
+				dup_f = ast_frisolate(f);
+				if (dup_f) {
+					if (dup_f == f) {
+						f = NULL;
+					}
+					AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
+				}
 			}
-			feature_detected = 1;
-			break;
-		} else if (!strncmp(feature_exten, code, strlen(code))) {
-			if (res == AST_FEATURE_RETURN_PASSDIGITS) {
-				res = AST_FEATURE_RETURN_STOREDIGITS;
+		} else if (chan == active_channel) {
+			if (!ast_strlen_zero(ast_channel_call_forward(chan))) {
+				state = 0;
+				ast_autoservice_start(transferee);
+				chan = ast_call_forward(caller, chan, NULL, tmp_cap, NULL, &state);
+				ast_autoservice_stop(transferee);
+				if (!chan) {
+					break;
+				}
+				continue;
+			}
+			f = ast_read(chan);
+			if (f == NULL) { /*doh! where'd he go?*/
+				switch (ast_channel_hangupcause(chan)) {
+				case AST_CAUSE_BUSY:
+					state = AST_CONTROL_BUSY;
+					break;
+				case AST_CAUSE_CONGESTION:
+					state = AST_CONTROL_CONGESTION;
+					break;
+				default:
+					state = 0;
+					break;
+				}
+				break;
 			}
-		}
-	}
-
-	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);
+			if (f->frametype == AST_FRAME_CONTROL) {
+				if (f->subclass.integer == AST_CONTROL_RINGING) {
+					ast_verb(3, "%s is ringing\n", ast_channel_name(chan));
+					ast_indicate(caller, AST_CONTROL_RINGING);
+				} else if (f->subclass.integer == AST_CONTROL_BUSY) {
+					state = f->subclass.integer;
+					ast_verb(3, "%s is busy\n", ast_channel_name(chan));
+					ast_indicate(caller, AST_CONTROL_BUSY);
+					ast_frfree(f);
+					break;
+				} else if (f->subclass.integer == AST_CONTROL_INCOMPLETE) {
+					ast_verb(3, "%s dialed incomplete extension %s; ignoring\n", ast_channel_name(chan), ast_channel_exten(chan));
+				} else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
+					state = f->subclass.integer;
+					ast_verb(3, "%s is congested\n", ast_channel_name(chan));
+					ast_indicate(caller, AST_CONTROL_CONGESTION);
+					ast_frfree(f);
+					break;
+				} else if (f->subclass.integer == AST_CONTROL_ANSWER) {
+					/* This is what we are hoping for */
+					state = f->subclass.integer;
+					ast_frfree(f);
+					ready=1;
+					break;
+				} else if (f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
+					ast_indicate_data(caller, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
+				} else if (f->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
+					if (caller_hungup) {
+						struct ast_party_connected_line connected;
 
-	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);
+						/* Just save it for the transfer. */
+						ast_party_connected_line_set_init(&connected, ast_channel_connected(caller));
+						res = ast_connected_line_parse_data(f->data.ptr, f->datalen,
+							&connected);
+						if (!res) {
+							ast_channel_set_connected_line(caller, &connected, NULL);
+						}
+						ast_party_connected_line_free(&connected);
+					} else {
+						ast_autoservice_start(transferee);
+						if (ast_channel_connected_line_sub(chan, caller, f, 1) &&
+							ast_channel_connected_line_macro(chan, caller, f, 1, 1)) {
+							ast_indicate_data(caller, AST_CONTROL_CONNECTED_LINE,
+								f->data.ptr, f->datalen);
+						}
+						ast_autoservice_stop(transferee);
+					}
+				} else if (f->subclass.integer == AST_CONTROL_REDIRECTING) {
+					if (!caller_hungup) {
+						ast_autoservice_start(transferee);
+						if (ast_channel_redirecting_sub(chan, caller, f, 1) &&
+							ast_channel_redirecting_macro(chan, caller, f, 1, 1)) {
+							ast_indicate_data(caller, AST_CONTROL_REDIRECTING,
+								f->data.ptr, f->datalen);
+						}
+						ast_autoservice_stop(transferee);
 					}
-					if (feature) {
-						memcpy(feature, fge->feature, sizeof(*feature));
+				} else if (f->subclass.integer != -1
+					&& f->subclass.integer != AST_CONTROL_PROGRESS
+					&& f->subclass.integer != AST_CONTROL_PROCEEDING) {
+					ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass.integer);
+				}
+				/* else who cares */
+			} else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
+				ast_write(caller, f);
+			}
+		} else if (caller == active_channel) {
+			f = ast_read(caller);
+			if (f) {
+				if (f->frametype == AST_FRAME_DTMF && dialed_code) {
+					dialed_code[x++] = f->subclass.integer;
+					dialed_code[x] = '\0';
+					if (strlen(dialed_code) == len) {
+						x = 0;
+					} else if (x && strncmp(dialed_code, disconnect_code, x)) {
+						x = 0;
+						dialed_code[x] = '\0';
 					}
-					if (res != AST_FEATURE_RETURN_KEEPTRYING) {
-						AST_RWLIST_UNLOCK(&feature_groups);
+					if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
+						/* Caller Canceled the call */
+						state = AST_CONTROL_UNHOLD;
+						ast_frfree(f);
 						break;
 					}
-					res = AST_FEATURE_RETURN_PASSDIGITS;
-				} else if (!strncmp(fge->exten, code, strlen(code))) {
-					res = AST_FEATURE_RETURN_STOREDIGITS;
+				} else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
+					ast_write(chan, f);
 				}
 			}
-			if (fge) {
-				break;
-			}
 		}
-		AST_RWLIST_UNLOCK(&feature_groups);
+		if (f)
+			ast_frfree(f);
+	} /* end while */
 
-		ast_rdlock_call_features();
+	ast_poll_channel_del(caller, chan);
 
-		if (!(tmpfeature = find_dynamic_feature(tok))) {
-			ast_unlock_call_features();
-			continue;
+	/*
+	 * We need to free all the deferred frames, but we only need to
+	 * queue the deferred frames if no hangup was received.
+	 */
+	ast_channel_lock(transferee);
+	transferee_hungup = (transferee_hungup || ast_check_hangup(transferee));
+	while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) {
+		if (!transferee_hungup) {
+			ast_queue_frame_head(transferee, f);
 		}
-
-		/* 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_frfree(f);
 	}
+	ast_channel_unlock(transferee);
 
-	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;
+done:
+	ast_indicate(caller, -1);
+	if (chan && (ready || ast_channel_state(chan) == AST_STATE_UP)) {
+		state = AST_CONTROL_ANSWER;
+	} else if (chan) {
+		ast_hangup(chan);
+		chan = NULL;
 	}
 
-	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);
+	tmp_cap = ast_format_cap_destroy(tmp_cap);
 
-	ast_free(dynamic_features_buf);
+	if (outstate)
+		*outstate = state;
 
-	return res;
+	return chan;
 }
 #endif
 
+void ast_channel_log(char *title, struct ast_channel *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);
-}
-
-#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;
+void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this is handy enough to justify keeping it in the source */
+{
+	ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
+	ast_log(LOG_NOTICE, "CHAN: name: %s;  appl: %s; data: %s; contxt: %s;  exten: %s; pri: %d;\n",
+		ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
+		ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+	ast_log(LOG_NOTICE, "CHAN: acctcode: %s;  dialcontext: %s; amaflags: %x; maccontxt: %s;  macexten: %s; macpri: %d;\n",
+		ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
+		ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
+	ast_log(LOG_NOTICE, "CHAN: masq: %p;  masqr: %p; uniqueID: %s; linkedID:%s\n",
+		ast_channel_masq(chan), ast_channel_masqr(chan),
+		ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
+	if (ast_channel_masqr(chan)) {
+		ast_log(LOG_NOTICE, "CHAN: masquerading as: %s;  cdr: %p;\n",
+			ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
 	}
-	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;
+	ast_log(LOG_NOTICE, "===== done ====\n");
 }
-#endif
 
-static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
+static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
 {
-	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);
+	const char *feature;
 
-		if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
-			ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
+	if (ast_strlen_zero(features)) {
+		return;
 	}
-	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;
-
-					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);
+	for (feature = features; *feature; feature++) {
+		struct ast_flags *party;
 
-				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 (isupper(*feature)) {
+			party = &config->features_caller;
+		} else {
+			party = &config->features_callee;
 		}
-	}
-}
 
-/*!
- * \internal
- * \brief Get feature and dial.
- *
- * \param caller Channel to represent as the calling channel for the dialed channel.
- * \param caller_name Original caller channel name.
- * \param requestor Channel to say is requesting the dial (usually the caller).
- * \param transferee Channel that the dialed channel will be transferred to.
- * \param type Channel technology type to dial.
- * \param format Codec formats for dialed channel.
- * \param addr destination of the call
- * \param timeout Time limit for dialed channel to answer in ms. Must be greater than zero.
- * \param outstate Status of dialed channel if unsuccessful.
- * \param language Language of the caller.
- *
- * \note
- * outstate can be:
- * 0, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION,
- * AST_CONTROL_ANSWER, or AST_CONTROL_UNHOLD.  If
- * AST_CONTROL_UNHOLD then the caller channel cancelled the
- * transfer or the dialed channel did not answer before the
- * timeout.
- *
- * \details
- * Request channel, set channel variables, initiate call,
- * check if they want to disconnect, go into loop, check if timeout has elapsed,
- * check if person to be transfered hung up, check for answer break loop,
- * set cdr return channel.
- *
- * \retval Channel Connected channel for transfer.
- * \retval NULL on failure to get third party connected.
- *
- * \note This is similar to __ast_request_and_dial() in channel.c
- */
-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)
-{
-	int state = 0;
-	int cause = 0;
-	int to;
-	int caller_hungup;
-	int transferee_hungup;
-	struct ast_channel *chan;
-	struct ast_channel *monitor_chans[3];
-	struct ast_channel *active_channel;
-	int res;
-	int ready = 0;
-	struct timeval started;
-	int x, len = 0;
-	char *disconnect_code = NULL, *dialed_code = NULL;
-	struct ast_format_cap *tmp_cap;
-	struct ast_format best_audio_fmt;
-	struct ast_frame *f;
-	AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
-
-	tmp_cap = ast_format_cap_alloc_nolock();
-	if (!tmp_cap) {
-		if (outstate) {
-			*outstate = 0;
-		}
-		return NULL;
-	}
-	ast_best_codec(cap, &best_audio_fmt);
-	ast_format_cap_add(tmp_cap, &best_audio_fmt);
-
-	caller_hungup = ast_check_hangup(caller);
-
-	if (!(chan = ast_request(type, tmp_cap, requestor, addr, &cause))) {
-		ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, addr);
-		switch (cause) {
-		case AST_CAUSE_BUSY:
-			state = AST_CONTROL_BUSY;
-			break;
-		case AST_CAUSE_CONGESTION:
-			state = AST_CONTROL_CONGESTION;
-			break;
-		default:
-			state = 0;
-			break;
-		}
-		goto done;
-	}
-
-	ast_channel_language_set(chan, language);
-	ast_channel_inherit_variables(caller, chan);
-	pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller_name);
-
-	ast_channel_lock(chan);
-	ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(requestor));
-	ast_channel_unlock(chan);
-
-	if (ast_call(chan, addr, timeout)) {
-		ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, addr);
-		switch (ast_channel_hangupcause(chan)) {
-		case AST_CAUSE_BUSY:
-			state = AST_CONTROL_BUSY;
-			break;
-		case AST_CAUSE_CONGESTION:
-			state = AST_CONTROL_CONGESTION;
-			break;
-		default:
-			state = 0;
-			break;
-		}
-		goto done;
-	}
-
-	/* 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;
-
-		disconnect_code = builtin_features[x].exten;
-		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;
-	AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
-
-	ast_poll_channel_add(caller, chan);
-
-	transferee_hungup = 0;
-	while (!ast_check_hangup(transferee) && (ast_channel_state(chan) != AST_STATE_UP)) {
-		int num_chans = 0;
-
-		monitor_chans[num_chans++] = transferee;
-		monitor_chans[num_chans++] = chan;
-		if (!caller_hungup) {
-			if (ast_check_hangup(caller)) {
-				caller_hungup = 1;
-
-#if defined(ATXFER_NULL_TECH)
-				/* Change caller's name to ensure that it will remain unique. */
-				set_new_chan_name(caller);
-
-				/*
-				 * Get rid of caller's physical technology so it is free for
-				 * other calls.
-				 */
-				set_kill_chan_tech(caller);
-#endif	/* defined(ATXFER_NULL_TECH) */
-			} else {
-				/* caller is not hungup so monitor it. */
-				monitor_chans[num_chans++] = caller;
-			}
-		}
-
-		/* see if the timeout has been violated */
-		if (ast_tvdiff_ms(ast_tvnow(), started) > timeout) {
-			state = AST_CONTROL_UNHOLD;
-			ast_log(LOG_NOTICE, "We exceeded our AT-timeout for %s\n", ast_channel_name(chan));
-			break; /*doh! timeout*/
-		}
-
-		active_channel = ast_waitfor_n(monitor_chans, num_chans, &to);
-		if (!active_channel)
-			continue;
-
-		f = NULL;
-		if (transferee == active_channel) {
-			struct ast_frame *dup_f;
-
-			f = ast_read(transferee);
-			if (f == NULL) { /*doh! where'd he go?*/
-				transferee_hungup = 1;
-				state = 0;
-				break;
-			}
-			if (ast_is_deferrable_frame(f)) {
-				dup_f = ast_frisolate(f);
-				if (dup_f) {
-					if (dup_f == f) {
-						f = NULL;
-					}
-					AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
-				}
-			}
-		} else if (chan == active_channel) {
-			if (!ast_strlen_zero(ast_channel_call_forward(chan))) {
-				state = 0;
-				ast_autoservice_start(transferee);
-				chan = ast_call_forward(caller, chan, NULL, tmp_cap, NULL, &state);
-				ast_autoservice_stop(transferee);
-				if (!chan) {
-					break;
-				}
-				continue;
-			}
-			f = ast_read(chan);
-			if (f == NULL) { /*doh! where'd he go?*/
-				switch (ast_channel_hangupcause(chan)) {
-				case AST_CAUSE_BUSY:
-					state = AST_CONTROL_BUSY;
-					break;
-				case AST_CAUSE_CONGESTION:
-					state = AST_CONTROL_CONGESTION;
-					break;
-				default:
-					state = 0;
-					break;
-				}
-				break;
-			}
-
-			if (f->frametype == AST_FRAME_CONTROL) {
-				if (f->subclass.integer == AST_CONTROL_RINGING) {
-					ast_verb(3, "%s is ringing\n", ast_channel_name(chan));
-					ast_indicate(caller, AST_CONTROL_RINGING);
-				} else if (f->subclass.integer == AST_CONTROL_BUSY) {
-					state = f->subclass.integer;
-					ast_verb(3, "%s is busy\n", ast_channel_name(chan));
-					ast_indicate(caller, AST_CONTROL_BUSY);
-					ast_frfree(f);
-					break;
-				} else if (f->subclass.integer == AST_CONTROL_INCOMPLETE) {
-					ast_verb(3, "%s dialed incomplete extension %s; ignoring\n", ast_channel_name(chan), ast_channel_exten(chan));
-				} else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
-					state = f->subclass.integer;
-					ast_verb(3, "%s is congested\n", ast_channel_name(chan));
-					ast_indicate(caller, AST_CONTROL_CONGESTION);
-					ast_frfree(f);
-					break;
-				} else if (f->subclass.integer == AST_CONTROL_ANSWER) {
-					/* This is what we are hoping for */
-					state = f->subclass.integer;
-					ast_frfree(f);
-					ready=1;
-					break;
-				} else if (f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
-					ast_indicate_data(caller, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
-				} else if (f->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
-					if (caller_hungup) {
-						struct ast_party_connected_line connected;
-
-						/* Just save it for the transfer. */
-						ast_party_connected_line_set_init(&connected, ast_channel_connected(caller));
-						res = ast_connected_line_parse_data(f->data.ptr, f->datalen,
-							&connected);
-						if (!res) {
-							ast_channel_set_connected_line(caller, &connected, NULL);
-						}
-						ast_party_connected_line_free(&connected);
-					} else {
-						ast_autoservice_start(transferee);
-						if (ast_channel_connected_line_sub(chan, caller, f, 1) &&
-							ast_channel_connected_line_macro(chan, caller, f, 1, 1)) {
-							ast_indicate_data(caller, AST_CONTROL_CONNECTED_LINE,
-								f->data.ptr, f->datalen);
-						}
-						ast_autoservice_stop(transferee);
-					}
-				} else if (f->subclass.integer == AST_CONTROL_REDIRECTING) {
-					if (!caller_hungup) {
-						ast_autoservice_start(transferee);
-						if (ast_channel_redirecting_sub(chan, caller, f, 1) &&
-							ast_channel_redirecting_macro(chan, caller, f, 1, 1)) {
-							ast_indicate_data(caller, AST_CONTROL_REDIRECTING,
-								f->data.ptr, f->datalen);
-						}
-						ast_autoservice_stop(transferee);
-					}
-				} else if (f->subclass.integer != -1
-					&& f->subclass.integer != AST_CONTROL_PROGRESS
-					&& f->subclass.integer != AST_CONTROL_PROCEEDING) {
-					ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass.integer);
-				}
-				/* else who cares */
-			} else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
-				ast_write(caller, f);
-			}
-		} else if (caller == active_channel) {
-			f = ast_read(caller);
-			if (f) {
-				if (f->frametype == AST_FRAME_DTMF) {
-					dialed_code[x++] = f->subclass.integer;
-					dialed_code[x] = '\0';
-					if (strlen(dialed_code) == len) {
-						x = 0;
-					} else if (x && strncmp(dialed_code, disconnect_code, x)) {
-						x = 0;
-						dialed_code[x] = '\0';
-					}
-					if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
-						/* Caller Canceled the call */
-						state = AST_CONTROL_UNHOLD;
-						ast_frfree(f);
-						break;
-					}
-				} else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
-					ast_write(chan, f);
-				}
-			}
-		}
-		if (f)
-			ast_frfree(f);
-	} /* end while */
-
-	ast_poll_channel_del(caller, chan);
-
-	/*
-	 * We need to free all the deferred frames, but we only need to
-	 * queue the deferred frames if no hangup was received.
-	 */
-	ast_channel_lock(transferee);
-	transferee_hungup = (transferee_hungup || ast_check_hangup(transferee));
-	while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) {
-		if (!transferee_hungup) {
-			ast_queue_frame_head(transferee, f);
-		}
-		ast_frfree(f);
-	}
-	ast_channel_unlock(transferee);
-
-done:
-	ast_indicate(caller, -1);
-	if (chan && (ready || ast_channel_state(chan) == AST_STATE_UP)) {
-		state = AST_CONTROL_ANSWER;
-	} else if (chan) {
-		ast_hangup(chan);
-		chan = NULL;
-	}
-
-	tmp_cap = ast_format_cap_destroy(tmp_cap);
-
-	if (outstate)
-		*outstate = state;
-
-	return chan;
-}
-
-void ast_channel_log(char *title, struct ast_channel *chan);
-
-void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this is handy enough to justify keeping it in the source */
-{
-	ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
-	ast_log(LOG_NOTICE, "CHAN: name: %s;  appl: %s; data: %s; contxt: %s;  exten: %s; pri: %d;\n",
-		ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
-		ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
-	ast_log(LOG_NOTICE, "CHAN: acctcode: %s;  dialcontext: %s; amaflags: %x; maccontxt: %s;  macexten: %s; macpri: %d;\n",
-		ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
-		ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
-	ast_log(LOG_NOTICE, "CHAN: masq: %p;  masqr: %p; uniqueID: %s; linkedID:%s\n",
-		ast_channel_masq(chan), ast_channel_masqr(chan),
-		ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
-	if (ast_channel_masqr(chan)) {
-		ast_log(LOG_NOTICE, "CHAN: masquerading as: %s;  cdr: %p;\n",
-			ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
-	}
-
-	ast_log(LOG_NOTICE, "===== done ====\n");
-}
-
-static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
-{
-	const char *feature;
-
-	if (ast_strlen_zero(features)) {
-		return;
-	}
-
-	for (feature = features; *feature; feature++) {
-		struct ast_flags *party;
-
-		if (isupper(*feature)) {
-			party = &config->features_caller;
-		} else {
-			party = &config->features_callee;
-		}
-
-		switch (tolower(*feature)) {
-		case 't' :
-			ast_set_flag(party, AST_FEATURE_REDIRECT);
-			break;
-		case 'k' :
-			ast_set_flag(party, AST_FEATURE_PARKCALL);
-			break;
-		case 'h' :
-			ast_set_flag(party, AST_FEATURE_DISCONNECT);
-			break;
-		case 'w' :
-			ast_set_flag(party, AST_FEATURE_AUTOMON);
-			break;
-		case 'x' :
-			ast_set_flag(party, AST_FEATURE_AUTOMIXMON);
-			break;
-		default :
-			ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
-			break;
-		}
-	}
-}
-
-static void add_features_datastores(struct ast_channel *caller, struct ast_channel *callee, struct ast_bridge_config *config)
-{
-	if (add_features_datastore(caller, &config->features_caller, &config->features_callee)) {
-		/*
-		 * If we don't return here, then when we do a builtin_atxfer we
-		 * will copy the disconnect flags over from the atxfer to the
-		 * callee (Party C).
-		 */
-		return;
-	}
-
-	add_features_datastore(callee, &config->features_callee, &config->features_caller);
-}
-
-static void clear_dialed_interfaces(struct ast_channel *chan)
-{
-	struct ast_datastore *di_datastore;
-
-	ast_channel_lock(chan);
-	if ((di_datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL))) {
-		if (option_debug) {
-			ast_log(LOG_DEBUG, "Removing dialed interfaces datastore on %s since we're bridging\n", ast_channel_name(chan));
-		}
-		if (!ast_channel_datastore_remove(chan, di_datastore)) {
-			ast_datastore_free(di_datastore);
-		}
-	}
-	ast_channel_unlock(chan);
-}
-
-void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval start, const char *why)
-{
-	int dead;
-	long duration;
-
-	ast_channel_lock(chan);
-	dead = ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
-		|| (ast_channel_softhangup_internal_flag(chan)
-			& ~(AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE));
-	ast_channel_unlock(chan);
-	if (dead) {
-		/* Channel is a zombie or a real hangup. */
-		return;
-	}
-
-	duration = ast_tvdiff_ms(ast_tvnow(), start);
-	ast_senddigit_end(chan, digit, duration);
-	ast_log(LOG_DTMF, "DTMF end '%c' simulated on %s due to %s, duration %ld ms\n",
-		digit, ast_channel_name(chan), why, duration);
-}
-
-/*!
- * \internal
- * \brief Setup bridge builtin features.
- * \since 12.0.0
- *
- * \param features Bridge features to setup.
- * \param chan Get features from this channel.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
-{
-	struct ast_flags *flags;
-	char dtmf[FEATURE_MAX_LEN];
-	int res;
-
-	ast_channel_lock(chan);
-	flags = ast_bridge_features_ds_get(chan);
-	ast_channel_unlock(chan);
-	if (!flags) {
-		return 0;
-	}
-
-	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)) {
-/* 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)) {
-/* 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_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_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_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);
-		}
-	}
-	ast_unlock_call_features();
-
-#if 0	/* BUGBUG don't report errors untill all of the builtin features are supported. */
-	return res ? -1 : 0;
-#else
-	return 0;
-#endif
-}
-
-struct dtmf_hook_run_app {
-	/*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
-	unsigned int flags;
-	/*! Offset into app_name[] where the MOH class name starts.  (zero if no MOH) */
-	int moh_offset;
-	/*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
-	int app_args_offset;
-	/*! Application name to run. */
-	char app_name[0];
-};
-
-/*!
- * \internal
- * \brief Setup bridge dynamic features.
- * \since 12.0.0
- *
- * \param bridge The bridge that the channel is part of
- * \param bridge_channel Channel executing the feature
- * \param hook_pvt Private data passed in when the hook was created
- *
- * \retval 0 Keep the callback hook.
- * \retval -1 Remove the callback hook.
- */
-static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
-{
-	struct dtmf_hook_run_app *pvt = hook_pvt;
-	void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
-
-	if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
-		run_it = ast_bridge_channel_write_app;
-	} else {
-		run_it = ast_bridge_channel_run_app;
-	}
-
-/*
- * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
- *
- * This would replace DYNAMIC_PEERNAME which is redundant with
- * BRIDGEPEER anyway.  The value of DYNAMIC_WHO_TRIGGERED is
- * really useful in the case of a multi-party bridge.
- */
-	run_it(bridge_channel, pvt->app_name,
-		pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
-		pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Add a dynamic DTMF feature hook to the bridge features.
- * \since 12.0.0
- *
- * \param features Bridge features to setup.
- * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
- * \param dtmf DTMF trigger sequence.
- * \param app_name Dialplan application name to run.
- * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
- * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
-{
-	struct dtmf_hook_run_app *app_data;
-	size_t len_name = strlen(app_name) + 1;
-	size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
-	size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
-	size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
-
-	/* Fill in application run hook data. */
-	app_data = ast_malloc(len_data);
-	if (!app_data) {
-		return -1;
-	}
-	app_data->flags = flags;
-	app_data->app_args_offset = len_args ? len_name : 0;
-	app_data->moh_offset = len_moh ? len_name + len_args : 0;
-	strcpy(app_data->app_name, app_name);/* Safe */
-	if (len_args) {
-		strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
-	}
-	if (len_moh) {
-		strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
-	}
-
-	return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
-		app_data, ast_free_ptr, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
-}
-
-/*!
- * \internal
- * \brief Setup bridge dynamic features.
- * \since 12.0.0
- *
- * \param features Bridge features to setup.
- * \param chan Get features from this channel.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-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;
-
-	ast_channel_lock(chan);
-	feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
-	if (!ast_strlen_zero(feat)) {
-		dynamic_features = ast_strdupa(feat);
-	}
-	ast_channel_unlock(chan);
-	if (!dynamic_features) {
-		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);
-
-		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;
-}
-
-/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
-/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
-int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
-{
-	int res = 0;
-
-	/* Always pass through any DTMF digits. */
-	bridge_channel->features->dtmf_passthrough = 1;
-
-	res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
-	res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
-
-	return res;
-}
-
-static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
-{
-	if (config->end_sound) {
-		ast_string_field_set(limits, duration_sound, config->end_sound);
-	}
-
-	if (config->warning_sound) {
-		ast_string_field_set(limits, warning_sound, config->warning_sound);
-	}
-
-	if (config->start_sound) {
-		ast_string_field_set(limits, connect_sound, config->start_sound);
-	}
-
-	limits->frequency = config->warning_freq;
-	limits->warning = config->play_warning;
-}
-
-/*!
- * \internal brief Setup limit hook structures on calls that need limits
- *
- * \param config ast_bridge_config which provides the limit data
- * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
- * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
- */
-static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
-{
-	if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
-		bridge_config_set_limits_warning_values(config, caller_limits);
-	}
-
-	if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
-		bridge_config_set_limits_warning_values(config, callee_limits);
-	}
-
-	caller_limits->duration = config->timelimit;
-	callee_limits->duration = config->timelimit;
-}
-
-/*!
- * \internal
- * \brief Check if Monitor needs to be started on a channel.
- * \since 12.0.0
- *
- * \param chan The bridge considers this channel the caller.
- * \param peer The bridge considers this channel the callee.
- *
- * \return Nothing
- */
-static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *peer)
-{
-	const char *value;
-	const char *monitor_args = NULL;
-	struct ast_channel *monitor_chan = NULL;
-
-	ast_channel_lock(chan);
-	value = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR");
-	if (!ast_strlen_zero(value)) {
-		monitor_args = ast_strdupa(value);
-		monitor_chan = chan;
-	}
-	ast_channel_unlock(chan);
-	if (!monitor_chan) {
-		ast_channel_lock(peer);
-		value = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR");
-		if (!ast_strlen_zero(value)) {
-			monitor_args = ast_strdupa(value);
-			monitor_chan = peer;
-		}
-		ast_channel_unlock(peer);
-	}
-	if (monitor_chan) {
-		struct ast_app *monitor_app;
-
-		monitor_app = pbx_findapp("Monitor");
-		if (monitor_app) {
-			pbx_exec(monitor_chan, monitor_app, monitor_args);
-		}
-	}
-}
-
-/*!
- * \internal
- * \brief Send the peer channel on its way on bridge start failure.
- * \since 12.0.0
- *
- * \param chan Chan to put into autoservice.
- * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
- *
- * \return Nothing
- */
-static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
-{
-	if (ast_after_bridge_goto_setup(peer)
-		|| ast_pbx_start(peer)) {
-		ast_autoservice_chan_hangup_peer(chan, peer);
-	}
-}
-
-static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config,
-		struct ast_bridge_features *chan_features, struct ast_bridge_features *peer_features)
-{
-	int res;
-
-/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
-	pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
-	pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
-
-/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
-	/* Clear any BLINDTRANSFER since the transfer has completed. */
-	pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
-	pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
-
-	set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
-	add_features_datastores(chan, peer, config);
-
-	/*
-	 * This is an interesting case.  One example is if a ringing
-	 * channel gets redirected to an extension that picks up a
-	 * parked call.  This will make sure that the call taken out of
-	 * parking gets told that the channel it just got bridged to is
-	 * still ringing.
-	 */
-	if (ast_channel_state(chan) == AST_STATE_RINGING
-		&& ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
-		ast_indicate(peer, AST_CONTROL_RINGING);
-	}
-
-	bridge_check_monitor(chan, peer);
-
-	set_config_flags(chan, config);
-
-	/* Answer if need be */
-	if (ast_channel_state(chan) != AST_STATE_UP) {
-		if (ast_raw_answer(chan, 1)) {
-			return -1;
-		}
-	}
-
-#ifdef FOR_DEBUG
-	/* show the two channels and cdrs involved in the bridge for debug & devel purposes */
-	ast_channel_log("Pre-bridge CHAN Channel info", chan);
-	ast_channel_log("Pre-bridge PEER Channel info", peer);
-#endif
-	/* two channels are being marked as linked here */
-	ast_channel_set_linkgroup(chan, peer);
-
-	/*
-	 * If we are bridging a call, stop worrying about forwarding
-	 * loops.  We presume that if a call is being bridged, that the
-	 * humans in charge know what they're doing.  If they don't,
-	 * well, what can we do about that?
-	 */
-	clear_dialed_interfaces(chan);
-	clear_dialed_interfaces(peer);
-
-	res = 0;
-	ast_channel_lock(chan);
-	res |= ast_bridge_features_ds_set(chan, &config->features_caller);
-	ast_channel_unlock(chan);
-	ast_channel_lock(peer);
-	res |= ast_bridge_features_ds_set(peer, &config->features_callee);
-	ast_channel_unlock(peer);
-
-	if (res) {
-		return -1;
-	}
-
-	if (config->timelimit) {
-		struct ast_bridge_features_limits call_duration_limits_chan;
-		struct ast_bridge_features_limits call_duration_limits_peer;
-		int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
-
-		if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
-			ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
-
-			return -1;
-		}
-
-		if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
-			ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
-			ast_bridge_features_limits_destroy(&call_duration_limits_chan);
-
-			return -1;
-		}
-
-		bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
-
-		if (ast_bridge_features_set_limits(chan_features, &call_duration_limits_chan, 0)) {
-			abandon_call = 1;
-		}
-		if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
-			abandon_call = 1;
-		}
-
-		/* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
-		ast_bridge_features_limits_destroy(&call_duration_limits_chan);
-		ast_bridge_features_limits_destroy(&call_duration_limits_peer);
-
-		if (abandon_call) {
-			ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
-			return -1;
-		}
-	}
-
-	return 0;
-}
-
-/*!
- * \brief bridge the call and set CDR
- *
- * \param chan The bridge considers this channel the caller.
- * \param peer The bridge considers this channel the callee.
- * \param config Configuration for this bridge.
- *
- * Set start time, check for two channels,check if monitor on
- * check for feature activation, create new CDR
- * \retval res on success.
- * \retval -1 on failure to bridge.
- */
-int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
-{
-	int res;
-	struct ast_bridge *bridge;
-	struct ast_bridge_features chan_features;
-	struct ast_bridge_features *peer_features;
-
-	/* Setup features. */
-	res = ast_bridge_features_init(&chan_features);
-	peer_features = ast_bridge_features_new();
-	if (res || !peer_features) {
-		ast_bridge_features_destroy(peer_features);
-		ast_bridge_features_cleanup(&chan_features);
-		bridge_failed_peer_goto(chan, peer);
-		return -1;
-	}
-
-	if (pre_bridge_setup(chan, peer, config, &chan_features, peer_features)) {
-		ast_bridge_features_destroy(peer_features);
-		ast_bridge_features_cleanup(&chan_features);
-		bridge_failed_peer_goto(chan, peer);
-		return -1;
-	}
-
-	/* Create bridge */
-	bridge = ast_bridge_basic_new();
-	if (!bridge) {
-		ast_bridge_features_destroy(peer_features);
-		ast_bridge_features_cleanup(&chan_features);
-		bridge_failed_peer_goto(chan, peer);
-		return -1;
-	}
-
-	/* Put peer into the bridge */
-	if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
-		ast_bridge_destroy(bridge);
-		ast_bridge_features_cleanup(&chan_features);
-		bridge_failed_peer_goto(chan, peer);
-		return -1;
-	}
-
-	/* Join bridge */
-	ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
-
-	/*
-	 * If the bridge was broken for a hangup that isn't real, then
-	 * don't run the h extension, because the channel isn't really
-	 * hung up.  This should really only happen with
-	 * AST_SOFTHANGUP_ASYNCGOTO.
-	 */
-	res = -1;
-	ast_channel_lock(chan);
-	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
-		res = 0;
-	}
-	ast_channel_unlock(chan);
-
-	ast_bridge_features_cleanup(&chan_features);
-
-/* BUGBUG this is used by Dial and FollowMe for CDR information.  By Queue for Queue stats like CDRs. */
-	if (res && config->end_bridge_callback) {
-		config->end_bridge_callback(config->end_bridge_callback_data);
-	}
-
-	return res;
-}
-
-/*! \brief Output parking event to manager */
-static void post_manager_event(const char *s, struct parkeduser *pu)
-{
-	manager_event(EVENT_FLAG_CALL, s,
-		"Exten: %s\r\n"
-		"Channel: %s\r\n"
-		"Parkinglot: %s\r\n"
-		"CallerIDNum: %s\r\n"
-		"CallerIDName: %s\r\n"
-		"ConnectedLineNum: %s\r\n"
-		"ConnectedLineName: %s\r\n"
-		"UniqueID: %s\r\n",
-		pu->parkingexten,
-		ast_channel_name(pu->chan),
-		pu->parkinglot->name,
-		S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
-		S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
-		S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
-		S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
-		ast_channel_uniqueid(pu->chan)
-		);
-}
-
-static char *callback_dialoptions(struct ast_flags *features_callee, struct ast_flags *features_caller, char *options, size_t len)
-{
-	int i = 0;
-	enum {
-		OPT_CALLEE_REDIRECT   = 't',
-		OPT_CALLER_REDIRECT   = 'T',
-		OPT_CALLEE_AUTOMON    = 'w',
-		OPT_CALLER_AUTOMON    = 'W',
-		OPT_CALLEE_DISCONNECT = 'h',
-		OPT_CALLER_DISCONNECT = 'H',
-		OPT_CALLEE_PARKCALL   = 'k',
-		OPT_CALLER_PARKCALL   = 'K',
-	};
-
-	memset(options, 0, len);
-	if (ast_test_flag(features_caller, AST_FEATURE_REDIRECT) && i < len) {
-		options[i++] = OPT_CALLER_REDIRECT;
-	}
-	if (ast_test_flag(features_caller, AST_FEATURE_AUTOMON) && i < len) {
-		options[i++] = OPT_CALLER_AUTOMON;
-	}
-	if (ast_test_flag(features_caller, AST_FEATURE_DISCONNECT) && i < len) {
-		options[i++] = OPT_CALLER_DISCONNECT;
-	}
-	if (ast_test_flag(features_caller, AST_FEATURE_PARKCALL) && i < len) {
-		options[i++] = OPT_CALLER_PARKCALL;
-	}
-
-	if (ast_test_flag(features_callee, AST_FEATURE_REDIRECT) && i < len) {
-		options[i++] = OPT_CALLEE_REDIRECT;
-	}
-	if (ast_test_flag(features_callee, AST_FEATURE_AUTOMON) && i < len) {
-		options[i++] = OPT_CALLEE_AUTOMON;
-	}
-	if (ast_test_flag(features_callee, AST_FEATURE_DISCONNECT) && i < len) {
-		options[i++] = OPT_CALLEE_DISCONNECT;
-	}
-	if (ast_test_flag(features_callee, AST_FEATURE_PARKCALL) && i < len) {
-		options[i++] = OPT_CALLEE_PARKCALL;
-	}
-
-	return options;
-}
-
-/*!
- * \internal
- * \brief Run management on a parked call.
- *
- * \note The parkinglot parkings list is locked on entry.
- *
- * \retval TRUE if the parking completed.
- */
-static int manage_parked_call(struct parkeduser *pu, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
-{
-	struct ast_channel *chan = pu->chan;	/* shorthand */
-	int tms;        /* timeout for this item */
-	int x;          /* fd index in channel */
-
-	tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
-	if (tms > pu->parkingtime) {
-		/*
-		 * Call has been parked too long.
-		 * Stop entertaining the caller.
-		 */
-		switch (pu->hold_method) {
-		case AST_CONTROL_HOLD:
-			ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
-			break;
-		case AST_CONTROL_RINGING:
-			ast_indicate(pu->chan, -1);
-			break;
-		default:
+		switch (tolower(*feature)) {
+		case 't' :
+			ast_set_flag(party, AST_FEATURE_REDIRECT);
+			break;
+		case 'k' :
+			ast_set_flag(party, AST_FEATURE_PARKCALL);
+			break;
+		case 'h' :
+			ast_set_flag(party, AST_FEATURE_DISCONNECT);
+			break;
+		case 'w' :
+			ast_set_flag(party, AST_FEATURE_AUTOMON);
+			break;
+		case 'x' :
+			ast_set_flag(party, AST_FEATURE_AUTOMIXMON);
+			break;
+		default :
+			ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
 			break;
-		}
-		pu->hold_method = 0;
-
-		/* Get chan, exten from derived kludge */
-		if (pu->peername[0]) {
-			char *peername;
-			char *dash;
-			char *peername_flat; /* using something like DAHDI/52 for an extension name is NOT a good idea */
-			char parkingslot[AST_MAX_EXTENSION]; /* buffer for parkinglot slot number */
-			int i;
-
-			peername = ast_strdupa(pu->peername);
-			dash = strrchr(peername, '-');
-			if (dash) {
-				*dash = '\0';
-			}
-
-			peername_flat = ast_strdupa(peername);
-			for (i = 0; peername_flat[i]; i++) {
-				if (peername_flat[i] == '/') {
-					peername_flat[i] = '_';
-				}
-			}
-
-			if (!ast_context_find_or_create(NULL, NULL, parking_con_dial, registrar)) {
-				ast_log(LOG_ERROR,
-					"Parking dial context '%s' does not exist and unable to create\n",
-					parking_con_dial);
-			} else {
-				char returnexten[AST_MAX_EXTENSION];
-				char comebackdialtime[AST_MAX_EXTENSION];
-				struct ast_datastore *features_datastore;
-				struct ast_dial_features *dialfeatures;
-
-				if (!strncmp(peername, "Parked/", 7)) {
-					peername += 7;
-				}
-
-				ast_channel_lock(chan);
-				features_datastore = ast_channel_datastore_find(chan, &dial_features_info,
-					NULL);
-				if (features_datastore && (dialfeatures = features_datastore->data)) {
-					char buf[MAX_DIAL_FEATURE_OPTIONS] = {0,};
-
-					snprintf(returnexten, sizeof(returnexten), "%s,%u,%s", peername,
-						pu->parkinglot->cfg.comebackdialtime,
-						callback_dialoptions(&dialfeatures->peer_features,
-							&dialfeatures->my_features, buf, sizeof(buf)));
-				} else { /* Existing default */
-					ast_log(LOG_NOTICE, "Dial features not found on %s, using default!\n",
-						ast_channel_name(chan));
-					snprintf(returnexten, sizeof(returnexten), "%s,%u,t", peername,
-						pu->parkinglot->cfg.comebackdialtime);
-				}
-				ast_channel_unlock(chan);
-
-				snprintf(comebackdialtime, sizeof(comebackdialtime), "%u",
-						pu->parkinglot->cfg.comebackdialtime);
-				pbx_builtin_setvar_helper(chan, "COMEBACKDIALTIME", comebackdialtime);
-
-				pbx_builtin_setvar_helper(chan, "PARKER", peername);
-
-				if (ast_add_extension(parking_con_dial, 1, peername_flat, 1, NULL, NULL,
-					"Dial", ast_strdup(returnexten), ast_free_ptr, registrar)) {
-					ast_log(LOG_ERROR,
-						"Could not create parking return dial exten: %s@%s\n",
-						peername_flat, parking_con_dial);
-				}
-			}
-
-			snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
-			pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parkingslot);
-			pbx_builtin_setvar_helper(chan, "PARKEDLOT", pu->parkinglot->name);
-
-			if (pu->options_specified) {
-				/*
-				 * Park() was called with overriding return arguments, respect
-				 * those arguments.
-				 */
-				set_c_e_p(chan, pu->context, pu->exten, pu->priority);
-			} else if (pu->parkinglot->cfg.comebacktoorigin) {
-				set_c_e_p(chan, parking_con_dial, peername_flat, 1);
-			} else {
-				/* Handle fallback when extensions don't exist here since that logic was removed from pbx */
-				if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1, NULL)) {
-					set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1);
-				} else if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, "s", 1, NULL)) {
-					ast_verb(2, "Can not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
-						pu->parkinglot->cfg.comebackcontext, peername_flat, pu->parkinglot->cfg.comebackcontext);
-					set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, "s", 1);
-				} else {
-					ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
-						ast_channel_name(chan),
-						pu->parkinglot->cfg.comebackcontext, peername_flat,
-						pu->parkinglot->cfg.comebackcontext);
-					set_c_e_p(chan, "default", "s", 1);
-				}
-			}
-		} else {
-			/*
-			 * They've been waiting too long, send them back to where they
-			 * came.  Theoretically they should have their original
-			 * extensions and such, but we copy to be on the safe side.
-			 */
-			set_c_e_p(chan, pu->context, pu->exten, pu->priority);
-		}
-		post_manager_event("ParkedCallTimeOut", pu);
-		ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallTimeOut", NULL);
-
-		ast_verb(2, "Timeout for %s parked on %d (%s). Returning to %s,%s,%d\n",
-			ast_channel_name(pu->chan), pu->parkingnum, pu->parkinglot->name, ast_channel_context(pu->chan),
-			ast_channel_exten(pu->chan), ast_channel_priority(pu->chan));
-
-		/* Start up the PBX, or hang them up */
-		if (ast_pbx_start(chan))  {
-			ast_log(LOG_WARNING,
-				"Unable to restart the PBX for user on '%s', hanging them up...\n",
-				ast_channel_name(pu->chan));
-			ast_hangup(chan);
-		}
-
-		/* And take them out of the parking lot */
-		return 1;
-	}
-
-	/* still within parking time, process descriptors */
-	if (pfds) {
-		for (x = 0; x < AST_MAX_FDS; x++) {
-			struct ast_frame *f;
-			int y;
-
-			if (!ast_channel_fd_isset(chan, x)) {
-				continue;	/* nothing on this descriptor */
-			}
-
-			for (y = 0; y < nfds; y++) {
-				if (pfds[y].fd == ast_channel_fd(chan, x)) {
-					/* Found poll record! */
-					break;
-				}
-			}
-			if (y == nfds) {
-				/* Not found */
-				continue;
-			}
-
-			if (!(pfds[y].revents & (POLLIN | POLLERR | POLLPRI))) {
-				/* Next x */
-				continue;
-			}
-
-			if (pfds[y].revents & POLLPRI) {
-				ast_set_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
-			} else {
-				ast_clear_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
-			}
-			ast_channel_fdno_set(chan, x);
-
-			/* See if they need servicing */
-			f = ast_read(pu->chan);
-			/* Hangup? */
-			if (!f || (f->frametype == AST_FRAME_CONTROL
-				&& f->subclass.integer == AST_CONTROL_HANGUP)) {
-				if (f) {
-					ast_frfree(f);
-				}
-				post_manager_event("ParkedCallGiveUp", pu);
-				ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallGiveUp",
-					NULL);
-
-				/* There's a problem, hang them up */
-				ast_verb(2, "%s got tired of being parked\n", ast_channel_name(chan));
-				ast_hangup(chan);
-
-				/* And take them out of the parking lot */
-				return 1;
-			} else {
-				/* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
-				ast_frfree(f);
-				if (pu->hold_method == AST_CONTROL_HOLD
-					&& pu->moh_trys < 3
-					&& !ast_channel_generatordata(chan)) {
-					ast_debug(1,
-						"MOH on parked call stopped by outside source.  Restarting on channel %s.\n",
-						ast_channel_name(chan));
-					ast_indicate_data(chan, AST_CONTROL_HOLD,
-						S_OR(pu->parkinglot->cfg.mohclass, NULL),
-						(!ast_strlen_zero(pu->parkinglot->cfg.mohclass)
-							? strlen(pu->parkinglot->cfg.mohclass) + 1 : 0));
-					pu->moh_trys++;
-				}
-				break;
-			}
-		} /* End for */
-	}
-
-	/* mark fds for next round */
-	for (x = 0; x < AST_MAX_FDS; x++) {
-		if (ast_channel_fd_isset(chan, x)) {
-			void *tmp = ast_realloc(*new_pfds,
-				(*new_nfds + 1) * sizeof(struct pollfd));
-
-			if (!tmp) {
-				continue;
-			}
-			*new_pfds = tmp;
-			(*new_pfds)[*new_nfds].fd = ast_channel_fd(chan, x);
-			(*new_pfds)[*new_nfds].events = POLLIN | POLLERR | POLLPRI;
-			(*new_pfds)[*new_nfds].revents = 0;
-			(*new_nfds)++;
-		}
-	}
-	/* Keep track of our shortest wait */
-	if (tms < *ms || *ms < 0) {
-		*ms = tms;
-	}
-
-	/* Stay in the parking lot. */
-	return 0;
-}
-
-/*! \brief Run management on parkinglots, called once per parkinglot */
-static void manage_parkinglot(struct ast_parkinglot *curlot, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
-{
-	struct parkeduser *pu;
-	struct ast_context *con;
-
-	/* Lock parkings list */
-	AST_LIST_LOCK(&curlot->parkings);
-	AST_LIST_TRAVERSE_SAFE_BEGIN(&curlot->parkings, pu, list) {
-		if (pu->notquiteyet) { /* Pretend this one isn't here yet */
-			continue;
-		}
-		if (manage_parked_call(pu, pfds, nfds, new_pfds, new_nfds, ms)) {
-			/* Parking is complete for this call so remove it from the parking lot. */
-			con = ast_context_find(pu->parkinglot->cfg.parking_con);
-			if (con) {
-				if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
-					ast_log(LOG_WARNING,
-						"Whoa, failed to remove the parking extension %s@%s!\n",
-						pu->parkingexten, pu->parkinglot->cfg.parking_con);
-				}
-				notify_metermaids(pu->parkingexten, pu->parkinglot->cfg.parking_con,
-					AST_DEVICE_NOT_INUSE);
-			} else {
-				ast_log(LOG_WARNING,
-					"Whoa, parking lot '%s' context '%s' does not exist.\n",
-					pu->parkinglot->name, pu->parkinglot->cfg.parking_con);
-			}
-			AST_LIST_REMOVE_CURRENT(list);
-			parkinglot_unref(pu->parkinglot);
-			ast_free(pu);
 		}
 	}
-	AST_LIST_TRAVERSE_SAFE_END;
-	AST_LIST_UNLOCK(&curlot->parkings);
 }
 
-/*!
- * \brief Take care of parked calls and unpark them if needed
- * \param ignore unused var.
- *
- * Start inf loop, lock parking lot, check if any parked channels have gone above timeout
- * if so, remove channel from parking lot and return it to the extension that parked it.
- * Check if parked channel decided to hangup, wait until next FD via select().
- */
-static void *do_parking_thread(void *ignore)
+static void add_features_datastores(struct ast_channel *caller, struct ast_channel *callee, struct ast_bridge_config *config)
 {
-	struct pollfd *pfds = NULL, *new_pfds = NULL;
-	int nfds = 0, new_nfds = 0;
-
-	for (;;) {
-		struct ao2_iterator iter;
-		struct ast_parkinglot *curlot;
-		int ms = -1;	/* poll2 timeout, uninitialized */
-
-		iter = ao2_iterator_init(parkinglots, 0);
-		while ((curlot = ao2_iterator_next(&iter))) {
-			manage_parkinglot(curlot, pfds, nfds, &new_pfds, &new_nfds, &ms);
-			ao2_ref(curlot, -1);
-		}
-		ao2_iterator_destroy(&iter);
-
-		/* Recycle */
-		ast_free(pfds);
-		pfds = new_pfds;
-		nfds = new_nfds;
-		new_pfds = NULL;
-		new_nfds = 0;
-
-		/* Wait for something to happen */
-		ast_poll(pfds, nfds, ms);
-		pthread_testcancel();
+	if (add_features_datastore(caller, &config->features_caller, &config->features_callee)) {
+		/*
+		 * If we don't return here, then when we do a builtin_atxfer we
+		 * will copy the disconnect flags over from the atxfer to the
+		 * callee (Party C).
+		 */
+		return;
 	}
-	/* If this WERE reached, we'd need to free(pfds) */
-	return NULL;	/* Never reached */
+
+	add_features_datastore(callee, &config->features_callee, &config->features_caller);
 }
 
-/*! \brief Find parkinglot by name */
-static struct ast_parkinglot *find_parkinglot(const char *name)
+static void clear_dialed_interfaces(struct ast_channel *chan)
 {
-	struct ast_parkinglot *parkinglot;
-
-	if (ast_strlen_zero(name)) {
-		return NULL;
-	}
+	struct ast_datastore *di_datastore;
 
-	parkinglot = ao2_find(parkinglots, (void *) name, 0);
-	if (parkinglot) {
-		ast_debug(1, "Found Parking lot: %s\n", parkinglot->name);
+	ast_channel_lock(chan);
+	if ((di_datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL))) {
+		if (option_debug) {
+			ast_log(LOG_DEBUG, "Removing dialed interfaces datastore on %s since we're bridging\n", ast_channel_name(chan));
+		}
+		if (!ast_channel_datastore_remove(chan, di_datastore)) {
+			ast_datastore_free(di_datastore);
+		}
 	}
-
-	return parkinglot;
+	ast_channel_unlock(chan);
 }
 
-/*! \brief Copy parkinglot and store it with new name */
-static struct ast_parkinglot *copy_parkinglot(const char *name, const struct ast_parkinglot *parkinglot)
+void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval start, const char *why)
 {
-	struct ast_parkinglot *copylot;
-
-	if ((copylot = find_parkinglot(name))) { /* Parkinglot with that name already exists */
-		ao2_ref(copylot, -1);
-		return NULL;
-	}
+	int dead;
+	long duration;
 
-	copylot = create_parkinglot(name);
-	if (!copylot) {
-		return NULL;
+	ast_channel_lock(chan);
+	dead = ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
+		|| (ast_channel_softhangup_internal_flag(chan)
+			& ~(AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE));
+	ast_channel_unlock(chan);
+	if (dead) {
+		/* Channel is a zombie or a real hangup. */
+		return;
 	}
 
-	ast_debug(1, "Building parking lot %s\n", name);
-
-	/* Copy the source parking lot configuration. */
-	copylot->cfg = parkinglot->cfg;
-
-	return copylot;
+	duration = ast_tvdiff_ms(ast_tvnow(), start);
+	ast_senddigit_end(chan, digit, duration);
+	ast_log(LOG_DTMF, "DTMF end '%c' simulated on %s due to %s, duration %ld ms\n",
+		digit, ast_channel_name(chan), why, duration);
 }
 
-AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
-	AST_APP_OPTION('r', AST_PARK_OPT_RINGING),
-	AST_APP_OPTION('R', AST_PARK_OPT_RANDOMIZE),
-	AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
-END_OPTIONS );
-
 /*!
- * \brief Unreference parkinglot object.
+ * \internal
+ * \brief Setup bridge builtin features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
  */
-static void parkinglot_unref(struct ast_parkinglot *parkinglot)
-{
-	ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name,
-		ao2_ref(parkinglot, 0) - 1);
-	ao2_ref(parkinglot, -1);
-}
-
-static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot)
-{
-	int refcount;
-
-	refcount = ao2_ref(parkinglot, +1);
-	ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name, refcount + 1);
-	return parkinglot;
-}
-
-/*! \brief Destroy a parking lot */
-static void parkinglot_destroy(void *obj)
-{
-	struct ast_parkinglot *doomed = obj;
-
-	/*
-	 * No need to destroy parked calls here because any parked call
-	 * holds a parking lot reference.  Therefore the parkings list
-	 * must be empty.
-	 */
-	ast_assert(AST_LIST_EMPTY(&doomed->parkings));
-	AST_LIST_HEAD_DESTROY(&doomed->parkings);
-}
-
-/*! \brief Allocate parking lot structure */
-static struct ast_parkinglot *create_parkinglot(const char *name)
+static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
 {
-	struct ast_parkinglot *newlot;
+	struct ast_flags *flags;
+	char dtmf[AST_FEATURE_MAX_LEN];
+	int res;
 
-	if (ast_strlen_zero(name)) { /* No name specified */
-		return NULL;
+	ast_channel_lock(chan);
+	flags = ast_bridge_features_ds_get(chan);
+	ast_channel_unlock(chan);
+	if (!flags) {
+		return 0;
 	}
 
-	newlot = ao2_alloc(sizeof(*newlot), parkinglot_destroy);
-	if (!newlot)
-		return NULL;
-
-	ast_copy_string(newlot->name, name, sizeof(newlot->name));
-	newlot->cfg.is_invalid = 1;/* No config is set yet. */
-	AST_LIST_HEAD_INIT(&newlot->parkings);
-
-	return newlot;
-}
-
-/*!
- * \brief Add parking hints for all defined parking spaces.
- * \param context Dialplan context to add the hints.
- * \param start Starting space in parkinglot.
- * \param stop Ending space in parkinglot.
- */
-static void park_add_hints(const char *context, int start, int stop)
-{
-	int numext;
-	char device[AST_MAX_EXTENSION];
-	char exten[10];
-
-	for (numext = start; numext <= stop; numext++) {
-		snprintf(exten, sizeof(exten), "%d", numext);
-		snprintf(device, sizeof(device), "park:%s@%s", exten, context);
-		ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+	res = 0;
+	if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
+		/* Add atxfer and blind transfer. */
+		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);
+		}
+		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)) &&
+			!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)) &&
+			!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)) &&
+			!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)) &&
+			!ast_strlen_zero(dtmf)) {
+		res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
+				NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
 	}
-}
 
-/*! Default configuration for default parking lot. */
-static const struct parkinglot_cfg parkinglot_cfg_default_default = {
-	.mohclass = "default",
-	.parkext = DEFAULT_PARK_EXTENSION,
-	.parking_con = "parkedcalls",
-	.parking_start = 701,
-	.parking_stop = 750,
-	.parkingtime = DEFAULT_PARK_TIME,
-	.comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
-	.comebackcontext = DEFAULT_COMEBACK_CONTEXT,
-	.comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
-};
+#if 0	/* BUGBUG don't report errors untill all of the builtin features are supported. */
+	return res ? -1 : 0;
+#else
+	return 0;
+#endif
+}
 
-/*! Default configuration for normal parking lots. */
-static const struct parkinglot_cfg parkinglot_cfg_default = {
-	.parkext = DEFAULT_PARK_EXTENSION,
-	.parkingtime = DEFAULT_PARK_TIME,
-	.comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
-	.comebackcontext = DEFAULT_COMEBACK_CONTEXT,
-	.comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
+struct dtmf_hook_run_app {
+	/*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
+	unsigned int flags;
+	/*! Offset into app_name[] where the MOH class name starts.  (zero if no MOH) */
+	int moh_offset;
+	/*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+	int app_args_offset;
+	/*! Application name to run. */
+	char app_name[0];
 };
 
 /*!
  * \internal
- * \brief Set parking lot feature flag configuration value.
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
  *
- * \param pl_name Parking lot name for diagnostic messages.
- * \param param Parameter value to set.
- * \param var Current configuration variable item.
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
  *
- * \return Nothing
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
  */
-static void parkinglot_feature_flag_cfg(const char *pl_name, int *param, struct ast_variable *var)
+static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
-	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;
+	struct dtmf_hook_run_app *pvt = hook_pvt;
+	void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+	if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
+		run_it = ast_bridge_channel_write_app;
+	} else {
+		run_it = ast_bridge_channel_run_app;
 	}
+
+/*
+ * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
+ *
+ * This would replace DYNAMIC_PEERNAME which is redundant with
+ * BRIDGEPEER anyway.  The value of DYNAMIC_WHO_TRIGGERED is
+ * really useful in the case of a multi-party bridge.
+ */
+	run_it(bridge_channel, pvt->app_name,
+		pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
+		pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
+	return 0;
 }
 
 /*!
  * \internal
- * \brief Read parking lot configuration.
+ * \brief Add a dynamic DTMF feature hook to the bridge features.
+ * \since 12.0.0
  *
- * \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.
+ * \param features Bridge features to setup.
+ * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
+ * \param dtmf DTMF trigger sequence.
+ * \param app_name Dialplan application name to run.
+ * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
+ * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
  *
  * \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)
+static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
 {
-	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;
-	}
+	struct dtmf_hook_run_app *app_data;
+	size_t len_name = strlen(app_name) + 1;
+	size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+	size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
+	size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
 
-	/* 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;
+	/* Fill in application run hook data. */
+	app_data = ast_malloc(len_data);
+	if (!app_data) {
+		return -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;
+	app_data->flags = flags;
+	app_data->app_args_offset = len_args ? len_name : 0;
+	app_data->moh_offset = len_moh ? len_name + len_args : 0;
+	strcpy(app_data->app_name, app_name);/* Safe */
+	if (len_args) {
+		strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
 	}
-	if (error) {
-		cfg->is_invalid = 1;
+	if (len_moh) {
+		strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
 	}
 
-	return error;
+	return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
+		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 Activate the given parkinglot.
- *
- * \param parkinglot Parking lot to activate.
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
  *
- * \details
- * Insert into the dialplan the context, parking lot access
- * extension, and optional dialplan hints.
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
  *
  * \retval 0 on success.
  * \retval -1 on error.
  */
-static int parkinglot_activate(struct ast_parkinglot *parkinglot)
+static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
 {
-	int disabled = 0;
-	char app_data[5 + AST_MAX_CONTEXT];
+	RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
+	int res = 0;
 
-	/* Create Park option list.  Must match with struct park_app_args options. */
-	if (parkinglot->cfg.parkext_exclusive) {
-		/* Specify the parking lot this parking extension parks calls. */
-		snprintf(app_data, sizeof(app_data), ",,,,,%s", parkinglot->name);
-	} else {
-		/* The dialplan must specify which parking lot to use. */
-		app_data[0] = '\0';
+	ast_channel_lock(chan);
+	applicationmap = ast_get_chan_applicationmap(chan);
+	ast_channel_unlock(chan);
+	if (!applicationmap) {
+		return 0;
 	}
 
-	/* Create context */
-	if (!ast_context_find_or_create(NULL, NULL, parkinglot->cfg.parking_con, registrar)) {
-		ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n",
-			parkinglot->cfg.parking_con);
-		disabled = 1;
+	ao2_callback_data(applicationmap, 0, setup_dynamic_feature, features, &res);
 
-	/* Add a parking extension into the context */
-	} else if (ast_add_extension(parkinglot->cfg.parking_con, 1, parkinglot->cfg.parkext,
-		1, NULL, NULL, parkcall, ast_strdup(app_data), ast_free_ptr, registrar)) {
-		ast_log(LOG_ERROR, "Could not create parking lot %s access exten %s@%s\n",
-			parkinglot->name, parkinglot->cfg.parkext, parkinglot->cfg.parking_con);
-		disabled = 1;
-	} else {
-		/* Add parking hints */
-		if (parkinglot->cfg.parkaddhints) {
-			park_add_hints(parkinglot->cfg.parking_con, parkinglot->cfg.parking_start,
-				parkinglot->cfg.parking_stop);
-		}
+	return res;
+}
 
-		/*
-		 * XXX Not sure why we should need to notify the metermaids for
-		 * this exten.  It was originally done for the default parking
-		 * lot entry exten only but should be done for all entry extens
-		 * if we do it for one.
-		 */
-		/* Notify metermaids about parking lot entry exten state. */
-		notify_metermaids(parkinglot->cfg.parkext, parkinglot->cfg.parking_con,
-			AST_DEVICE_INUSE);
-	}
+/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
+/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
+{
+	int res = 0;
 
-	parkinglot->disabled = disabled;
-	return disabled ? -1 : 0;
+	/* Always pass through any DTMF digits. */
+	bridge_channel->features->dtmf_passthrough = 1;
+
+	res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
+	res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
+
+	return res;
 }
 
-/*! \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)
+static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
 {
-	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;
+	if (config->end_sound) {
+		ast_string_field_set(limits, duration_sound, config->end_sound);
 	}
-	new_cfg = *cfg_defaults;
-
-	ast_debug(1, "Building parking lot %s\n", parkinglot->name);
 
-	ao2_lock(parkinglot);
+	if (config->warning_sound) {
+		ast_string_field_set(limits, warning_sound, config->warning_sound);
+	}
 
-	/* 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;
+	if (config->start_sound) {
+		ast_string_field_set(limits, connect_sound, config->start_sound);
 	}
-	parkinglot->the_mark = 0;
 
-	ao2_unlock(parkinglot);
+	limits->frequency = config->warning_freq;
+	limits->warning = config->play_warning;
+}
 
-	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;
+/*!
+ * \internal brief Setup limit hook structures on calls that need limits
+ *
+ * \param config ast_bridge_config which provides the limit data
+ * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
+ * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
+ */
+static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
+{
+	if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
+		bridge_config_set_limits_warning_values(config, caller_limits);
 	}
 
-	/* Move it into the list, if it wasn't already there */
-	if (!oldparkinglot) {
-		ao2_link(parkinglots, parkinglot);
+	if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
+		bridge_config_set_limits_warning_values(config, callee_limits);
 	}
-	parkinglot_unref(parkinglot);
 
-	return parkinglot;
+	caller_limits->duration = config->timelimit;
+	callee_limits->duration = config->timelimit;
 }
 
 /*!
  * \internal
- * \brief Process an applicationmap section config line.
+ * \brief Check if Monitor needs to be started on a channel.
+ * \since 12.0.0
  *
- * \param var Config variable line.
+ * \param chan The bridge considers this channel the caller.
+ * \param peer The bridge considers this channel the callee.
  *
  * \return Nothing
  */
-static void process_applicationmap_line(struct ast_variable *var)
+static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *peer)
 {
-	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);
-	);
+	const char *value;
+	const char *monitor_args = NULL;
+	struct ast_channel *monitor_chan = NULL;
 
-	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';
+	ast_channel_lock(chan);
+	value = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR");
+	if (!ast_strlen_zero(value)) {
+		monitor_args = ast_strdupa(value);
+		monitor_chan = chan;
+	}
+	ast_channel_unlock(chan);
+	if (!monitor_chan) {
+		ast_channel_lock(peer);
+		value = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR");
+		if (!ast_strlen_zero(value)) {
+			monitor_args = ast_strdupa(value);
+			monitor_chan = peer;
 		}
+		ast_channel_unlock(peer);
 	}
+	if (monitor_chan) {
+		struct ast_app *monitor_app;
 
-	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;
+		monitor_app = pbx_findapp("Monitor");
+		if (monitor_app) {
+			pbx_exec(monitor_chan, monitor_app, monitor_args);
+		}
 	}
+}
 
-	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;
+/*!
+ * \internal
+ * \brief Send the peer channel on its way on bridge start failure.
+ * \since 12.0.0
+ *
+ * \param chan Chan to put into autoservice.
+ * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
+ *
+ * \return Nothing
+ */
+static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
+{
+	if (ast_after_bridge_goto_setup(peer)
+		|| ast_pbx_start(peer)) {
+		ast_autoservice_chan_hangup_peer(chan, peer);
 	}
-	ast_unlock_call_features();
+}
 
-	if (!(feature = ast_calloc(1, sizeof(*feature)))) {
-		return;
+static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config,
+		struct ast_bridge_features *chan_features, struct ast_bridge_features *peer_features)
+{
+	int res;
+
+/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
+	pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
+	pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
+
+/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
+	/* Clear any BLINDTRANSFER since the transfer has completed. */
+	pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
+	pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
+
+	set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
+	add_features_datastores(chan, peer, config);
+
+	/*
+	 * This is an interesting case.  One example is if a ringing
+	 * channel gets redirected to an extension that picks up a
+	 * parked call.  This will make sure that the call taken out of
+	 * parking gets told that the channel it just got bridged to is
+	 * still ringing.
+	 */
+	if (ast_channel_state(chan) == AST_STATE_RINGING
+		&& ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
+		ast_indicate(peer, AST_CONTROL_RINGING);
 	}
 
-	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);
+	bridge_check_monitor(chan, peer);
 
-	if (args.app_args) {
-		ast_copy_string(feature->app_args, args.app_args, FEATURE_APP_ARGS_LEN);
-	}
+	set_config_flags(chan, config);
 
-	if (args.moh_class) {
-		ast_copy_string(feature->moh_class, args.moh_class, FEATURE_MOH_LEN);
+	/* Answer if need be */
+	if (ast_channel_state(chan) != AST_STATE_UP) {
+		if (ast_raw_answer(chan, 1)) {
+			return -1;
+		}
 	}
 
-	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;
-	}
+#ifdef FOR_DEBUG
+	/* show the two channels and cdrs involved in the bridge for debug & devel purposes */
+	ast_channel_log("Pre-bridge CHAN Channel info", chan);
+	ast_channel_log("Pre-bridge PEER Channel info", peer);
+#endif
+	/* two channels are being marked as linked here */
+	ast_channel_set_linkgroup(chan, peer);
 
 	/*
-	 * We will parse and require correct syntax for the ActivatedBy
-	 * option, but the ActivatedBy option is not honored anymore.
+	 * If we are bridging a call, stop worrying about forwarding
+	 * loops.  We presume that if a call is being bridged, that the
+	 * humans in charge know what they're doing.  If they don't,
+	 * well, what can we do about that?
 	 */
-	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;
-	}
+	clear_dialed_interfaces(chan);
+	clear_dialed_interfaces(peer);
 
-	ast_register_feature(feature);
+	res = 0;
+	ast_channel_lock(chan);
+	res |= ast_bridge_features_ds_set(chan, &config->features_caller);
+	ast_channel_unlock(chan);
+	ast_channel_lock(peer);
+	res |= ast_bridge_features_ds_set(peer, &config->features_callee);
+	ast_channel_unlock(peer);
 
-	ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n",
-		var->name, args.app, args.app_args, args.exten);
-}
+	if (res) {
+		return -1;
+	}
 
-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"
-	};
+	if (config->timelimit) {
+		struct ast_bridge_features_limits call_duration_limits_chan;
+		struct ast_bridge_features_limits call_duration_limits_peer;
+		int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
 
-	/* 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));
-		}
-	}
+		if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
+			ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
 
-	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);
+			return -1;
 		}
-	}
-
-	/* 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);
+		if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
+			ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
+			ast_bridge_features_limits_destroy(&call_duration_limits_chan);
 
-	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;
+			return -1;
 		}
 
-		/* 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;
-		}
+		bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
 
-		if (!(fg = register_group(ctg))) {
-			continue;
+		if (ast_bridge_features_set_limits(chan_features, &call_duration_limits_chan, 0)) {
+			abandon_call = 1;
+		}
+		if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
+			abandon_call = 1;
 		}
 
-		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;
-			}
+		/* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
+		ast_bridge_features_limits_destroy(&call_duration_limits_chan);
+		ast_bridge_features_limits_destroy(&call_duration_limits_peer);
 
-			register_group_feature(fg, var->value, feature);
+		if (abandon_call) {
+			ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
+			return -1;
 		}
 	}
 
-	AST_RWLIST_UNLOCK(&feature_groups);
-
 	return 0;
 }
 
 /*!
- * \internal
- * \brief Destroy the given dialplan usage context.
+ * \brief bridge the call and set CDR
  *
- * \param doomed Parking lot usage context to destroy.
+ * \param chan The bridge considers this channel the caller.
+ * \param peer The bridge considers this channel the callee.
+ * \param config Configuration for this bridge.
  *
- * \return Nothing
+ * Set start time, check for two channels,check if monitor on
+ * check for feature activation, create new CDR
+ * \retval res on success.
+ * \retval -1 on failure to bridge.
  */
-static void destroy_dialplan_usage_context(struct parking_dp_context *doomed)
+int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
 {
-	struct parking_dp_ramp *ramp;
-	struct parking_dp_spaces *spaces;
+	int res;
+	struct ast_bridge *bridge;
+	struct ast_bridge_features chan_features;
+	struct ast_bridge_features *peer_features;
 
-	while ((ramp = AST_LIST_REMOVE_HEAD(&doomed->access_extens, node))) {
-		ast_free(ramp);
+	/* Setup features. */
+	res = ast_bridge_features_init(&chan_features);
+	peer_features = ast_bridge_features_new();
+	if (res || !peer_features) {
+		ast_bridge_features_destroy(peer_features);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
 	}
-	while ((spaces = AST_LIST_REMOVE_HEAD(&doomed->spaces, node))) {
-		ast_free(spaces);
+
+	if (pre_bridge_setup(chan, peer, config, &chan_features, peer_features)) {
+		ast_bridge_features_destroy(peer_features);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
 	}
-	while ((spaces = AST_LIST_REMOVE_HEAD(&doomed->hints, node))) {
-		ast_free(spaces);
+
+	/* Create bridge */
+	bridge = ast_bridge_basic_new();
+	if (!bridge) {
+		ast_bridge_features_destroy(peer_features);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
 	}
-	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;
+	/* Put peer into the bridge */
+	if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
+		ast_bridge_destroy(bridge);
+		ast_bridge_features_cleanup(&chan_features);
+		bridge_failed_peer_goto(chan, peer);
+		return -1;
+	}
+
+	/* Join bridge */
+	ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
 
-	while ((item = AST_LIST_REMOVE_HEAD(doomed, node))) {
-		destroy_dialplan_usage_context(item);
+	/*
+	 * If the bridge was broken for a hangup that isn't real, then
+	 * don't run the h extension, because the channel isn't really
+	 * hung up.  This should really only happen with
+	 * AST_SOFTHANGUP_ASYNCGOTO.
+	 */
+	res = -1;
+	ast_channel_lock(chan);
+	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+		res = 0;
 	}
-}
+	ast_channel_unlock(chan);
 
-/*!
- * \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;
+	ast_bridge_features_cleanup(&chan_features);
 
-	ramp_node = ast_calloc(1, sizeof(*ramp_node) + strlen(exten));
-	if (!ramp_node) {
-		return NULL;
+/* BUGBUG this is used by Dial and FollowMe for CDR information.  By Queue for Queue stats like CDRs. */
+	if (res && config->end_bridge_callback) {
+		config->end_bridge_callback(config->end_bridge_callback_data);
 	}
-	ramp_node->exclusive = exclusive;
-	strcpy(ramp_node->exten, exten);
-	return ramp_node;
+
+	return res;
 }
 
-/*!
- * \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)
+/*! \brief Output parking event to manager */
+static void post_manager_event(const char *s, struct parkeduser *pu)
 {
-	struct parking_dp_ramp *cur_ramp;
-	struct parking_dp_ramp *new_ramp;
-	int cmp;
+	manager_event(EVENT_FLAG_CALL, s,
+		"Exten: %s\r\n"
+		"Channel: %s\r\n"
+		"Parkinglot: %s\r\n"
+		"CallerIDNum: %s\r\n"
+		"CallerIDName: %s\r\n"
+		"ConnectedLineNum: %s\r\n"
+		"ConnectedLineName: %s\r\n"
+		"UniqueID: %s\r\n",
+		pu->parkingexten,
+		ast_channel_name(pu->chan),
+		pu->parkinglot->name,
+		S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
+		S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
+		S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
+		S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
+		ast_channel_uniqueid(pu->chan)
+		);
+}
+
+static char *callback_dialoptions(struct ast_flags *features_callee, struct ast_flags *features_caller, char *options, size_t len)
+{
+	int i = 0;
+	enum {
+		OPT_CALLEE_REDIRECT   = 't',
+		OPT_CALLER_REDIRECT   = 'T',
+		OPT_CALLEE_AUTOMON    = 'w',
+		OPT_CALLER_AUTOMON    = 'W',
+		OPT_CALLEE_DISCONNECT = 'h',
+		OPT_CALLER_DISCONNECT = 'H',
+		OPT_CALLEE_PARKCALL   = 'k',
+		OPT_CALLER_PARKCALL   = 'K',
+	};
 
-	/* Make sure that exclusive is only 0 or 1 */
-	if (exclusive) {
-		exclusive = 1;
+	memset(options, 0, len);
+	if (ast_test_flag(features_caller, AST_FEATURE_REDIRECT) && i < len) {
+		options[i++] = OPT_CALLER_REDIRECT;
+	}
+	if (ast_test_flag(features_caller, AST_FEATURE_AUTOMON) && i < len) {
+		options[i++] = OPT_CALLER_AUTOMON;
+	}
+	if (ast_test_flag(features_caller, AST_FEATURE_DISCONNECT) && i < len) {
+		options[i++] = OPT_CALLER_DISCONNECT;
+	}
+	if (ast_test_flag(features_caller, AST_FEATURE_PARKCALL) && i < len) {
+		options[i++] = OPT_CALLER_PARKCALL;
 	}
 
-	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;
+	if (ast_test_flag(features_callee, AST_FEATURE_REDIRECT) && i < len) {
+		options[i++] = OPT_CALLEE_REDIRECT;
+	}
+	if (ast_test_flag(features_callee, AST_FEATURE_AUTOMON) && i < len) {
+		options[i++] = OPT_CALLEE_AUTOMON;
+	}
+	if (ast_test_flag(features_callee, AST_FEATURE_DISCONNECT) && i < len) {
+		options[i++] = OPT_CALLEE_DISCONNECT;
+	}
+	if (ast_test_flag(features_callee, AST_FEATURE_PARKCALL) && i < len) {
+		options[i++] = OPT_CALLEE_PARKCALL;
 	}
-	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;
+	return options;
 }
 
 /*!
  * \internal
- * \brief Create a new parking lot spaces dialplan usage node.
+ * \brief Run management on a parked call.
  *
- * \param start First parking lot space to add.
- * \param stop Last parking lot space to add.
+ * \note The parkinglot parkings list is locked on entry.
  *
- * \retval New usage ramp node on success.
- * \retval NULL on error.
+ * \retval TRUE if the parking completed.
  */
-static struct parking_dp_spaces *build_dialplan_useage_spaces(int start, int stop)
+static int manage_parked_call(struct parkeduser *pu, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
 {
-	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;
-}
+	struct ast_channel *chan = pu->chan;	/* shorthand */
+	int tms;        /* timeout for this item */
+	int x;          /* fd index in channel */
 
-/*!
- * \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;
-			}
+	tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
+	if (tms > pu->parkingtime) {
+		/*
+		 * Call has been parked too long.
+		 * Stop entertaining the caller.
+		 */
+		switch (pu->hold_method) {
+		case AST_CONTROL_HOLD:
+			ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
+			break;
+		case AST_CONTROL_RINGING:
+			ast_indicate(pu->chan, -1);
+			break;
+		default:
+			break;
+		}
+		pu->hold_method = 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);
-			}
+		/* Get chan, exten from derived kludge */
+		if (pu->peername[0]) {
+			char *peername;
+			char *dash;
+			char *peername_flat; /* using something like DAHDI/52 for an extension name is NOT a good idea */
+			char parkingslot[AST_MAX_EXTENSION]; /* buffer for parkinglot slot number */
+			int i;
 
-			/* Current node is eaten by the expanding node. */
-			if (expand_node->stop < cur_node->stop) {
-				expand_node->stop = cur_node->stop;
+			peername = ast_strdupa(pu->peername);
+			dash = strrchr(peername, '-');
+			if (dash) {
+				*dash = '\0';
 			}
-			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;
+			peername_flat = ast_strdupa(peername);
+			for (i = 0; peername_flat[i]; i++) {
+				if (peername_flat[i] == '/') {
+					peername_flat[i] = '_';
+				}
 			}
-			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);
-		}
+			if (!ast_context_find_or_create(NULL, NULL, parking_con_dial, registrar)) {
+				ast_log(LOG_ERROR,
+					"Parking dial context '%s' does not exist and unable to create\n",
+					parking_con_dial);
+			} else {
+				char returnexten[AST_MAX_EXTENSION];
+				char comebackdialtime[AST_MAX_EXTENSION];
+				struct ast_datastore *features_datastore;
+				struct ast_dial_features *dialfeatures;
 
-		/* 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 (!strncmp(peername, "Parked/", 7)) {
+					peername += 7;
+				}
 
-	if (expand_node) {
-		/*
-		 * The previous node expanded and either ate all following nodes
-		 * or it was the last node.
-		 */
-		return 0;
-	}
+				ast_channel_lock(chan);
+				features_datastore = ast_channel_datastore_find(chan, &dial_features_info,
+					NULL);
+				if (features_datastore && (dialfeatures = features_datastore->data)) {
+					char buf[MAX_DIAL_FEATURE_OPTIONS] = {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;
-}
+					snprintf(returnexten, sizeof(returnexten), "%s,%u,%s", peername,
+						pu->parkinglot->cfg.comebackdialtime,
+						callback_dialoptions(&dialfeatures->peer_features,
+							&dialfeatures->my_features, buf, sizeof(buf)));
+				} else { /* Existing default */
+					ast_log(LOG_NOTICE, "Dial features not found on %s, using default!\n",
+						ast_channel_name(chan));
+					snprintf(returnexten, sizeof(returnexten), "%s,%u,t", peername,
+						pu->parkinglot->cfg.comebackdialtime);
+				}
+				ast_channel_unlock(chan);
 
-/*!
- * \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;
-}
+				snprintf(comebackdialtime, sizeof(comebackdialtime), "%u",
+						pu->parkinglot->cfg.comebackdialtime);
+				pbx_builtin_setvar_helper(chan, "COMEBACKDIALTIME", comebackdialtime);
 
-/*!
- * \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;
+				pbx_builtin_setvar_helper(chan, "PARKER", peername);
 
-	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;
-}
+				if (ast_add_extension(parking_con_dial, 1, peername_flat, 1, NULL, NULL,
+					"Dial", ast_strdup(returnexten), ast_free_ptr, registrar)) {
+					ast_log(LOG_ERROR,
+						"Could not create parking return dial exten: %s@%s\n",
+						peername_flat, parking_con_dial);
+				}
+			}
 
-/*!
- * \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;
+			snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
+			pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parkingslot);
+			pbx_builtin_setvar_helper(chan, "PARKEDLOT", pu->parkinglot->name);
 
-	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;
+			if (pu->options_specified) {
+				/*
+				 * Park() was called with overriding return arguments, respect
+				 * those arguments.
+				 */
+				set_c_e_p(chan, pu->context, pu->exten, pu->priority);
+			} else if (pu->parkinglot->cfg.comebacktoorigin) {
+				set_c_e_p(chan, parking_con_dial, peername_flat, 1);
+			} else {
+				/* Handle fallback when extensions don't exist here since that logic was removed from pbx */
+				if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1, NULL)) {
+					set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1);
+				} else if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, "s", 1, NULL)) {
+					ast_verb(2, "Can not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
+						pu->parkinglot->cfg.comebackcontext, peername_flat, pu->parkinglot->cfg.comebackcontext);
+					set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, "s", 1);
+				} else {
+					ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
+						ast_channel_name(chan),
+						pu->parkinglot->cfg.comebackcontext, peername_flat,
+						pu->parkinglot->cfg.comebackcontext);
+					set_c_e_p(chan, "default", "s", 1);
+				}
+			}
+		} else {
+			/*
+			 * They've been waiting too long, send them back to where they
+			 * came.  Theoretically they should have their original
+			 * extensions and such, but we copy to be on the safe side.
+			 */
+			set_c_e_p(chan, pu->context, pu->exten, pu->priority);
 		}
-		AST_LIST_INSERT_BEFORE_CURRENT(new_ctx, node);
-		return 0;
-	}
-	AST_LIST_TRAVERSE_SAFE_END;
+		post_manager_event("ParkedCallTimeOut", pu);
+		ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallTimeOut", NULL);
 
-	/* 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;
-}
+		ast_verb(2, "Timeout for %s parked on %d (%s). Returning to %s,%s,%d\n",
+			ast_channel_name(pu->chan), pu->parkingnum, pu->parkinglot->name, ast_channel_context(pu->chan),
+			ast_channel_exten(pu->chan), ast_channel_priority(pu->chan));
 
-/*!
- * \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;
+		/* Start up the PBX, or hang them up */
+		if (ast_pbx_start(chan))  {
+			ast_log(LOG_WARNING,
+				"Unable to restart the PBX for user on '%s', hanging them up...\n",
+				ast_channel_name(pu->chan));
+			ast_hangup(chan);
 		}
+
+		/* And take them out of the parking lot */
+		return 1;
 	}
-	ao2_iterator_destroy(&iter);
 
-	return status;
-}
+	/* still within parking time, process descriptors */
+	if (pfds) {
+		for (x = 0; x < AST_MAX_FDS; x++) {
+			struct ast_frame *f;
+			int y;
+
+			if (!ast_channel_fd_isset(chan, x)) {
+				continue;	/* nothing on this descriptor */
+			}
+
+			for (y = 0; y < nfds; y++) {
+				if (pfds[y].fd == ast_channel_fd(chan, x)) {
+					/* Found poll record! */
+					break;
+				}
+			}
+			if (y == nfds) {
+				/* Not found */
+				continue;
+			}
+
+			if (!(pfds[y].revents & (POLLIN | POLLERR | POLLPRI))) {
+				/* Next x */
+				continue;
+			}
+
+			if (pfds[y].revents & POLLPRI) {
+				ast_set_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
+			} else {
+				ast_clear_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
+			}
+			ast_channel_fdno_set(chan, x);
 
-/*!
- * \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 */
+			/* See if they need servicing */
+			f = ast_read(pu->chan);
+			/* Hangup? */
+			if (!f || (f->frametype == AST_FRAME_CONTROL
+				&& f->subclass.integer == AST_CONTROL_HANGUP)) {
+				if (f) {
+					ast_frfree(f);
+				}
+				post_manager_event("ParkedCallGiveUp", pu);
+				ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallGiveUp",
+					NULL);
 
-	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);
-	}
-}
+				/* There's a problem, hang them up */
+				ast_verb(2, "%s got tired of being parked\n", ast_channel_name(chan));
+				ast_hangup(chan);
 
-/*!
- * \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;
+				/* And take them out of the parking lot */
+				return 1;
+			} else {
+				/* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
+				ast_frfree(f);
+				if (pu->hold_method == AST_CONTROL_HOLD
+					&& pu->moh_trys < 3
+					&& !ast_channel_generatordata(chan)) {
+					ast_debug(1,
+						"MOH on parked call stopped by outside source.  Restarting on channel %s.\n",
+						ast_channel_name(chan));
+					ast_indicate_data(chan, AST_CONTROL_HOLD,
+						S_OR(pu->parkinglot->cfg.mohclass, NULL),
+						(!ast_strlen_zero(pu->parkinglot->cfg.mohclass)
+							? strlen(pu->parkinglot->cfg.mohclass) + 1 : 0));
+					pu->moh_trys++;
+				}
+				break;
+			}
+		} /* End for */
+	}
 
-	old_ramp = AST_LIST_FIRST(old_ramps);
-	new_ramp = AST_LIST_FIRST(new_ramps);
+	/* mark fds for next round */
+	for (x = 0; x < AST_MAX_FDS; x++) {
+		if (ast_channel_fd_isset(chan, x)) {
+			void *tmp = ast_realloc(*new_pfds,
+				(*new_nfds + 1) * sizeof(struct pollfd));
 
-	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. */
+			if (!tmp) {
+				continue;
+			}
+			*new_pfds = tmp;
+			(*new_pfds)[*new_nfds].fd = ast_channel_fd(chan, x);
+			(*new_pfds)[*new_nfds].events = POLLIN | POLLERR | POLLPRI;
+			(*new_pfds)[*new_nfds].revents = 0;
+			(*new_nfds)++;
 		}
-		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);
+	/* Keep track of our shortest wait */
+	if (tms < *ms || *ms < 0) {
+		*ms = tms;
 	}
-}
-
-/*!
- * \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);
+	/* Stay in the parking lot. */
+	return 0;
 }
 
-/*!
- * \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))
+/*! \brief Run management on parkinglots, called once per parkinglot */
+static void manage_parkinglot(struct ast_parkinglot *curlot, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
 {
-	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;
+	struct parkeduser *pu;
+	struct ast_context *con;
 
-	while (old_range) {
-		if (space < old_range->start) {
-			space = old_range->start;
+	/* Lock parkings list */
+	AST_LIST_LOCK(&curlot->parkings);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&curlot->parkings, pu, list) {
+		if (pu->notquiteyet) { /* Pretend this one isn't here yet */
+			continue;
 		}
-		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);
+		if (manage_parked_call(pu, pfds, nfds, new_pfds, new_nfds, ms)) {
+			/* Parking is complete for this call so remove it from the parking lot. */
+			con = ast_context_find(pu->parkinglot->cfg.parking_con);
+			if (con) {
+				if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
+					ast_log(LOG_WARNING,
+						"Whoa, failed to remove the parking extension %s@%s!\n",
+						pu->parkingexten, pu->parkinglot->cfg.parking_con);
 				}
-				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;
+				notify_metermaids(pu->parkingexten, pu->parkinglot->cfg.parking_con,
+					AST_DEVICE_NOT_INUSE);
+			} else {
+				ast_log(LOG_WARNING,
+					"Whoa, parking lot '%s' context '%s' does not exist.\n",
+					pu->parkinglot->name, pu->parkinglot->cfg.parking_con);
 			}
-		} 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);
+			AST_LIST_REMOVE_CURRENT(list);
+			parkinglot_unref(pu->parkinglot);
+			ast_free(pu);
 		}
 	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&curlot->parkings);
 }
 
 /*!
- * \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.
+ * \brief Take care of parked calls and unpark them if needed
+ * \param ignore unused var.
  *
- * \return Nothing
+ * Start inf loop, lock parking lot, check if any parked channels have gone above timeout
+ * if so, remove channel from parking lot and return it to the extension that parked it.
+ * Check if parked channel decided to hangup, wait until next FD via select().
  */
-static void remove_dead_dialplan_useage(struct parking_dp_map *old_map, struct parking_dp_map *new_map)
+static void *do_parking_thread(void *ignore)
 {
-	struct parking_dp_context *old_ctx;
-	struct parking_dp_context *new_ctx;
-	struct ast_context *con;
-	int cmp;
+	struct pollfd *pfds = NULL, *new_pfds = NULL;
+	int nfds = 0, new_nfds = 0;
 
-	old_ctx = AST_LIST_FIRST(old_map);
-	new_ctx = AST_LIST_FIRST(new_map);
+	for (;;) {
+		struct ao2_iterator iter;
+		struct ast_parkinglot *curlot;
+		int ms = -1;	/* poll2 timeout, uninitialized */
 
-	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. */
+		iter = ao2_iterator_init(parkinglots, 0);
+		while ((curlot = ao2_iterator_next(&iter))) {
+			manage_parkinglot(curlot, pfds, nfds, &new_pfds, &new_nfds, &ms);
+			ao2_ref(curlot, -1);
 		}
-		new_ctx = AST_LIST_NEXT(new_ctx, node);
-	}
+		ao2_iterator_destroy(&iter);
 
-	/* 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);
-		}
+		/* Recycle */
+		ast_free(pfds);
+		pfds = new_pfds;
+		nfds = new_nfds;
+		new_pfds = NULL;
+		new_nfds = 0;
+
+		/* Wait for something to happen */
+		ast_poll(pfds, nfds, ms);
+		pthread_testcancel();
 	}
+	/* If this WERE reached, we'd need to free(pfds) */
+	return NULL;	/* Never reached */
 }
 
-static int parkinglot_markall_cb(void *obj, void *arg, int flags)
+/*! \brief Find parkinglot by name */
+static struct ast_parkinglot *find_parkinglot(const char *name)
 {
-	struct ast_parkinglot *parkinglot = obj;
-
-	parkinglot->the_mark = 1;
-	return 0;
-}
+	struct ast_parkinglot *parkinglot;
 
-static int parkinglot_is_marked_cb(void *obj, void *arg, int flags)
-{
-	struct ast_parkinglot *parkinglot = obj;
+	if (ast_strlen_zero(name)) {
+		return NULL;
+	}
 
-	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;
+	parkinglot = ao2_find(parkinglots, (void *) name, 0);
+	if (parkinglot) {
+		ast_debug(1, "Found Parking lot: %s\n", parkinglot->name);
 	}
 
-	return 0;
+	return parkinglot;
 }
 
-static int parkinglot_activate_cb(void *obj, void *arg, int flags)
+/*! \brief Copy parkinglot and store it with new name */
+static struct ast_parkinglot *copy_parkinglot(const char *name, const struct ast_parkinglot *parkinglot)
 {
-	struct ast_parkinglot *parkinglot = obj;
+	struct ast_parkinglot *copylot;
 
-	if (parkinglot->the_mark) {
-		/*
-		 * Don't activate a parking lot that still bears the_mark since
-		 * it is effectively deleted.
-		 */
-		return 0;
+	if ((copylot = find_parkinglot(name))) { /* Parkinglot with that name already exists */
+		ao2_ref(copylot, -1);
+		return NULL;
 	}
 
-	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);
+	copylot = create_parkinglot(name);
+	if (!copylot) {
+		return NULL;
 	}
 
-	return 0;
+	ast_debug(1, "Building parking lot %s\n", name);
+
+	/* Copy the source parking lot configuration. */
+	copylot->cfg = parkinglot->cfg;
+
+	return copylot;
+}
+
+AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
+	AST_APP_OPTION('r', AST_PARK_OPT_RINGING),
+	AST_APP_OPTION('R', AST_PARK_OPT_RANDOMIZE),
+	AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
+END_OPTIONS );
+
+/*!
+ * \brief Unreference parkinglot object.
+ */
+static void parkinglot_unref(struct ast_parkinglot *parkinglot)
+{
+	ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name,
+		ao2_ref(parkinglot, 0) - 1);
+	ao2_ref(parkinglot, -1);
 }
 
-static int load_config(int reload)
+static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot)
 {
-	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");
-	}
+	int refcount;
 
-	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;
-	}
+	refcount = ao2_ref(parkinglot, +1);
+	ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name, refcount + 1);
+	return parkinglot;
+}
 
-	/* Save current parking lot dialplan needs. */
-	if (build_dialplan_useage_map(&old_usage_map, 0)) {
-		destroy_dialplan_usage_map(&old_usage_map);
+/*! \brief Destroy a parking lot */
+static void parkinglot_destroy(void *obj)
+{
+	struct ast_parkinglot *doomed = obj;
 
-		/* Allow reloading later to see if conditions have improved. */
-		force_reload_load = 1;
-		return -1;
-	}
+	/*
+	 * No need to destroy parked calls here because any parked call
+	 * holds a parking lot reference.  Therefore the parkings list
+	 * must be empty.
+	 */
+	ast_assert(AST_LIST_EMPTY(&doomed->parkings));
+	AST_LIST_HEAD_DESTROY(&doomed->parkings);
+}
 
-	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");
+/*! \brief Allocate parking lot structure */
+static struct ast_parkinglot *create_parkinglot(const char *name)
+{
+	struct ast_parkinglot *newlot;
 
-	/* 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;
+	if (ast_strlen_zero(name)) { /* No name specified */
+		return NULL;
 	}
 
-	/* 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);
+	newlot = ao2_alloc(sizeof(*newlot), parkinglot_destroy);
+	if (!newlot)
+		return NULL;
 
-	ao2_t_callback(parkinglots, OBJ_NODATA, parkinglot_activate_cb, NULL,
-		"callback to activate all parking lots");
+	ast_copy_string(newlot->name, name, sizeof(newlot->name));
+	newlot->cfg.is_invalid = 1;/* No config is set yet. */
+	AST_LIST_HEAD_INIT(&newlot->parkings);
 
-	return 0;
+	return newlot;
 }
 
 /*!
- * \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.
+ * \brief Add parking hints for all defined parking spaces.
+ * \param context Dialplan context to add the hints.
+ * \param start Starting space in parkinglot.
+ * \param stop Ending space in parkinglot.
  */
-static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static void park_add_hints(const char *context, int start, int stop)
 {
-	int i;
-	struct ast_call_feature *feature;
-#define HFS_FORMAT "%-25s %-7s %-7s\n"
-
-	switch (cmd) {
+	int numext;
+	char device[AST_MAX_EXTENSION];
+	char exten[10];
 
-	case CLI_INIT:
-		e->command = "features show";
-		e->usage =
-			"Usage: features show\n"
-			"       Lists configured features\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
+	for (numext = start; numext <= stop; numext++) {
+		snprintf(exten, sizeof(exten), "%d", numext);
+		snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+		ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
 	}
+}
 
-	ast_cli(a->fd, HFS_FORMAT, "Builtin Feature", "Default", "Current");
-	ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
+/*! Default configuration for default parking lot. */
+static const struct parkinglot_cfg parkinglot_cfg_default_default = {
+	.mohclass = "default",
+	.parkext = DEFAULT_PARK_EXTENSION,
+	.parking_con = "parkedcalls",
+	.parking_start = 701,
+	.parking_stop = 750,
+	.parkingtime = DEFAULT_PARK_TIME,
+	.comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
+	.comebackcontext = DEFAULT_COMEBACK_CONTEXT,
+	.comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
+};
 
-	ast_cli(a->fd, HFS_FORMAT, "Pickup", "*8", ast_pickup_ext());          /* default hardcoded above, so we'll hardcode it here */
+/*! Default configuration for normal parking lots. */
+static const struct parkinglot_cfg parkinglot_cfg_default = {
+	.parkext = DEFAULT_PARK_EXTENSION,
+	.parkingtime = DEFAULT_PARK_TIME,
+	.comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
+	.comebackcontext = DEFAULT_COMEBACK_CONTEXT,
+	.comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
+};
 
-	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();
+/*!
+ * \internal
+ * \brief Activate the given parkinglot.
+ *
+ * \param parkinglot Parking lot to activate.
+ *
+ * \details
+ * Insert into the dialplan the context, parking lot access
+ * extension, and optional dialplan hints.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int parkinglot_activate(struct ast_parkinglot *parkinglot)
+{
+	int disabled = 0;
+	char app_data[5 + AST_MAX_CONTEXT];
 
-	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");
+	/* Create Park option list.  Must match with struct park_app_args options. */
+	if (parkinglot->cfg.parkext_exclusive) {
+		/* Specify the parking lot this parking extension parks calls. */
+		snprintf(app_data, sizeof(app_data), ",,,,,%s", parkinglot->name);
 	} else {
-		AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
-			ast_cli(a->fd, HFS_FORMAT, feature->sname, "no def", feature->exten);
-		}
+		/* The dialplan must specify which parking lot to use. */
+		app_data[0] = '\0';
 	}
-	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;
+	/* Create context */
+	if (!ast_context_find_or_create(NULL, NULL, parkinglot->cfg.parking_con, registrar)) {
+		ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n",
+			parkinglot->cfg.parking_con);
+		disabled = 1;
 
-		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);
-			}
+	/* Add a parking extension into the context */
+	} else if (ast_add_extension(parkinglot->cfg.parking_con, 1, parkinglot->cfg.parkext,
+		1, NULL, NULL, parkcall, ast_strdup(app_data), ast_free_ptr, registrar)) {
+		ast_log(LOG_ERROR, "Could not create parking lot %s access exten %s@%s\n",
+			parkinglot->name, parkinglot->cfg.parkext, parkinglot->cfg.parking_con);
+		disabled = 1;
+	} else {
+		/* Add parking hints */
+		if (parkinglot->cfg.parkaddhints) {
+			park_add_hints(parkinglot->cfg.parking_con, parkinglot->cfg.parking_start,
+				parkinglot->cfg.parking_stop);
 		}
-	}
-	AST_RWLIST_UNLOCK(&feature_groups);
 
-	ast_cli(a->fd, "\n");
+		/*
+		 * XXX Not sure why we should need to notify the metermaids for
+		 * this exten.  It was originally done for the default parking
+		 * lot entry exten only but should be done for all entry extens
+		 * if we do it for one.
+		 */
+		/* Notify metermaids about parking lot entry exten state. */
+		notify_metermaids(parkinglot->cfg.parkext, parkinglot->cfg.parking_con,
+			AST_DEVICE_INUSE);
+	}
 
-	return CLI_SUCCESS;
+	parkinglot->disabled = disabled;
+	return disabled ? -1 : 0;
 }
 
 int ast_features_reload(void)
@@ -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 0000000000000000000000000000000000000000..f8bdb1c3c90a71ec74d5349eeac4275f59fd750a
--- /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 229b83b4e7c76ec4989e7fc558207f828bf817fa..da5a98490cad03f2e985c0b155753b9c6d738217 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);
 	}