diff --git a/apps/app_dial.c b/apps/app_dial.c
index d1d76bcff1e6498b4716f11c1172c09c36431384..0226995d2157e6d5a49eb5ccad4d99f548f43d12 100755
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -1135,12 +1135,18 @@ static int dial_exec(struct ast_channel *chan, void *data)
 		
 		if (!res) {
 			memset(&config,0,sizeof(struct ast_bridge_config));
-			config.play_to_caller=play_to_caller;
-			config.play_to_callee=play_to_callee;
-			config.allowredirect_in = allowredir_in;
-			config.allowredirect_out = allowredir_out;
-			config.allowdisconnect_in = allowdisconnect_in;
-			config.allowdisconnect_out = allowdisconnect_out;
+			if (play_to_caller)
+				config.features_caller |= AST_FEATURE_PLAY_WARNING;
+			if (play_to_callee)
+				config.features_callee |= AST_FEATURE_PLAY_WARNING;
+			if (allowredir_in)
+				config.features_callee |= AST_FEATURE_REDIRECT;
+			if (allowredir_out)
+				config.features_caller |= AST_FEATURE_REDIRECT;
+			if (allowdisconnect_in)
+				config.features_callee |= AST_FEATURE_DISCONNECT;
+			if (allowdisconnect_out)
+				config.features_caller |= AST_FEATURE_DISCONNECT;
 			config.timelimit = timelimit;
 			config.play_warning = play_warning;
 			config.warning_freq = warning_freq;
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 476eceb608749846a54d7ad28d110b1751898ffc..38a550fc8260a2521edc69d5a9818b2e2aa51790 100755
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -1480,10 +1480,14 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri
 		time(&callstart);
 
 		memset(&config,0,sizeof(struct ast_bridge_config));
-		config.allowredirect_in = ast_test_flag(&flags, QUEUE_FLAG_REDIR_IN);
-		config.allowredirect_out = ast_test_flag(&flags, QUEUE_FLAG_REDIR_OUT);
-		config.allowdisconnect_in = ast_test_flag(&flags, QUEUE_FLAG_DISCON_IN);
-		config.allowdisconnect_out = ast_test_flag(&flags, QUEUE_FLAG_DISCON_OUT);
+		if (ast_test_flag(&flags, QUEUE_FLAG_REDIR_IN))
+			config.features_callee |= AST_FEATURE_REDIRECT;
+		if (ast_test_flag(&flags, QUEUE_FLAG_REDIR_OUT))
+			config.features_caller |= AST_FEATURE_REDIRECT;
+		if (ast_test_flag(&flags, QUEUE_FLAG_DISCON_IN))
+			config.features_callee |= AST_FEATURE_DISCONNECT;
+		if (ast_test_flag(&flags, QUEUE_FLAG_DISCON_OUT))
+			config.features_caller |= AST_FEATURE_DISCONNECT;
 		bridge = ast_bridge_call(qe->chan,peer,&config);
 
 		if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
diff --git a/channel.c b/channel.c
index 39720028717bf1e8fc86242fdf6a6fad554f261f..467fbc9e5f326aa587eec4b9dc864ecdddfa641b 100755
--- a/channel.c
+++ b/channel.c
@@ -2600,7 +2600,6 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 {
 	/* Copy voice back and forth between the two channels.	Give the peer
 	   the ability to transfer calls with '#<extension' syntax. */
-	int flags;
 	struct ast_channel *cs[3];
 	int to = -1;
 	struct ast_frame *f;
@@ -2614,8 +2613,6 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 	long elapsed_ms=0, time_left_ms=0;
 	int playit=0, playitagain=1, first_time=1;
 
-	flags = (config->allowdisconnect_out||config->allowredirect_out ? AST_BRIDGE_DTMF_CHANNEL_0 : 0) + (config->allowdisconnect_in||config->allowredirect_in ? AST_BRIDGE_DTMF_CHANNEL_1 : 0);
-
 	*fo = NULL;
 	firstpass = config->firstpass;
 	config->firstpass = 0;
@@ -2624,9 +2621,9 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 	gettimeofday(&start_time,NULL);
 	time_left_ms = config->timelimit;
 
-	if (config->play_to_caller && config->start_sound && firstpass)
+	if ((config->features_caller & AST_FEATURE_PLAY_WARNING) && config->start_sound && firstpass)
 		bridge_playfile(c0,c1,config->start_sound,time_left_ms / 1000);
-	if (config->play_to_callee && config->start_sound && firstpass)
+	if ((config->features_callee & AST_FEATURE_PLAY_WARNING) && config->start_sound && firstpass)
 		bridge_playfile(c1,c0,config->start_sound,time_left_ms / 1000);
 
 	/* Stop if we're a zombie or need a soft hangup */
@@ -2664,7 +2661,7 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 			elapsed_ms = tvdiff(&precise_now,&start_time);
 			time_left_ms = config->timelimit - elapsed_ms;
 
-			if (playitagain && (config->play_to_caller || config->play_to_callee) && (config->play_warning && time_left_ms <= config->play_warning)) { 
+			if (playitagain && ((config->features_caller & AST_FEATURE_PLAY_WARNING) || (config->features_callee & AST_FEATURE_PLAY_WARNING)) && (config->play_warning && time_left_ms <= config->play_warning)) { 
 				/* narrowing down to the end */
 				if (config->warning_freq == 0) {
 					playit = 1;
@@ -2680,9 +2677,9 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 				}
 			}
 			if (time_left_ms <= 0) {
-				if (config->play_to_caller && config->end_sound)
+				if ((config->features_caller & AST_FEATURE_PLAY_WARNING) && config->end_sound)
 					bridge_playfile(c0,c1,config->end_sound,0);
-				if (config->play_to_callee && config->end_sound)
+				if ((config->features_callee & AST_FEATURE_PLAY_WARNING) && config->end_sound)
 					bridge_playfile(c1,c0,config->end_sound,0);
 				*fo = NULL;
 				if (who) *rc = who;
@@ -2690,9 +2687,9 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 				break;
 			}
 			if (time_left_ms >= 5000 && playit) {
-				if (config->play_to_caller && config->warning_sound && config->play_warning)
+				if ((config->features_caller & AST_FEATURE_PLAY_WARNING) && config->warning_sound && config->play_warning)
 					bridge_playfile(c0,c1,config->warning_sound,time_left_ms / 1000);
-				if (config->play_to_callee && config->warning_sound && config->play_warning)
+				if ((config->features_callee & AST_FEATURE_PLAY_WARNING) && config->warning_sound && config->play_warning)
 					bridge_playfile(c1,c0,config->warning_sound,time_left_ms / 1000);
 				playit = 0;
 			}
@@ -2711,7 +2708,7 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 				/* Looks like they share a bridge code */
 			if (option_verbose > 2) 
 				ast_verbose(VERBOSE_PREFIX_3 "Attempting native bridge of %s and %s\n", c0->name, c1->name);
-			if (!(res = c0->pvt->bridge(c0, c1, flags, fo, rc))) {
+			if (!(res = c0->pvt->bridge(c0, c1, config->flags, fo, rc))) {
 				c0->_bridge = NULL;
 				c1->_bridge = NULL;
 				manager_event(EVENT_FLAG_CALL, "Unlink", 
@@ -2759,7 +2756,7 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 			break;
 		}
 
-		if ((f->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
+		if ((f->frametype == AST_FRAME_CONTROL) && !(config->flags & AST_BRIDGE_IGNORE_SIGS)) {
 			*fo = f;
 			*rc = who;
 			res =  0;
@@ -2772,9 +2769,9 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 			(f->frametype == AST_FRAME_IMAGE) ||
 			(f->frametype == AST_FRAME_DTMF)) {
 			if ((f->frametype == AST_FRAME_DTMF) && 
-				(flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1))) {
+				(config->flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1))) {
 				if ((who == c0)) {
-					if  ((flags & AST_BRIDGE_DTMF_CHANNEL_0)) {
+					if  ((config->flags & AST_BRIDGE_DTMF_CHANNEL_0)) {
 						*rc = c0;
 						*fo = f;
 						/* Take out of conference mode */
@@ -2785,7 +2782,7 @@ int ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, struct as
 						goto tackygoto;
 				} else
 				if ((who == c1)) {
-					if (flags & AST_BRIDGE_DTMF_CHANNEL_1) {
+					if (config->flags & AST_BRIDGE_DTMF_CHANNEL_1) {
 						*rc = c1;
 						*fo = f;
 						res =  0;
diff --git a/configs/features.conf.sample b/configs/features.conf.sample
index be442c563687216e941ddf14714ed3ffebf233b3..dc6cda1b7320312577a8b7e4c59dfc0c0f0f1515 100755
--- a/configs/features.conf.sample
+++ b/configs/features.conf.sample
@@ -13,3 +13,9 @@ context => parkedcalls		; Which context parked calls are in
 				; when someone dials a parked call
 ;adsipark = yes			; if you want ADSI parking announcements
 ;pickupexten = *8		; Configure the pickup extension.  Default is *8
+;featuredigittimeout = 500	; Max time (ms) between digits for 
+				; feature activation.  Default is 500
+
+[featuremap]
+;blindxfer => #			; Blind transfer
+;disconnect => *		; Disconnect
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 909e314cc594db2e134d684a7bd856828b45f4f9..3e0743022279e686e813744d946a51875e2d64b9 100755
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -233,13 +233,15 @@ struct ast_channel {
 #define AST_FLAG_EXCEPTION	(1 << 5)	/* if there is a pending exception */
 #define AST_FLAG_MOH        (1 << 6)    /* XXX anthm promises me this will disappear XXX listening to moh */
 
+#define AST_FEATURE_PLAY_WARNING	(1 << 0)
+#define AST_FEATURE_REDIRECT		(1 << 1)
+#define AST_FEATURE_DISCONNECT		(1 << 2)
+
+#define AST_FEATURE_FLAG_NEEDSDTMF		(1 << 0)
+
 struct ast_bridge_config {
-	int play_to_caller;
-	int play_to_callee;
-	int allowredirect_in;
-	int allowredirect_out;
-	int allowdisconnect_in;
-	int allowdisconnect_out;
+	unsigned int features_caller;
+	unsigned int features_callee;
 	long timelimit;
 	long play_warning;
 	long warning_freq;
@@ -247,6 +249,7 @@ struct ast_bridge_config {
 	char *end_sound;
 	char *start_sound;
 	int firstpass;
+	int flags;
 };
 
 struct chanmon;
diff --git a/res/res_features.c b/res/res_features.c
index 65d139b1eed301ec5dc3f4cdaa30552f8c8c5ea5..b725564c715622ba24ae0b706a5536ad85c92ba3 100755
--- a/res/res_features.c
+++ b/res/res_features.c
@@ -19,6 +19,7 @@
 #include <asterisk/options.h>
 #include <asterisk/module.h>
 #include <asterisk/translate.h>
+#include <asterisk/app.h>
 #include <asterisk/say.h>
 #include <asterisk/channel_pvt.h>
 #include <asterisk/features.h>
@@ -41,6 +42,7 @@
 
 #define DEFAULT_PARK_TIME 45000
 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
+#define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
 
 static char *parkedcall = "ParkedCall";
 
@@ -67,6 +69,7 @@ static int parking_stop = 750;
 static int adsipark = 0;
 
 static int transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+static int featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
 
 /* Default courtesy tone played when party joins conference */
 static char courtesytone[256] = "";
@@ -295,24 +298,258 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
 	return 0;
 }
 
+
+#define FEATURE_RETURN_HANGUP		-1
+#define FEATURE_RETURN_SUCCESSBREAK	 0
+#define FEATURE_RETURN_PBX_KEEPALIVE	AST_PBX_KEEPALIVE
+#define FEATURE_RETURN_NO_HANGUP_PEER	AST_PBX_NO_HANGUP_PEER
+#define FEATURE_RETURN_PASSDIGITS	 21
+#define FEATURE_RETURN_STOREDIGITS	 22
+#define FEATURE_RETURN_SUCCESS	 	 23
+
+#define FEATURE_SENSE_CHAN	(1 << 0)
+#define FEATURE_SENSE_PEER	(1 << 1)
+#define FEATURE_MAX_LEN		11
+
+static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
+{
+	if (option_verbose > 3)
+		ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to disconnect call.\n", code);
+	return FEATURE_RETURN_HANGUP;
+}
+
+static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
+{
+	struct ast_channel *transferer;
+	struct ast_channel *transferee;
+	char *transferer_real_context;
+	char newext[256], *ptr;
+	int res;
+	int len;
+
+	ast_log(LOG_NOTICE, "XXX Blind Transfer %s, %s (sense=%d) XXX\n", chan->name, peer->name, sense);
+	if (sense == FEATURE_SENSE_PEER) {
+		transferer = peer;
+		transferee = chan;
+	} else {
+		transferer = chan;
+		transferee = peer;
+	}
+	if (!(transferer_real_context=pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
+	   !(transferer_real_context=pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
+		/* Use the non-macro context to transfer the call */
+		if (!ast_strlen_zero(transferer->macrocontext))
+			transferer_real_context = transferer->macrocontext;
+		else
+			transferer_real_context = transferer->context;
+	}
+	/* Start autoservice on chan while we talk
+	   to the originator */
+	ast_autoservice_start(transferee);
+	ast_moh_start(transferee, NULL);
+
+	memset(newext, 0, sizeof(newext));
+	ptr = newext;
+
+	/* Transfer */
+	if ((res=ast_streamfile(transferer, "pbx-transfer", transferer->language))) {
+		ast_moh_stop(transferee);
+		ast_autoservice_stop(transferee);
+		return res;
+	}
+	if ((res=ast_waitstream(transferer, AST_DIGIT_ANY)) < 0) {
+		ast_moh_stop(transferee);
+		ast_autoservice_stop(transferee);
+		return res;
+	}
+	ast_stopstream(transferer);
+	if (res > 0) {
+		/* If they've typed a digit already, handle it */
+		newext[0] = res;
+		ptr++;
+		len--;
+	}
+	res = 0;
+	while (strlen(newext) < sizeof(newext) - 1) {
+		res = ast_waitfordigit(transferer, transferdigittimeout);
+		if (res < 1) 
+			break;
+		if (res == '#')
+			break;
+		*(ptr++) = res;
+		if (!ast_matchmore_extension(transferer, transferer_real_context, newext, 1, transferer->cid.cid_num)) 
+			break;
+	}
+
+	if (res < 0) {
+		ast_moh_stop(transferee);
+		ast_autoservice_stop(transferee);
+		return res;
+	}
+	if (!strcmp(newext, ast_parking_ext())) {
+		ast_moh_stop(transferee);
+
+		if (ast_autoservice_stop(transferee))
+			res = -1;
+		else if (!ast_park_call(transferee, transferer, 0, NULL)) {
+			/* We return non-zero, but tell the PBX not to hang the channel when
+			   the thread dies -- We have to be careful now though.  We are responsible for 
+			   hanging up the channel, else it will never be hung up! */
+
+			if (transferer==peer)
+				res=AST_PBX_KEEPALIVE;
+			else
+				res=AST_PBX_NO_HANGUP_PEER;
+			return res;
+		} else {
+			ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
+		}
+		/* XXX Maybe we should have another message here instead of invalid extension XXX */
+	} else if (ast_exists_extension(transferee, transferer_real_context, newext, 1, transferer->cid.cid_num)) {
+		pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
+		pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
+		ast_moh_stop(transferee);
+		res=ast_autoservice_stop(transferee);
+		if (!transferee->pbx) {
+			/* Doh!  Use our handy async_goto functions */
+			if (option_verbose > 2) 
+				ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n"
+								,transferee->name, newext, transferer_real_context);
+			if (ast_async_goto(transferee, transferer_real_context, newext, 1))
+				ast_log(LOG_WARNING, "Async goto failed :-(\n");
+			res = -1;
+		} else {
+			/* Set the channel's new extension, since it exists, using transferer context */
+			strncpy(transferee->exten, newext, sizeof(transferee->exten)-1);
+			strncpy(transferee->context, transferer_real_context, sizeof(transferee->context)-1);
+			transferee->priority = 0;
+		}
+		return res;
+	} else {
+		if (option_verbose > 2)	
+			ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", newext, transferer_real_context);
+	}
+	res = ast_streamfile(transferer, "pbx-invalid", transferee->language);
+	if (res) {
+		ast_moh_stop(transferee);
+		ast_autoservice_stop(transferee);
+		return res;
+	}
+	res = ast_waitstream(transferer, AST_DIGIT_ANY);
+	ast_stopstream(transferer);
+	ast_moh_stop(transferee);
+	res = ast_autoservice_stop(transferee);
+	if (res) {
+		if (option_verbose > 1)
+			ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
+		return res;
+	}
+	return FEATURE_RETURN_SUCCESS;
+}
+
+struct ast_call_feature {
+	int feature_mask;
+	char *fname;
+	char *sname;
+	char exten[FEATURE_MAX_LEN];
+	char default_exten[FEATURE_MAX_LEN];
+	int (*operation)(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense);
+	unsigned int flags;
+};
+
+#define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
+struct ast_call_feature builtin_features[] = 
+{
+	{ AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF },
+	{ AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF },
+};
+
+static void unmap_features(void)
+{
+	int x;
+	for (x=0;x<FEATURES_COUNT;x++)
+		strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
+}
+
+static int remap_feature(const char *name, const char *value)
+{
+	int x;
+	int res = -1;
+	for (x=0;x<FEATURES_COUNT;x++) {
+		if (!strcasecmp(name, builtin_features[x].sname)) {
+			strncpy(builtin_features[x].exten, value, sizeof(builtin_features[x].exten) - 1);
+			if (option_verbose > 1)
+				ast_verbose(VERBOSE_PREFIX_2 "Remapping feature %s (%s) to sequence '%s'\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
+			res = 0;
+		} else if (!strcmp(value, builtin_features[x].exten)) 
+			ast_log(LOG_WARNING, "Sequence '%s' already mapped to function %s (%s) while assigning to %s\n", value, builtin_features[x].fname, builtin_features[x].sname, name);
+	}
+	return res;
+}
+
+static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
+{
+	int x;
+	unsigned int features;
+	int res = FEATURE_RETURN_PASSDIGITS;
+
+	if (sense == FEATURE_SENSE_CHAN)
+		features = config->features_caller;
+	else
+		features = config->features_callee;
+	ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features);
+	for (x=0;x<FEATURES_COUNT;x++) {
+		if ((features & builtin_features[x].feature_mask) &&
+		    !ast_strlen_zero(builtin_features[x].exten)) {
+			/* Feature is up for consideration */
+			if (!strcmp(builtin_features[x].exten, code)) {
+				res = builtin_features[x].operation(chan, peer, config, code, sense);
+				break;
+			} else if (!strncmp(builtin_features[x].exten, code, strlen(code))) {
+				if (res == FEATURE_RETURN_PASSDIGITS)
+					res = FEATURE_RETURN_STOREDIGITS;
+			}
+		}
+	}
+	return res;
+}
+
+static void set_config_flags(struct ast_bridge_config *config)
+{
+	int x;
+	config->flags = 0;
+	for (x=0;x<FEATURES_COUNT;x++) {
+		if (config->features_caller & builtin_features[x].feature_mask) {
+			if (builtin_features[x].flags & AST_FEATURE_FLAG_NEEDSDTMF)
+				ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+		}
+		if (config->features_callee & builtin_features[x].feature_mask) {
+			if (builtin_features[x].flags & AST_FEATURE_FLAG_NEEDSDTMF)
+				ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
+		}
+	}
+}
+
 int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast_bridge_config *config)
 {
 	/* Copy voice back and forth between the two channels.  Give the peer
 	   the ability to transfer calls with '#<extension' syntax. */
-	int len;
 	struct ast_frame *f;
 	struct ast_channel *who;
-	char newext[256], *ptr;
+	char chan_featurecode[FEATURE_MAX_LEN + 1]="";
+	char peer_featurecode[FEATURE_MAX_LEN + 1]="";
 	int res;
 	int diff;
+	int hasfeatures=0;
+	int hadfeatures=0;
 	struct ast_option_header *aoh;
-	struct ast_channel *transferer;
-	struct ast_channel *transferee;
 	struct timeval start, end;
-	char *transferer_real_context;
+	struct ast_bridge_config backup_config;
 	int allowdisconnect_in,allowdisconnect_out,allowredirect_in,allowredirect_out;
 	char *monitor_exec;
 
+	memset(&backup_config, 0, sizeof(backup_config));
+
 	if (chan && peer) {
 		pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name);
 		pbx_builtin_setvar_helper(peer, "BRIDGEPEER", chan->name);
@@ -330,10 +567,11 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
 			pbx_exec(peer, monitor_app, monitor_exec, 1);
 	}
 	
-	allowdisconnect_in = config->allowdisconnect_in;
-	allowdisconnect_out = config->allowdisconnect_out;
-	allowredirect_in = config->allowredirect_in;
-	allowredirect_out = config->allowredirect_out;
+	allowdisconnect_in = (config->features_callee & AST_FEATURE_DISCONNECT);
+	allowdisconnect_out = (config->features_caller & AST_FEATURE_DISCONNECT);
+	allowredirect_in = (config->features_callee & AST_FEATURE_REDIRECT);
+	allowredirect_out = (config->features_caller & AST_FEATURE_REDIRECT);
+	set_config_flags(config);
 	config->firstpass = 1;
 
 	/* Answer if need be */
@@ -363,14 +601,52 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
 			diff = (end.tv_sec - start.tv_sec) * 1000;
 			diff += (end.tv_usec - start.tv_usec) / 1000;
 			config->timelimit -= diff;
-			if (config->timelimit <=0) {
-				/* We ran out of time */
-				config->timelimit = 0;
-				who = chan;
-				if (f)
-					ast_frfree(f);
-				f = NULL;
-				res = 0;
+			if (hasfeatures) {
+				/* Running on backup config, meaning a feature might be being
+				   activated, but that's no excuse to keep things going 
+				   indefinitely! */
+				if (backup_config.timelimit && ((backup_config.timelimit -= diff) <= 0)) {
+					ast_log(LOG_DEBUG, "Timed out, realtime this time!\n");
+					config->timelimit = 0;
+					who = chan;
+					if (f)
+						ast_frfree(f);
+					f = NULL;
+					res = 0;
+				} else if (config->timelimit <= 0) {
+					/* Not *really* out of time, just out of time for
+					   digits to come in for features. */
+					ast_log(LOG_DEBUG, "Timed out for feature!\n");
+					if (!ast_strlen_zero(peer_featurecode)) {
+						ast_dtmf_stream(chan, peer, peer_featurecode, 0);
+						memset(peer_featurecode, 0, sizeof(peer_featurecode));
+					}
+					if (!ast_strlen_zero(chan_featurecode)) {
+						ast_dtmf_stream(peer, chan, chan_featurecode, 0);
+						memset(chan_featurecode, 0, sizeof(chan_featurecode));
+					}
+					if (f)
+						ast_frfree(f);
+					hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
+					if (!hasfeatures) {
+						/* Restore original (possibly time modified) bridge config */
+						memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
+						memset(&backup_config, 0, sizeof(backup_config));
+					}
+					hadfeatures = hasfeatures;
+					/* Continue as we were */
+					continue;
+				}
+			} else {
+				if (config->timelimit <=0) {
+					/* We ran out of time */
+					config->timelimit = 0;
+					who = chan;
+					if (f)
+						ast_frfree(f);
+					f = NULL;
+					res = 0;
+				}
 			}
 		}
 		if (res < 0) {
@@ -412,150 +688,62 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
 			}
 		}
 		/* check for '*', if we find it it's time to disconnect */
-		if (f && (f->frametype == AST_FRAME_DTMF) &&
-			(((who == chan) && allowdisconnect_out) || ((who == peer) && allowdisconnect_in)) &&
-			(f->subclass == '*')) {
-			
-			if (option_verbose > 3)
-				ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
-			res = -1;
-			break;
-		}
-
-		if ((f->frametype == AST_FRAME_DTMF) &&
-			((allowredirect_in && who == peer) || (allowredirect_out && who == chan)) &&
-			(f->subclass == '#')) {
-				if (allowredirect_in &&  who == peer) {
-					transferer = peer;
-					transferee = chan;
-				} else {
-					transferer = chan;
-					transferee = peer;
-				}
-				if (!(transferer_real_context=pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
-				   !(transferer_real_context=pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
-					/* Use the non-macro context to transfer the call */
-					if (!ast_strlen_zero(transferer->macrocontext))
-						transferer_real_context = transferer->macrocontext;
-					else
-						transferer_real_context = transferer->context;
-				}
-				/* Start autoservice on chan while we talk
-				   to the originator */
-				ast_autoservice_start(transferee);
-				ast_moh_start(transferee, NULL);
-
-				memset(newext, 0, sizeof(newext));
-				ptr = newext;
-
-					/* Transfer */
-				if ((res=ast_streamfile(transferer, "pbx-transfer", transferer->language))) {
-					ast_moh_stop(transferee);
-					ast_autoservice_stop(transferee);
-					break;
-				}
-				if ((res=ast_waitstream(transferer, AST_DIGIT_ANY)) < 0) {
-					ast_moh_stop(transferee);
-					ast_autoservice_stop(transferee);
-					break;
-				}
-				ast_stopstream(transferer);
-				if (res > 0) {
-					/* If they've typed a digit already, handle it */
-					newext[0] = res;
-					ptr++;
-					len --;
-				}
+		if (f && (f->frametype == AST_FRAME_DTMF)) {
+			char *featurecode;
+			int sense;
+			struct ast_channel *other;
+			hadfeatures = hasfeatures;
+			/* This cannot overrun because the longest feature is one shorter than our buffer */
+			if (who == chan) {
+				other = peer;
+				sense = FEATURE_SENSE_CHAN;
+				featurecode = chan_featurecode;
+			} else  {
+				other = chan;
+				sense = FEATURE_SENSE_PEER;
+				featurecode = peer_featurecode;
+			}
+			featurecode[strlen(featurecode)] = f->subclass;
+			res = ast_feature_interpret(chan, peer, config, featurecode, sense);
+			switch(res) {
+			case FEATURE_RETURN_PASSDIGITS:
+				ast_dtmf_stream(other, who, featurecode, 0);
+				/* Fall through */
+			case FEATURE_RETURN_SUCCESS:
+				memset(featurecode, 0, sizeof(chan_featurecode));
+				break;
+			}
+			if (res >= FEATURE_RETURN_PASSDIGITS) {
 				res = 0;
-				while (strlen(newext) < sizeof(newext) - 1) {
-					res = ast_waitfordigit(transferer, transferdigittimeout);
-					if (res < 1) 
-						break;
-					if (res == '#')
-						break;
-					*(ptr++) = res;
-					if (!ast_matchmore_extension(transferer, transferer_real_context
-								, newext, 1, transferer->cid.cid_num)) {
-						break;
-					}
-				}
-
-				if (res < 0) {
-					ast_moh_stop(transferee);
-					ast_autoservice_stop(transferee);
-					break;
-				}
-				if (!strcmp(newext, ast_parking_ext())) {
-					ast_moh_stop(transferee);
-
-					if (ast_autoservice_stop(transferee))
-						res = -1;
-					else if (!ast_park_call(transferee, transferer, 0, NULL)) {
-						/* We return non-zero, but tell the PBX not to hang the channel when
-						   the thread dies -- We have to be careful now though.  We are responsible for 
-						   hanging up the channel, else it will never be hung up! */
-
-						if (transferer==peer)
-							res=AST_PBX_KEEPALIVE;
-						else
-							res=AST_PBX_NO_HANGUP_PEER;
-						break;
-					} else {
-						ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
-					}
-					/* XXX Maybe we should have another message here instead of invalid extension XXX */
-				} else if (ast_exists_extension(transferee, transferer_real_context, newext, 1, transferer->cid.cid_num)) {
-					pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
-					pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
-					ast_moh_stop(transferee);
-					res=ast_autoservice_stop(transferee);
-					if (!transferee->pbx) {
-						/* Doh!  Use our handy async_goto functions */
-						if (option_verbose > 2) 
-							ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n"
-								,transferee->name, newext, transferer_real_context);
-						if (ast_async_goto(transferee, transferer_real_context, newext, 1))
-							ast_log(LOG_WARNING, "Async goto failed :-(\n");
-						res = -1;
-					} else {
-						/* Set the channel's new extension, since it exists, using transferer context */
-						strncpy(transferee->exten, newext, sizeof(transferee->exten)-1);
-						strncpy(transferee->context, transferer_real_context, sizeof(transferee->context)-1);
-						transferee->priority = 0;
-						ast_frfree(f);
-					}
-					break;
-				} else {
-					if (option_verbose > 2)	
-						ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", newext, transferer_real_context);
-				}
-				res = ast_streamfile(transferer, "pbx-invalid", transferee->language);
-				if (res) {
-					ast_moh_stop(transferee);
-					ast_autoservice_stop(transferee);
-					break;
-				}
-				res = ast_waitstream(transferer, AST_DIGIT_ANY);
-				ast_stopstream(transferer);
-				ast_moh_stop(transferee);
-				res = ast_autoservice_stop(transferee);
-				if (res) {
-					if (option_verbose > 1)
-						ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
-				}
 			} else {
-            			if (f && (f->frametype == AST_FRAME_DTMF)) {
-                  			if (who == peer)
-                        			ast_write(chan, f);
-                  			else
-                        			ast_write(peer, f);
-            			}            
-#if 1
-				ast_log(LOG_DEBUG, "Read from %s (%d,%d)\n", who->name, f->frametype, f->subclass);
-#endif
+				ast_frfree(f);
+				break;
+			}
+			hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
+			if (hadfeatures && !hasfeatures) {
+				/* Restore backup */
+				memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
+				memset(&backup_config, 0, sizeof(struct ast_bridge_config));
+			} else if (hasfeatures) {
+				if (!hadfeatures) {
+					/* Backup configuration */
+					memcpy(&backup_config, config, sizeof(struct ast_bridge_config));
+					/* Setup temporary config options */
+					config->play_warning = 0;
+					config->features_caller &= ~(AST_FEATURE_PLAY_WARNING);
+					config->features_callee &= ~(AST_FEATURE_PLAY_WARNING);
+					config->warning_freq = 0;
+					config->warning_sound = NULL;
+					config->end_sound = NULL;
+					config->start_sound = NULL;
+					config->firstpass = 0;
+				}
+				config->timelimit = featuredigittimeout;
+				ast_log(LOG_DEBUG, "Set time limit to %ld\n", config->timelimit);
 			}
-         if (f)
-               ast_frfree(f);
+		}
+		if (f)
+			ast_frfree(f);
 	}
 	return res;
 }
@@ -813,10 +1001,8 @@ static int park_exec(struct ast_channel *chan, void *data)
 			ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park);
 
 		memset(&config,0,sizeof(struct ast_bridge_config));
-		config.allowredirect_in = 1;
-		config.allowredirect_out = 1;
-		config.allowdisconnect_out = 0;
-		config.allowdisconnect_in = 0;
+		config.features_callee |= AST_FEATURE_REDIRECT;
+		config.features_caller |= AST_FEATURE_REDIRECT;
 		config.timelimit = 0;
 		config.play_warning = 0;
 		config.warning_freq = 0;
@@ -928,6 +1114,9 @@ int load_module(void)
 	struct ast_config *cfg;
 	struct ast_variable *var;
 
+	transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+	featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+
 	ast_cli_register(&showparked);
 
 	cfg = ast_load("features.conf");
@@ -964,6 +1153,11 @@ int load_module(void)
 					transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
 				} else
 					transferdigittimeout = transferdigittimeout * 1000;
+			} else if (!strcasecmp(var->name, "featuredigittimeout")) {
+				if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (transferdigittimeout < 1)) {
+					ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
+					featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+				}
 			} else if (!strcasecmp(var->name, "courtesytone")) {
 				strncpy(courtesytone, var->value, sizeof(courtesytone) - 1);
 			} else if (!strcasecmp(var->name, "pickupexten")) {
@@ -971,6 +1165,13 @@ int load_module(void)
 			}
 			var = var->next;
 		}
+		unmap_features();
+		var = ast_variable_browse(cfg, "featuremap");
+		while(var) {
+			if (remap_feature(var->name, var->value))
+				ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+			var = var->next;
+		}
 		ast_destroy(cfg);
 	}
 	con = ast_context_find(parking_con);