Make features configurable and easier to implement
authorMark Spencer <markster@digium.com>
Tue, 4 Jan 2005 04:01:40 +0000 (04:01 +0000)
committerMark Spencer <markster@digium.com>
Tue, 4 Jan 2005 04:01:40 +0000 (04:01 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@4650 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_dial.c
apps/app_queue.c
channel.c
configs/features.conf.sample
include/asterisk/channel.h
res/res_features.c

index d1d76bc..0226995 100755 (executable)
@@ -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;
index 476eceb..38a550f 100755 (executable)
@@ -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)) {
index 3972002..467fbc9 100755 (executable)
--- 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;
index be442c5..dc6cda1 100755 (executable)
@@ -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
index 909e314..3e07430 100755 (executable)
@@ -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;
index 65d139b..b725564 100755 (executable)
@@ -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);