Add support for configuring named groups of custom call features in
[asterisk/asterisk.git] / res / res_features.c
index faf48b4..28b41de 100644 (file)
@@ -59,25 +59,48 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/devicestate.h"
 #include "asterisk/monitor.h"
 
-#ifdef __AST_DEBUG_MALLOC
-static void FREE(void *ptr)
-{
-       free(ptr);
-}
-#else
-#define FREE free
-#endif
-
 #define DEFAULT_PARK_TIME 45000
 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
 #define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
 #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 AST_MAX_WATCHERS 256
 
+enum {
+       AST_FEATURE_FLAG_NEEDSDTMF = (1 << 0),
+       AST_FEATURE_FLAG_ONPEER =    (1 << 1),
+       AST_FEATURE_FLAG_ONSELF =    (1 << 2),
+       AST_FEATURE_FLAG_BYCALLEE =  (1 << 3),
+       AST_FEATURE_FLAG_BYCALLER =  (1 << 4),
+       AST_FEATURE_FLAG_BYBOTH  =   (3 << 3),
+};
+
+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);
+
 static char *parkedcall = "ParkedCall";
 
 static int parkaddhints = 0;                               /*!< Add parking hints automatically */
+static int parkedcalltransfers = 0;                        /*!< Enable DTMF based transfers on bridge when picking up parked calls */
+static int parkedcallreparking = 0;                        /*!< Enable DTMF based parking on bridge when picking up parked calls */
 static int parkingtime = DEFAULT_PARK_TIME;                /*!< No more than 45 seconds parked before you do something with them */
 static char parking_con[AST_MAX_EXTENSION];                /*!< Context for which parking is made accessible */
 static char parking_con_dial[AST_MAX_EXTENSION];           /*!< Context for dialback for parking (KLUDGE) */
@@ -99,8 +122,12 @@ static int adsipark;
 
 static int transferdigittimeout;
 static int featuredigittimeout;
+static int comebacktoorigin = 1;
 
 static int atxfernoanswertimeout;
+static unsigned int atxferdropcall;
+static unsigned int atxferloopdelay;
+static unsigned int atxfercallbackretries;
 
 static char *registrar = "res_features";                  /*!< Registrar for operations */
 
@@ -167,8 +194,11 @@ struct ast_bridge_thread_obj
        struct ast_bridge_config bconfig;
        struct ast_channel *chan;
        struct ast_channel *peer;
+       unsigned int return_to_pbx:1;
 };
 
+
+
 /*! \brief store context, priority and extension */
 static void set_c_e_p(struct ast_channel *chan, const char *context, const char *ext, int pri)
 {
@@ -189,14 +219,13 @@ static void check_goto_on_transfer(struct ast_channel *chan)
 
        goto_on_transfer = ast_strdupa(val);
 
-       if (!(xferchan = ast_channel_alloc(0)))
+       if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, chan->name)))
                return;
 
        for (x = goto_on_transfer; x && *x; x++) {
                if (*x == '^')
                        *x = '|';
        }
-       ast_string_field_set(xferchan, name, chan->name);
        /* Make formats okay */
        xferchan->readformat = chan->readformat;
        xferchan->writeformat = chan->writeformat;
@@ -214,17 +243,19 @@ static void check_goto_on_transfer(struct ast_channel *chan)
        }
 }
 
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name);
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate);
 
 
 static void *ast_bridge_call_thread(void *data) 
 {
        struct ast_bridge_thread_obj *tobj = data;
+       int res;
 
-       tobj->chan->appl = "Transferred Call";
+       tobj->chan->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge";
        tobj->chan->data = tobj->peer->name;
-       tobj->peer->appl = "Transferred Call";
+       tobj->peer->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge";
        tobj->peer->data = tobj->chan->name;
+
        if (tobj->chan->cdr) {
                ast_cdr_reset(tobj->chan->cdr, NULL);
                ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name);
@@ -235,10 +266,29 @@ static void *ast_bridge_call_thread(void *data)
        }
 
        ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
-       ast_hangup(tobj->chan);
-       ast_hangup(tobj->peer);
-       bzero(tobj, sizeof(*tobj)); /*! \todo XXX for safety */
+
+       if (tobj->return_to_pbx) {
+               if (!ast_check_hangup(tobj->peer)) {
+                       ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", tobj->peer->name);
+                       res = ast_pbx_start(tobj->peer);
+                       if (res != AST_PBX_SUCCESS)
+                               ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", tobj->peer->name);
+               } else
+                       ast_hangup(tobj->peer);
+               if (!ast_check_hangup(tobj->chan)) {
+                       ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", tobj->chan->name);
+                       res = ast_pbx_start(tobj->chan);
+                       if (res != AST_PBX_SUCCESS)
+                               ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", tobj->chan->name);
+               } else
+                       ast_hangup(tobj->chan);
+       } else {
+               ast_hangup(tobj->chan);
+               ast_hangup(tobj->peer);
+       }
+
        free(tobj);
+
        return NULL;
 }
 
@@ -265,10 +315,10 @@ static int adsi_announce_park(struct ast_channel *chan, char *parkingexten)
 
        snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten);
        message[0] = tmp;
-       res = adsi_load_session(chan, NULL, 0, 1);
+       res = ast_adsi_load_session(chan, NULL, 0, 1);
        if (res == -1)
                return res;
-       return adsi_print(chan, message, justify, 1);
+       return ast_adsi_print(chan, message, justify, 1);
 }
 
 /*! \brief Notify metermaids that we've changed an extension */
@@ -283,9 +333,9 @@ static void notify_metermaids(char *exten, char *context)
 }
 
 /*! \brief metermaids callback from devicestate.c */
-static int metermaidstate(const char *data)
+static enum ast_device_state metermaidstate(const char *data)
 {
-       int res = AST_DEVICE_INVALID;
+       enum ast_device_state res = AST_DEVICE_INVALID;
        char *context = ast_strdupa(data);
        char *exten;
 
@@ -298,7 +348,7 @@ static int metermaidstate(const char *data)
 
        res = ast_exists_extension(NULL, context, exten, 1, NULL);
 
-       if (!res)
+       if (res == AST_DEVICE_UNKNOWN)
                return AST_DEVICE_NOT_INUSE;
        else
                return AST_DEVICE_INUSE;
@@ -324,10 +374,13 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
        if (!ast_strlen_zero(parkingexten)) {
                if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) {
+                       ast_mutex_unlock(&parking_lock);
+                       free(pu);
                        ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con);
                        return 0;       /* Continue execution if possible */
                }
                ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten));
+               x = atoi(parkingexten);
        } else {
                /* Select parking space within range */
                parking_range = parking_stop - parking_start+1;
@@ -399,7 +452,7 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
                "Channel: %s\r\n"
                "From: %s\r\n"
                "Timeout: %ld\r\n"
-               "CallerID: %s\r\n"
+               "CallerIDNum: %s\r\n"
                "CallerIDName: %s\r\n",
                pu->parkingexten, pu->chan->name, peer ? peer->name : "",
                (long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL),
@@ -407,9 +460,9 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
                S_OR(pu->chan->cid.cid_name, "<unknown>")
                );
 
-       if (peer && adsipark && adsi_available(peer)) {
+       if (peer && adsipark && ast_adsi_available(peer)) {
                adsi_announce_park(peer, pu->parkingexten);     /* Only supports parking numbers */
-               adsi_unload_session(peer);
+               ast_adsi_unload_session(peer);
        }
 
        con = ast_context_find(parking_con);
@@ -418,11 +471,11 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        if (!con)       /* Still no context? Bad */
                ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
        else {          /* Add extension to context */
-               if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), FREE, registrar))
+               if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), ast_free, registrar))
                        notify_metermaids(pu->parkingexten, parking_con);
        }
        /* Tell the peer channel the number of the parking space */
-       if (peer && !pu->parkingnum == -1) /* Only say number if it's a number */
+       if (peer && pu->parkingnum != -1) /* Only say number if it's a number */
                ast_say_digits(peer, pu->parkingnum, "", peer->language);
        if (pu->notquiteyet) {
                /* Wake up parking thread if we're really done */
@@ -441,12 +494,10 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
        struct ast_frame *f;
 
        /* Make a new, fake channel that we'll use to masquerade in the real one */
-       if (!(chan = ast_channel_alloc(0))) {
+       if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->amaflags, "Parked/%s",rchan->name))) {
                ast_log(LOG_WARNING, "Unable to create parked channel\n");
                return -1;
        }
-       /* Let us keep track of the channel name */
-       ast_string_field_build(chan, name, "Parked/%s",rchan->name);
 
        /* Make formats okay */
        chan->readformat = rchan->readformat;
@@ -497,10 +548,10 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer,
 {
        struct ast_channel *parker;
         struct ast_channel *parkee;
+       int res = 0;
+       struct ast_module_user *u;
 
-       int res=0;
-       struct localuser *u;
-       LOCAL_USER_ADD(u);
+       u = ast_module_user_add(chan);
 
        set_peers(&parker, &parkee, peer, chan, sense);
        /* Setup the exten/priority to be s/1 since we don't know
@@ -513,7 +564,9 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer,
                res = ast_safe_sleep(chan, 1000);
        if (!res)
                res = ast_park_call(parkee, parker, 0, NULL);
-       LOCAL_USER_REMOVE(u);
+
+       ast_module_user_remove(u);
+
        if (!res) {
                if (sense == FEATURE_SENSE_CHAN)
                        res = AST_PBX_NO_HANGUP_PEER;
@@ -547,7 +600,7 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
        if (!ast_strlen_zero(courtesytone)) {
                if (ast_autoservice_start(callee_chan))
                        return -1;
-               if (ast_stream_and_wait(caller_chan, courtesytone, caller_chan->language, "")) {
+               if (ast_stream_and_wait(caller_chan, courtesytone, "")) {
                        ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
                        ast_autoservice_stop(callee_chan);
                        return -1;
@@ -589,7 +642,7 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
                        snprintf(args, len, "%s|%s|m", S_OR(touch_format, "wav"), touch_filename);
                }
 
-               for( x = 0; x < strlen(args); x++) {
+               for(x = 0; x < strlen(args); x++) {
                        if (args[x] == '/')
                                args[x] = '-';
                }
@@ -652,7 +705,7 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
        memset(xferto, 0, sizeof(xferto));
        
        /* Transfer */
-       res = ast_stream_and_wait(transferer, "pbx-transfer", transferer->language, AST_DIGIT_ANY);
+       res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
        if (res < 0) {
                finishup(transferee);
                return -1; /* error ? */
@@ -681,9 +734,20 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
                }
                /*! \todo XXX Maybe we should have another message here instead of invalid extension XXX */
        } else if (ast_exists_extension(transferee, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
-               pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
+               pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", transferee->name);
                pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
                res=finishup(transferee);
+               if (!transferer->cdr) {
+                       transferer->cdr=ast_cdr_alloc();
+                       if (transferer) {
+                               ast_cdr_init(transferer->cdr, transferer); /* initilize our channel's cdr */
+                               ast_cdr_start(transferer->cdr);
+                       }
+               }
+               if (transferer->cdr) {
+                       ast_cdr_setdestchan(transferer->cdr, transferee->name);
+                       ast_cdr_setapp(transferer->cdr, "BLINDTRANSFER","");
+               }
                if (!transferee->pbx) {
                        /* Doh!  Use our handy async_goto functions */
                        if (option_verbose > 2) 
@@ -702,7 +766,7 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
                if (option_verbose > 2) 
                        ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", xferto, transferer_real_context);
        }
-       if (ast_stream_and_wait(transferer, xferfailsound, transferer->language, AST_DIGIT_ANY) < 0 ) {
+       if (ast_stream_and_wait(transferer, xferfailsound, AST_DIGIT_ANY) < 0) {
                finishup(transferee);
                return -1;
        }
@@ -733,6 +797,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
        struct ast_channel *transferee;
        const char *transferer_real_context;
        char xferto[256] = "";
+       char callbackto[256] = "";
        int res;
        int outstate=0;
        struct ast_channel *newchan;
@@ -751,7 +816,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
        ast_indicate(transferee, AST_CONTROL_HOLD);
        
        /* Transfer */
-       res = ast_stream_and_wait(transferer, "pbx-transfer", transferer->language, AST_DIGIT_ANY);
+       res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
        if (res < 0) {
                finishup(transferee);
                return res;
@@ -768,7 +833,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
        if (res == 0) {
                ast_log(LOG_WARNING, "Did not read data.\n");
                finishup(transferee);
-               if (ast_stream_and_wait(transferer, "beeperr", transferer->language, ""))
+               if (ast_stream_and_wait(transferer, "beeperr", ""))
                        return -1;
                return FEATURE_RETURN_SUCCESS;
        }
@@ -777,103 +842,198 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
        if (!ast_exists_extension(transferer, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
                ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context);
                finishup(transferee);
-               if (ast_stream_and_wait(transferer, "beeperr", transferer->language, ""))
+               if (ast_stream_and_wait(transferer, "beeperr", ""))
                        return -1;
                return FEATURE_RETURN_SUCCESS;
        }
 
        l = strlen(xferto);
        snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context);     /* append context */
-       newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats),
-               xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name);
-       ast_indicate(transferer, -1);
-       if (!newchan) {
-               finishup(transferee);
-               /* any reason besides user requested cancel and busy triggers the failed sound */
-               if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
-                               ast_stream_and_wait(transferer, xferfailsound, transferer->language, ""))
+       newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+               xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+
+       if (!ast_check_hangup(transferer)) {
+               /* Transferer is up - old behaviour */
+               ast_indicate(transferer, -1);
+               if (!newchan) {
+                       finishup(transferee);
+                       /* any reason besides user requested cancel and busy triggers the failed sound */
+                       if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
+                               ast_stream_and_wait(transferer, xferfailsound, ""))
+                               return -1;
+                       if (ast_stream_and_wait(transferer, xfersound, ""))
+                               ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+                       return FEATURE_RETURN_SUCCESS;
+               }
+
+               if (check_compat(transferer, newchan))
                        return -1;
-               return FEATURE_RETURN_SUCCESS;
-       }
+               memset(&bconfig,0,sizeof(struct ast_bridge_config));
+               ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
+               ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
+               res = ast_bridge_call(transferer, newchan, &bconfig);
+               if (newchan->_softhangup || !transferer->_softhangup) {
+                       ast_hangup(newchan);
+                       if (ast_stream_and_wait(transferer, xfersound, ""))
+                               ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+                       finishup(transferee);
+                       transferer->_softhangup = 0;
+                       return FEATURE_RETURN_SUCCESS;
+               }
+               if (check_compat(transferee, newchan))
+                       return -1;
+               ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+               if ((ast_autoservice_stop(transferee) < 0)
+                || (ast_waitfordigit(transferee, 100) < 0)
+                || (ast_waitfordigit(newchan, 100) < 0)
+                || ast_check_hangup(transferee)
+                || ast_check_hangup(newchan)) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+               if (!xferchan) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               /* Make formats okay */
+               xferchan->readformat = transferee->readformat;
+               xferchan->writeformat = transferee->writeformat;
+               ast_channel_masquerade(xferchan, transferee);
+               ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+               xferchan->_state = AST_STATE_UP;
+               ast_clear_flag(xferchan, AST_FLAGS_ALL);
+               xferchan->_softhangup = 0;
+               if ((f = ast_read(xferchan)))
+                       ast_frfree(f);
+               newchan->_state = AST_STATE_UP;
+               ast_clear_flag(newchan, AST_FLAGS_ALL);
+               newchan->_softhangup = 0;
+               if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+                       ast_hangup(xferchan);
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               tobj->chan = xferchan;
+               tobj->peer = newchan;
+               tobj->bconfig = *config;
 
-       if (check_compat(transferer, newchan))
-               return -1;
-       memset(&bconfig,0,sizeof(struct ast_bridge_config));
-       ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
-       ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
-       res = ast_bridge_call(transferer, newchan, &bconfig);
-       if (newchan->_softhangup || newchan->_state != AST_STATE_UP || !transferer->_softhangup) {
-               ast_hangup(newchan);
-               if (ast_stream_and_wait(transferer, xfersound, transferer->language, ""))
+               if (ast_stream_and_wait(newchan, xfersound, ""))
                        ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
-               finishup(transferee);
-               transferer->_softhangup = 0;
-               return FEATURE_RETURN_SUCCESS;
-       }
-       
-       if (check_compat(transferee, newchan))
-               return -1;
+               ast_bridge_call_thread_launch(tobj);
+               return -1;      /* XXX meaning the channel is bridged ? */
+       } else if (!ast_check_hangup(transferee)) {
+               /* act as blind transfer */
+               if (ast_autoservice_stop(transferee) < 0) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
 
-       ast_indicate(transferee, AST_CONTROL_UNHOLD);
-       
-       if ((ast_autoservice_stop(transferee) < 0)
-          || (ast_waitfordigit(transferee, 100) < 0)
-          || (ast_waitfordigit(newchan, 100) < 0) 
-          || ast_check_hangup(transferee) 
-          || ast_check_hangup(newchan)) {
-               ast_hangup(newchan);
-               return -1;
-       }
+               if (!newchan) {
+                       unsigned int tries = 0;
 
-       xferchan = ast_channel_alloc(0);
-       if (!xferchan) {
-               ast_hangup(newchan);
-               return -1;
-       }
-       ast_string_field_build(xferchan, name, "Transfered/%s", transferee->name);
-       /* Make formats okay */
-       xferchan->readformat = transferee->readformat;
-       xferchan->writeformat = transferee->writeformat;
-       ast_channel_masquerade(xferchan, transferee);
-       ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
-       xferchan->_state = AST_STATE_UP;
-       ast_clear_flag(xferchan, AST_FLAGS_ALL);        
-       xferchan->_softhangup = 0;
+                       /* newchan wasn't created - we should callback to transferer */
+                       if (!ast_exists_extension(transferer, transferer_real_context, transferer->cid.cid_num, 1, transferee->cid.cid_num)) {
+                               ast_log(LOG_WARNING, "Extension %s does not exist in context %s - callback failed\n",transferer->cid.cid_num,transferer_real_context);
+                               if (ast_stream_and_wait(transferee, "beeperr", ""))
+                                       return -1;
+                               return FEATURE_RETURN_SUCCESS;
+                       }
+                       snprintf(callbackto, sizeof(callbackto), "%s@%s/n", transferer->cid.cid_num, transferer_real_context);  /* append context */
+
+                       newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+                               callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+                       while (!newchan && !atxferdropcall && tries < atxfercallbackretries) {
+                               /* Trying to transfer again */
+                               ast_autoservice_start(transferee);
+                               ast_indicate(transferee, AST_CONTROL_HOLD);
+
+                               newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+                               xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+                               if (ast_autoservice_stop(transferee) < 0) {
+                                       ast_hangup(newchan);
+                                       return -1;
+                               }
+                               if (!newchan) {
+                                       /* Transfer failed, sleeping */
+                                       if (option_debug)
+                                               ast_log(LOG_DEBUG, "Sleeping for %d ms before callback.\n", atxferloopdelay);
+                                       ast_safe_sleep(transferee, atxferloopdelay);
+                                       if (option_debug)
+                                               ast_log(LOG_DEBUG, "Trying to callback...\n");
+                                       newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+                                               callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+                               }
+                               tries++;
+                       }
+               }
+               if (!newchan)
+                       return -1;
 
-       if ((f = ast_read(xferchan)))
-               ast_frfree(f);
+               /* newchan is up, we should prepare transferee and bridge them */
+               if (check_compat(transferee, newchan))
+                       return -1;
+               ast_indicate(transferee, AST_CONTROL_UNHOLD);
 
-       newchan->_state = AST_STATE_UP;
-       ast_clear_flag(newchan, AST_FLAGS_ALL); 
-       newchan->_softhangup = 0;
+               if ((ast_waitfordigit(transferee, 100) < 0)
+                  || (ast_waitfordigit(newchan, 100) < 0)
+                  || ast_check_hangup(transferee)
+                  || ast_check_hangup(newchan)) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
 
-       tobj = ast_calloc(1, sizeof(struct ast_bridge_thread_obj));
-       if (!tobj) {
-               ast_hangup(xferchan);
-               ast_hangup(newchan);
+               xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+               if (!xferchan) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               /* Make formats okay */
+               xferchan->readformat = transferee->readformat;
+               xferchan->writeformat = transferee->writeformat;
+               ast_channel_masquerade(xferchan, transferee);
+               ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+               xferchan->_state = AST_STATE_UP;
+               ast_clear_flag(xferchan, AST_FLAGS_ALL);
+               xferchan->_softhangup = 0;
+               if ((f = ast_read(xferchan)))
+                       ast_frfree(f);
+               newchan->_state = AST_STATE_UP;
+               ast_clear_flag(newchan, AST_FLAGS_ALL);
+               newchan->_softhangup = 0;
+               if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+                       ast_hangup(xferchan);
+                       ast_hangup(newchan);
+                       return -1;
+               }
+               tobj->chan = xferchan;
+               tobj->peer = newchan;
+               tobj->bconfig = *config;
+
+               if (ast_stream_and_wait(newchan, xfersound, ""))
+                       ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+               ast_bridge_call_thread_launch(tobj);
+               return -1;      /* XXX meaning the channel is bridged ? */
+       } else {
+               /* Transferee hung up */
+               finishup(transferee);
                return -1;
        }
-       tobj->chan = xferchan;
-       tobj->peer = newchan;
-       tobj->bconfig = *config;
-
-       if (ast_stream_and_wait(newchan, xfersound, newchan->language, ""))
-               ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
-       ast_bridge_call_thread_launch(tobj);
-       return -1;      /* XXX meaning the channel is bridged ? */
 }
 
-
 /* add atxfer and automon as undefined so you can only use em if you configure them */
-#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_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 },
+#define FEATURES_COUNT ARRAY_LEN(builtin_features)
+
+AST_RWLOCK_DEFINE_STATIC(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, "" },
 };
 
 
@@ -895,6 +1055,69 @@ void ast_register_feature(struct ast_call_feature *feature)
                ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
 }
 
+/*! \brief 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(1, sizeof(*fg))))
+               return NULL;
+
+       if (ast_string_field_init(fg, 128)) {
+               free(fg);
+               return NULL;
+       }
+
+       ast_string_field_set(fg, gname, fgname);
+
+       AST_LIST_INSERT_HEAD(&feature_groups, fg, entry);
+
+       if (option_verbose >= 2) 
+               ast_verbose(VERBOSE_PREFIX_2 "Registered group '%s'\n", fg->gname);
+
+       return fg;
+}
+
+/*! \brief This function must be called while feature_groups is locked... */
+
+static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature) 
+{
+       struct feature_group_exten *fge;
+
+       if (!(fge = ast_calloc(1, sizeof(*fge))))
+               return;
+
+       if (ast_string_field_init(fge, 128)) {
+               free(fge);
+               return;
+       }
+
+       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;
+       }
+
+       ast_string_field_set(fge, exten, (ast_strlen_zero(exten) ? feature->exten : exten));
+
+       fge->feature = feature;
+
+       AST_LIST_INSERT_HEAD(&fg->features, fge, entry);                
+
+       if (option_verbose >= 2)
+               ast_verbose(VERBOSE_PREFIX_2 "Registered feature '%s' for group '%s' at exten '%s'\n", 
+                                       feature->sname, fg->gname, exten);
+}
+
 /*! \brief unregister feature from feature_list */
 void ast_unregister_feature(struct ast_call_feature *feature)
 {
@@ -918,31 +1141,93 @@ static void ast_unregister_features(void)
        AST_LIST_UNLOCK(&feature_list);
 }
 
-/*! \brief find a feature by name */
-static struct ast_call_feature *find_feature(char *name)
+/*! \brief find a call feature by name */
+static struct ast_call_feature *find_dynamic_feature(const char *name)
 {
        struct ast_call_feature *tmp;
 
-       AST_LIST_LOCK(&feature_list);
        AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
                if (!strcasecmp(tmp->sname, name))
                        break;
        }
-       AST_LIST_UNLOCK(&feature_list);
 
        return tmp;
 }
 
+/*! \brief Remove all 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_all(fge);
+                       free(fge);
+               }
+
+               ast_string_field_free_all(fg);
+               free(fg);
+       }
+       AST_RWLIST_UNLOCK(&feature_groups);
+}
+
+/*! \brief Find a group by name */
+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;
+}
+
+static struct feature_group_exten *find_group_exten(struct feature_group *fg, const char *code) {
+       struct feature_group_exten *fge = NULL;
+
+       AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+               if(!strcasecmp(fge->exten, code))
+                       break;
+       }
+
+       return fge;
+}
+
+void ast_rdlock_call_features(void)
+{
+       ast_rwlock_rdlock(&features_lock);
+}
+
+void ast_unlock_call_features(void)
+{
+       ast_rwlock_unlock(&features_lock);
+}
+
+/*! \brief find a call feature by name */
+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 NULL;
+}
+
 /*! \brief exec an app by feature */
 static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
 {
        struct ast_app *app;
        struct ast_call_feature *feature;
+       struct ast_channel *work, *idle;
        int res;
 
        AST_LIST_LOCK(&feature_list);
-       AST_LIST_TRAVERSE(&feature_list,feature,feature_entry) {
-               if (!strcasecmp(feature->exten,code))
+       AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
+               if (!strcasecmp(feature->exten, code))
                        break;
        }
        AST_LIST_UNLOCK(&feature_list);
@@ -951,21 +1236,52 @@ static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer,
                ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n");
                return -1; 
        }
-       
-       app = pbx_findapp(feature->app);
-       if (app) {
-               struct ast_channel *work = ast_test_flag(feature,AST_FEATURE_FLAG_CALLEE) ? peer : chan;
-               res = pbx_exec(work, app, feature->app_args);
-               if (res == AST_PBX_KEEPALIVE)
-                       return FEATURE_RETURN_PBX_KEEPALIVE;
-               else if (res == AST_PBX_NO_HANGUP_PEER)
-                       return FEATURE_RETURN_NO_HANGUP_PEER;
-               else if (res)
-                       return FEATURE_RETURN_SUCCESSBREAK;
+
+       if (sense == FEATURE_SENSE_CHAN) {
+               if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
+                       return FEATURE_RETURN_PASSDIGITS;
+               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 FEATURE_RETURN_PASSDIGITS;
+               if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
+                       work = peer;
+                       idle = chan;
+               } else {
+                       work = chan;
+                       idle = peer;
+               }
+       }
+
+       if (!(app = pbx_findapp(feature->app))) {
                ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app);
                return -2;
        }
+
+       ast_autoservice_start(idle);
+       
+       if (!ast_strlen_zero(feature->moh_class))
+               ast_moh_start(idle, feature->moh_class, NULL);
+
+       res = pbx_exec(work, app, feature->app_args);
+
+       if (!ast_strlen_zero(feature->moh_class))
+               ast_moh_stop(idle);
+
+       ast_autoservice_stop(idle);
+
+       if (res == AST_PBX_KEEPALIVE)
+               return FEATURE_RETURN_PBX_KEEPALIVE;
+       else if (res == AST_PBX_NO_HANGUP_PEER)
+               return FEATURE_RETURN_NO_HANGUP_PEER;
+       else if (res)
+               return FEATURE_RETURN_SUCCESSBREAK;
        
        return FEATURE_RETURN_SUCCESS;  /*! \todo XXX should probably return res */
 }
@@ -973,23 +1289,28 @@ static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer,
 static void unmap_features(void)
 {
        int x;
+
+       ast_rwlock_wrlock(&features_lock);
        for (x = 0; x < FEATURES_COUNT; x++)
                strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
+       ast_rwlock_unlock(&features_lock);
 }
 
 static int remap_feature(const char *name, const char *value)
 {
-       int x;
-       int res = -1;
+       int x, res = -1;
+
+       ast_rwlock_wrlock(&features_lock);
        for (x = 0; x < FEATURES_COUNT; x++) {
-               if (!strcasecmp(name, builtin_features[x].sname)) {
-                       ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
-                       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);
+               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_rwlock_unlock(&features_lock);
+
        return res;
 }
 
@@ -999,16 +1320,20 @@ static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *p
        struct ast_flags features;
        int res = FEATURE_RETURN_PASSDIGITS;
        struct ast_call_feature *feature;
+       struct feature_group *fg = NULL;
+       struct feature_group_exten *fge;
        const char *dynamic_features=pbx_builtin_getvar_helper(chan,"DYNAMIC_FEATURES");
+       char *tmp, *tok;
 
        if (sense == FEATURE_SENSE_CHAN)
-               ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);   
+               ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
        else
-               ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);   
+               ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
        if (option_debug > 2)
                ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features.flags);
 
-       for (x=0; x < FEATURES_COUNT; x++) {
+       ast_rwlock_rdlock(&features_lock);
+       for (x = 0; x < FEATURES_COUNT; x++) {
                if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
                    !ast_strlen_zero(builtin_features[x].exten)) {
                        /* Feature is up for consideration */
@@ -1021,30 +1346,43 @@ static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *p
                        }
                }
        }
+       ast_rwlock_unlock(&features_lock);
 
+       if (ast_strlen_zero(dynamic_features))
+               return res;
 
-       if (!ast_strlen_zero(dynamic_features)) {
-               char *tmp = ast_strdupa(dynamic_features);
-               char *tok;
+       tmp = ast_strdupa(dynamic_features);
 
-               while ((tok = strsep(&tmp, "#")) != NULL) {
-                       feature = find_feature(tok);
-                       
-                       if (feature) {
-                               /* Feature is up for consideration */
-                               if (!strcmp(feature->exten, code)) {
-                                       if (option_verbose > 2)
-                                               ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
-                                       if (sense == FEATURE_SENSE_CHAN)
-                                               res = feature->operation(chan, peer, config, code, sense);
-                                       else
-                                               res = feature->operation(peer, chan, config, code, sense);
-                                       break;
-                               } else if (!strncmp(feature->exten, code, strlen(code))) {
-                                       res = FEATURE_RETURN_STOREDIGITS;
-                               }
-                       }
+       while ((tok = strsep(&tmp, "#"))) {
+               AST_RWLIST_RDLOCK(&feature_groups);
+
+               fg = find_group(tok);
+
+               if (fg && (fge = find_group_exten(fg, code))) {
+                       res = fge->feature->operation(chan, peer, config, code, sense);
+                       AST_RWLIST_UNLOCK(&feature_groups);
+                       continue;
+               }
+
+               AST_RWLIST_UNLOCK(&feature_groups);
+               AST_LIST_LOCK(&feature_list);
+
+               if(!(feature = find_dynamic_feature(tok))) {
+                       AST_LIST_UNLOCK(&feature_list);
+                       continue;
                }
+                       
+               /* Feature is up for consideration */
+               if (!strcmp(feature->exten, code)) {
+                       if (option_verbose > 2)
+                               ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
+                       res = feature->operation(chan, peer, config, code, sense);
+                       AST_LIST_UNLOCK(&feature_list);
+                       break;
+               } else if (!strncmp(feature->exten, code, strlen(code)))
+                       res = FEATURE_RETURN_STOREDIGITS;
+
+               AST_LIST_UNLOCK(&feature_list);
        }
        
        return res;
@@ -1054,16 +1392,20 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
 {
        int x;
        
-       ast_clear_flag(config, AST_FLAGS_ALL);  
+       ast_clear_flag(config, AST_FLAGS_ALL);
+
+       ast_rwlock_rdlock(&features_lock);
        for (x = 0; x < FEATURES_COUNT; x++) {
-               if (ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF)) {
-                       if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
-                               ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+               if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF))
+                       continue;
 
-                       if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
-                               ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
-               }
+               if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
+                       ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+
+               if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
+                       ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
        }
+       ast_rwlock_unlock(&features_lock);
        
        if (chan && peer && !(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");
@@ -1075,19 +1417,21 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
 
                        /* while we have a feature */
                        while ((tok = strsep(&tmp, "#"))) {
-                               if ((feature = find_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
-                                       if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLER))
+                               AST_LIST_LOCK(&feature_list);
+                               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_CALLEE))
+                                       if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
                                                ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
                                }
+                               AST_LIST_UNLOCK(&feature_list);
                        }
                }
        }
 }
 
 /*! \todo XXX Check - this is very similar to the code in channel.c */
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name)
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate)
 {
        int state = 0;
        int cause = 0;
@@ -1101,6 +1445,14 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                ast_set_callerid(chan, cid_num, cid_name, cid_num);
                ast_channel_inherit_variables(caller, chan);    
                pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller->name);
+               if (!chan->cdr) {
+                       chan->cdr=ast_cdr_alloc();
+                       if (chan->cdr) {
+                               ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */
+                               ast_cdr_start(chan->cdr);
+                       }
+               }
+                       
                if (!ast_call(chan, data, timeout)) {
                        struct timeval started;
                        int x, len = 0;
@@ -1108,7 +1460,8 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
 
                        ast_indicate(caller, AST_CONTROL_RINGING);
                        /* support dialing of the featuremap disconnect code while performing an attended tranfer */
-                       for (x=0; x < FEATURES_COUNT; x++) {
+                       ast_rwlock_rdlock(&features_lock);
+                       for (x = 0; x < FEATURES_COUNT; x++) {
                                if (strcasecmp(builtin_features[x].sname, "disconnect"))
                                        continue;
 
@@ -1118,10 +1471,11 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                memset(dialed_code, 0, len);
                                break;
                        }
+                       ast_rwlock_unlock(&features_lock);
                        x = 0;
                        started = ast_tvnow();
                        to = timeout;
-                       while (!ast_check_hangup(caller) && timeout && (chan->_state != AST_STATE_UP)) {
+                       while (!((transferee && transferee->_softhangup) && (!igncallerstate && ast_check_hangup(caller))) && timeout && (chan->_state != AST_STATE_UP)) {
                                struct ast_frame *f = NULL;
 
                                monitor_chans[0] = caller;
@@ -1150,12 +1504,12 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                                if (f->subclass == AST_CONTROL_RINGING) {
                                                        state = f->subclass;
                                                        if (option_verbose > 2)
-                                                               ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", chan->name);
+                                                               ast_verbose(VERBOSE_PREFIX_3 "%s is ringing\n", chan->name);
                                                        ast_indicate(caller, AST_CONTROL_RINGING);
                                                } else if ((f->subclass == AST_CONTROL_BUSY) || (f->subclass == AST_CONTROL_CONGESTION)) {
                                                        state = f->subclass;
                                                        if (option_verbose > 2)
-                                                               ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", chan->name);
+                                                               ast_verbose(VERBOSE_PREFIX_3 "%s is busy\n", chan->name);
                                                        ast_indicate(caller, AST_CONTROL_BUSY);
                                                        ast_frfree(f);
                                                        f = NULL;
@@ -1176,31 +1530,34 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                } else if (caller && (active_channel == caller)) {
                                        f = ast_read(caller);
                                        if (f == NULL) { /*doh! where'd he go?*/
-                                               if (caller->_softhangup && !chan->_softhangup) {
-                                                       /* make this a blind transfer */
-                                                       ready = 1;
+                                               if (!igncallerstate) {
+                                                       if (caller->_softhangup && !chan->_softhangup) {
+                                                               /* make this a blind transfer */
+                                                               ready = 1;
+                                                               break;
+                                                       }
+                                                       state = AST_CONTROL_HANGUP;
+                                                       res = 0;
                                                        break;
                                                }
-                                               state = AST_CONTROL_HANGUP;
-                                               res = 0;
-                                               break;
-                                       }
+                                       } else {
                                        
-                                       if (f->frametype == AST_FRAME_DTMF) {
-                                               dialed_code[x++] = f->subclass;
-                                               dialed_code[x] = '\0';
-                                               if (strlen(dialed_code) == len) {
-                                                       x = 0;
-                                               } else if (x && strncmp(dialed_code, disconnect_code, x)) {
-                                                       x = 0;
+                                               if (f->frametype == AST_FRAME_DTMF) {
+                                                       dialed_code[x++] = f->subclass;
                                                        dialed_code[x] = '\0';
-                                               }
-                                               if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
-                                                       /* Caller Canceled the call */
-                                                       state = AST_CONTROL_UNHOLD;
-                                                       ast_frfree(f);
-                                                       f = NULL;
-                                                       break;
+                                                       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);
+                                                               f = NULL;
+                                                               break;
+                                                       }
                                                }
                                        }
                                }
@@ -1270,8 +1627,8 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
        int hasfeatures=0;
        int hadfeatures=0;
        struct ast_option_header *aoh;
-       struct timeval start = { 0 , 0 };
        struct ast_bridge_config backup_config;
+       struct ast_cdr *bridge_cdr;
 
        memset(&backup_config, 0, sizeof(backup_config));
 
@@ -1321,16 +1678,15 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                free(peer->cdr);
                peer->cdr = NULL;
        }
+
        for (;;) {
                struct ast_channel *other;      /* used later */
-               if (config->feature_timer)
-                       start = ast_tvnow();
 
                res = ast_channel_bridge(chan, peer, config, &f, &who);
 
                if (config->feature_timer) {
                        /* Update time limit for next pass */
-                       diff = ast_tvdiff_ms(ast_tvnow(), start);
+                       diff = ast_tvdiff_ms(ast_tvnow(), config->start_time);
                        config->feature_timer -= diff;
                        if (hasfeatures) {
                                /* Running on backup config, meaning a feature might be being
@@ -1369,7 +1725,12 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                                        hadfeatures = hasfeatures;
                                        /* Continue as we were */
                                        continue;
-                               }
+                               } else if (!f) {
+                                       /* The bridge returned without a frame and there is a feature in progress.
+                                        * However, we don't think the feature has quite yet timed out, so just
+                                        * go back into the bridge. */
+                                       continue;
+                               }
                        } else {
                                if (config->feature_timer <=0) {
                                        /* We ran out of time */
@@ -1389,7 +1750,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                
                if (!f || (f->frametype == AST_FRAME_CONTROL &&
                                (f->subclass == AST_CONTROL_HANGUP || f->subclass == AST_CONTROL_BUSY || 
-                                       f->subclass == AST_CONTROL_CONGESTION ) ) ) {
+                                       f->subclass == AST_CONTROL_CONGESTION))) {
                        res = -1;
                        break;
                }
@@ -1408,9 +1769,9 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                                if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST)
                                        ast_channel_setoption(other, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0);
                        }
-               }
-               /* check for '*', if we find it it's time to disconnect */
-               if (f->frametype == AST_FRAME_DTMF) {
+               } else if (f->frametype == AST_FRAME_DTMF_BEGIN) {
+                       /* eat it */
+               } else if (f->frametype == AST_FRAME_DTMF) {
                        char *featurecode;
                        int sense;
 
@@ -1472,28 +1833,75 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                }
                if (f)
                        ast_frfree(f);
+
+       }
+       /* arrange the cdrs */
+       bridge_cdr = ast_cdr_alloc();
+       if (bridge_cdr) {
+               if (chan->cdr && peer->cdr) { /* both of them? merge */
+                       ast_cdr_init(bridge_cdr,chan); /* seems more logicaller to use the  destination as a base, but, really, it's random */
+                       ast_cdr_start(bridge_cdr); /* now is the time to start */
+                       
+                       /* absorb the channel cdr */
+                       ast_cdr_merge(bridge_cdr, chan->cdr);
+                       ast_cdr_discard(chan->cdr); /* no posting these guys */
+                       
+                       /* absorb the peer cdr */
+                       ast_cdr_merge(bridge_cdr, peer->cdr);
+                       ast_cdr_discard(peer->cdr); /* no posting these guys */
+                       peer->cdr = NULL;
+                       chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
+               } else if (chan->cdr) {
+                       /* take the cdr from the channel - literally */
+                       ast_cdr_init(bridge_cdr,chan);
+                       /* absorb this data */
+                       ast_cdr_merge(bridge_cdr, chan->cdr);
+                       ast_cdr_discard(chan->cdr); /* no posting these guys */
+                       chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
+               } else if (peer->cdr) {
+                       /* take the cdr from the peer - literally */
+                       ast_cdr_init(bridge_cdr,peer);
+                       /* absorb this data */
+                       ast_cdr_merge(bridge_cdr, peer->cdr);
+                       ast_cdr_discard(peer->cdr); /* no posting these guys */
+                       peer->cdr = NULL;
+                       peer->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
+               } else {
+                       /* make up a new cdr */
+                       ast_cdr_init(bridge_cdr,chan); /* eh, just pick one of them */
+                       chan->cdr = bridge_cdr; /*  */
+               }
+               if (ast_strlen_zero(bridge_cdr->dstchannel)) {
+                       if (strcmp(bridge_cdr->channel, peer->name) != 0)
+                               ast_cdr_setdestchan(bridge_cdr, peer->name);
+                       else
+                               ast_cdr_setdestchan(bridge_cdr, chan->name);
+               }
        }
        return res;
 }
 
-static void post_manager_event(const char *s, char *parkingexten, struct ast_channel *chan)
+/*! \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"
-               "CallerID: %s\r\n"
+               "CallerIDNum: %s\r\n"
                "CallerIDName: %s\r\n\r\n",
-               parkingexten, 
-               chan->name,
-               S_OR(chan->cid.cid_num, "<unknown>"),
-               S_OR(chan->cid.cid_name, "<unknown>")
+               pu->parkingexten, 
+               pu->chan->name,
+               S_OR(pu->chan->cid.cid_num, "<unknown>"),
+               S_OR(pu->chan->cid.cid_name, "<unknown>")
                );
 }
 
 /*! \brief Take care of parked calls and unpark them if needed */
 static void *do_parking_thread(void *ignore)
 {
+       char parkingslot[AST_MAX_EXTENSION];
        fd_set rfds, efds;      /* results from previous select, to be preserved across loops. */
+
        FD_ZERO(&rfds);
        FD_ZERO(&efds);
 
@@ -1538,16 +1946,23 @@ static void *do_parking_thread(void *ignore)
                                        if (con) {
                                                char returnexten[AST_MAX_EXTENSION];
                                                snprintf(returnexten, sizeof(returnexten), "%s||t", peername);
-                                               ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), FREE, registrar);
+                                               ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), ast_free, registrar);
+                                       }
+                                       if (comebacktoorigin) { 
+                                               set_c_e_p(chan, parking_con_dial, peername, 1);
+                                       } else {
+                                               ast_log(LOG_WARNING, "now going to parkedcallstimeout,s,1 | ps is %d\n",pu->parkingnum);
+                                               snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
+                                               pbx_builtin_setvar_helper(pu->chan, "PARKINGSLOT", parkingslot);
+                                               set_c_e_p(chan, "parkedcallstimeout", peername, 1);
                                        }
-                                       set_c_e_p(chan, parking_con_dial, peername, 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->parkingexten, chan);
+                               post_manager_event("ParkedCallTimeOut", pu);
 
                                if (option_verbose > 1) 
                                        ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d. Returning to %s,%s,%d\n", chan->name, pu->parkingnum, chan->context, chan->exten, chan->priority);
@@ -1590,7 +2005,7 @@ static void *do_parking_thread(void *ignore)
                                        if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass ==  AST_CONTROL_HANGUP)) {
                                                if (f)
                                                        ast_frfree(f);
-                                               post_manager_event("ParkedCallGiveUp", pu->parkingexten, chan);
+                                               post_manager_event("ParkedCallGiveUp", pu);
 
                                                /* There's a problem, hang them up*/
                                                if (option_verbose > 1) 
@@ -1607,8 +2022,8 @@ static void *do_parking_thread(void *ignore)
                                                if (con) {
                                                        if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
                                                                ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
-                                               else
-                                                       notify_metermaids(pt->parkingexten, parking_con);
+                                                       else
+                                                               notify_metermaids(pt->parkingexten, parking_con);
                                                } else
                                                        ast_log(LOG_WARNING, "Whoa, no parking context?\n");
                                                free(pt);
@@ -1619,7 +2034,7 @@ static void *do_parking_thread(void *ignore)
                                                if (pu->moh_trys < 3 && !chan->generatordata) {
                                                        if (option_debug)
                                                                ast_log(LOG_DEBUG, "MOH on parked call stopped by outside source.  Restarting.\n");
-                                                       ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
+                                                       ast_indicate_data(chan, AST_CONTROL_HOLD, 
                                                                S_OR(parkmohclass, NULL),
                                                                !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
                                                        pu->moh_trys++;
@@ -1663,9 +2078,11 @@ static int park_call_exec(struct ast_channel *chan, void *data)
 {
        /* Data is unused at the moment but could contain a parking
           lot context eventually */
-       int res=0;
-       struct localuser *u;
-       LOCAL_USER_ADD(u);
+       int res = 0;
+       struct ast_module_user *u;
+
+       u = ast_module_user_add(chan);
+
        /* Setup the exten/priority to be s/1 since we don't know
           where this call should return */
        strcpy(chan->exten, "s");
@@ -1679,20 +2096,21 @@ static int park_call_exec(struct ast_channel *chan, void *data)
        /* Park the call */
        if (!res)
                res = ast_park_call(chan, chan, 0, NULL);
-       LOCAL_USER_REMOVE(u);
-       if (!res)
-               res = AST_PBX_KEEPALIVE;
-       return res;
+
+       ast_module_user_remove(u);
+
+       return !res ? AST_PBX_KEEPALIVE : res;
 }
 
 /*! \brief Pickup parked call */
 static int park_exec(struct ast_channel *chan, void *data)
 {
-       int res=0;
-       struct localuser *u;
+       int res = 0;
+       struct ast_module_user *u;
        struct ast_channel *peer=NULL;
        struct parkeduser *pu, *pl=NULL;
        struct ast_context *con;
+
        int park;
        struct ast_bridge_config config;
 
@@ -1700,7 +2118,9 @@ static int park_exec(struct ast_channel *chan, void *data)
                ast_log(LOG_WARNING, "Parkedcall requires an argument (extension number)\n");
                return -1;
        }
-       LOCAL_USER_ADD(u);
+       
+       u = ast_module_user_add(chan);
+
        park = atoi((char *)data);
        ast_mutex_lock(&parking_lock);
        pu = parkinglot;
@@ -1731,7 +2151,7 @@ static int park_exec(struct ast_channel *chan, void *data)
                        "Exten: %s\r\n"
                        "Channel: %s\r\n"
                        "From: %s\r\n"
-                       "CallerID: %s\r\n"
+                       "CallerIDNum: %s\r\n"
                        "CallerIDName: %s\r\n",
                        pu->parkingexten, pu->chan->name, chan->name,
                        S_OR(pu->chan->cid.cid_num, "<unknown>"),
@@ -1751,9 +2171,9 @@ static int park_exec(struct ast_channel *chan, void *data)
                        int error = 0;
                        ast_indicate(peer, AST_CONTROL_UNHOLD);
                        if (parkedplay == 0) {
-                               error = ast_stream_and_wait(chan, courtesytone, chan->language, "");
+                               error = ast_stream_and_wait(chan, courtesytone, "");
                        } else if (parkedplay == 1) {
-                               error = ast_stream_and_wait(peer, courtesytone, chan->language, "");
+                               error = ast_stream_and_wait(peer, courtesytone, "");
                        } else if (parkedplay == 2) {
                                if (!ast_streamfile(chan, courtesytone, chan->language) &&
                                                !ast_streamfile(peer, courtesytone, chan->language)) {
@@ -1784,35 +2204,43 @@ static int park_exec(struct ast_channel *chan, void *data)
                if (option_verbose > 2) 
                        ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park);
 
+               pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name);
+               ast_cdr_setdestchan(chan->cdr, peer->name);
                memset(&config, 0, sizeof(struct ast_bridge_config));
-               ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
-               ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
-               config.timelimit = 0;
-               config.play_warning = 0;
-               config.warning_freq = 0;
-               config.warning_sound=NULL;
+               if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH))
+                       ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
+               if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH))
+                       ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
+               if ((parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkedcallreparking == AST_FEATURE_FLAG_BYBOTH))
+                       ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
+               if ((parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkedcallreparking == AST_FEATURE_FLAG_BYBOTH))
+                       ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
                res = ast_bridge_call(chan, peer, &config);
 
+               pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name);
+               ast_cdr_setdestchan(chan->cdr, peer->name);
+
                /* Simulate the PBX hanging up */
                if (res != AST_PBX_NO_HANGUP_PEER)
                        ast_hangup(peer);
                return res;
        } else {
                /*! \todo XXX Play a message XXX */
-               if (ast_stream_and_wait(chan, "pbx-invalidpark", chan->language, ""))
+               if (ast_stream_and_wait(chan, "pbx-invalidpark", ""))
                        ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name);
                if (option_verbose > 2) 
                        ast_verbose(VERBOSE_PREFIX_3 "Channel %s tried to talk to nonexistent parked call %d\n", chan->name, park);
                res = -1;
        }
-       LOCAL_USER_REMOVE(u);
+
+       ast_module_user_remove(u);
+
        return res;
 }
 
 static int handle_showfeatures(int fd, int argc, char *argv[])
 {
        int i;
-       int fcount;
        struct ast_call_feature *feature;
        char format[] = "%-25s %-7s %-7s\n";
 
@@ -1821,23 +2249,20 @@ static int handle_showfeatures(int fd, int argc, char *argv[])
 
        ast_cli(fd, format, "Pickup", "*8", ast_pickup_ext());          /* default hardcoded above, so we'll hardcode it here */
 
-       fcount = sizeof(builtin_features) / sizeof(builtin_features[0]);
-
-       for (i = 0; i < fcount; i++)
-       {
+       ast_rwlock_rdlock(&features_lock);
+       for (i = 0; i < FEATURES_COUNT; i++)
                ast_cli(fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
-       }
+       ast_rwlock_unlock(&features_lock);
+
        ast_cli(fd, "\n");
        ast_cli(fd, format, "Dynamic Feature", "Default", "Current");
        ast_cli(fd, format, "---------------", "-------", "-------");
-       if (AST_LIST_EMPTY(&feature_list)) {
+       if (AST_LIST_EMPTY(&feature_list))
                ast_cli(fd, "(none)\n");
-       }
        else {
                AST_LIST_LOCK(&feature_list);
-               AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
+               AST_LIST_TRAVERSE(&feature_list, feature, feature_entry)
                        ast_cli(fd, format, feature->sname, "no def", feature->exten);  
-               }
                AST_LIST_UNLOCK(&feature_list);
        }
        ast_cli(fd, "\nCall parking\n");
@@ -1850,13 +2275,129 @@ static int handle_showfeatures(int fd, int argc, char *argv[])
        return RESULT_SUCCESS;
 }
 
+static char mandescr_bridge[] =
+"Description: Bridge together two channels already in the PBX\n"
+"Variables: ( Headers marked with * are required )\n"
+"   *Channel1: Channel to Bridge to Channel2\n"
+"   *Channel2: Channel to Bridge to Channel1\n"
+"        Tone: (Yes|No) Play courtesy tone to Channel 2\n"
+"\n";
+
+static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan)
+{
+       ast_moh_stop(chan);
+       ast_mutex_lock(&chan->lock);
+       ast_setstate(tmpchan, chan->_state);
+       tmpchan->readformat = chan->readformat;
+       tmpchan->writeformat = chan->writeformat;
+       ast_channel_masquerade(tmpchan, chan);
+       ast_mutex_lock(&tmpchan->lock);
+       ast_do_masquerade(tmpchan);
+       /* when returning from bridge, the channel will continue at the next priority */
+       ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1);
+       ast_mutex_unlock(&tmpchan->lock);
+       ast_mutex_unlock(&chan->lock);
+}
+
+static int action_bridge(struct mansession *s, const struct message *m)
+{
+       const char *channela = astman_get_header(m, "Channel1");
+       const char *channelb = astman_get_header(m, "Channel2");
+       const char *playtone = astman_get_header(m, "Tone");
+       struct ast_channel *chana = NULL, *chanb = NULL;
+       struct ast_channel *tmpchana = NULL, *tmpchanb = NULL;
+       struct ast_bridge_thread_obj *tobj = NULL;
+
+       /* make sure valid channels were specified */
+       if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) {
+               chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela));
+               chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb));
+               if (chana)
+                       ast_mutex_unlock(&chana->lock);
+               if (chanb)
+                       ast_mutex_unlock(&chanb->lock);
+
+               /* send errors if any of the channels could not be found/locked */
+               if (!chana) {
+                       char buf[256];
+                       snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela);
+                       astman_send_error(s, m, buf);
+                       return 0;
+               }
+               if (!chanb) {
+                       char buf[256];
+                       snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb);
+                       astman_send_error(s, m, buf);
+                       return 0;
+               }
+       } else {
+               astman_send_error(s, m, "Missing channel parameter in request");
+               return 0;
+       }
+
+       /* Answer the channels if needed */
+       if (chana->_state != AST_STATE_UP)
+               ast_answer(chana);
+       if (chanb->_state != AST_STATE_UP)
+               ast_answer(chanb);
+
+       /* create the placeholder channels and grab the other channels */
+       if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, 
+               NULL, NULL, 0, "Bridge/%s", chana->name))) {
+               astman_send_error(s, m, "Unable to create temporary channel!");
+               return 1;
+       }
+
+       if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, 
+               NULL, NULL, 0, "Bridge/%s", chanb->name))) {
+               astman_send_error(s, m, "Unable to create temporary channels!");
+               ast_channel_free(tmpchana);
+               return 1;
+       }
+
+       do_bridge_masquerade(chana, tmpchana);
+       do_bridge_masquerade(chanb, tmpchanb);
+       
+       /* make the channels compatible, send error if we fail doing so */
+       if (ast_channel_make_compatible(tmpchana, tmpchanb)) {
+               ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name);
+               astman_send_error(s, m, "Could not make channels compatible for manager bridge");
+               ast_hangup(tmpchana);
+               ast_hangup(tmpchanb);
+               return 1;
+       }
+
+       /* setup the bridge thread object and start the bridge */
+       if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+               ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno));
+               astman_send_error(s, m, "Unable to spawn a new bridge thread");
+               ast_hangup(tmpchana);
+               ast_hangup(tmpchanb);
+               return 1;
+       }
+
+       tobj->chan = tmpchana;
+       tobj->peer = tmpchanb;
+       tobj->return_to_pbx = 1;
+       
+       if (ast_true(playtone)) {
+               if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) {
+                       if (ast_waitstream(tmpchanb, "") < 0)
+                               ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name);
+               }
+       }
+
+       ast_bridge_call_thread_launch(tobj);
+
+       astman_send_ack(s, m, "Launched bridge thread with success");
+
+       return 0;
+}
+
 static char showfeatures_help[] =
-"Usage: show features\n"
+"Usage: feature list\n"
 "       Lists currently configured features.\n";
 
-static struct ast_cli_entry showfeatures =
-{ { "show", "features", NULL }, handle_showfeatures, "Lists configured features", showfeatures_help };
-
 static int handle_parkedcalls(int fd, int argc, char *argv[])
 {
        struct parkeduser *cur;
@@ -1875,7 +2416,7 @@ static int handle_parkedcalls(int fd, int argc, char *argv[])
                numparked++;
        }
        ast_mutex_unlock(&parking_lock);
-       ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : "");
+       ast_cli(fd, "%d parked call%s.\n", numparked, ESS(numparked));
 
 
        return RESULT_SUCCESS;
@@ -1885,48 +2426,55 @@ static char showparked_help[] =
 "Usage: show parkedcalls\n"
 "       Lists currently parked calls.\n";
 
-static struct ast_cli_entry showparked =
-{ { "show", "parkedcalls", NULL }, handle_parkedcalls, "Lists parked calls", showparked_help };
+static struct ast_cli_entry cli_features[] = {
+       { { "feature", "show", NULL },
+       handle_showfeatures, "Lists configured features",
+       showfeatures_help },
+
+       { { "show", "parkedcalls", NULL },
+       handle_parkedcalls, "Lists parked calls",
+       showparked_help },
+};
 
 /*! \brief Dump lot status */
-static int manager_parking_status( struct mansession *s, struct message *m )
+static int manager_parking_status(struct mansession *s, const struct message *m)
 {
        struct parkeduser *cur;
-       char *id = astman_get_header(m,"ActionID");
+       const char *id = astman_get_header(m, "ActionID");
        char idText[256] = "";
 
        if (!ast_strlen_zero(id))
-               snprintf(idText, 256, "ActionID: %s\r\n", id);
+               snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
 
        astman_send_ack(s, m, "Parked calls will follow");
 
-        ast_mutex_lock(&parking_lock);
+       ast_mutex_lock(&parking_lock);
 
-       for (cur=parkinglot; cur; cur = cur->next) {
+       for (cur = parkinglot; cur; cur = cur->next) {
                astman_append(s, "Event: ParkedCall\r\n"
                        "Exten: %d\r\n"
                        "Channel: %s\r\n"
                        "From: %s\r\n"
                        "Timeout: %ld\r\n"
-                       "CallerID: %s\r\n"
+                       "CallerIDNum: %s\r\n"
                        "CallerIDName: %s\r\n"
                        "%s"
                        "\r\n",
-                        cur->parkingnum, cur->chan->name, cur->peername,
-                        (long)cur->start.tv_sec + (long)(cur->parkingtime/1000) - (long)time(NULL),
+                       cur->parkingnum, cur->chan->name, cur->peername,
+                       (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
                        S_OR(cur->chan->cid.cid_num, ""),       /* XXX in other places it is <unknown> */
                        S_OR(cur->chan->cid.cid_name, ""),
                        idText);
-        }
+       }
 
        astman_append(s,
                "Event: ParkedCallsComplete\r\n"
                "%s"
                "\r\n",idText);
 
-        ast_mutex_unlock(&parking_lock);
+       ast_mutex_unlock(&parking_lock);
 
-        return RESULT_SUCCESS;
+       return RESULT_SUCCESS;
 }
 
 static char mandescr_park[] =
@@ -1936,11 +2484,11 @@ static char mandescr_park[] =
 "      *Channel2: Channel to announce park info to (and return to if timeout)\n"
 "      Timeout: Number of milliseconds to wait before callback.\n";  
 
-static int manager_park(struct mansession *s, struct message *m)
+static int manager_park(struct mansession *s, const struct message *m)
 {
-       char *channel = astman_get_header(m, "Channel");
-       char *channel2 = astman_get_header(m, "Channel2");
-       char *timeout = astman_get_header(m, "Timeout");
+       const char *channel = astman_get_header(m, "Channel");
+       const char *channel2 = astman_get_header(m, "Channel2");
+       const char *timeout = astman_get_header(m, "Timeout");
        char buf[BUFSIZ];
        int to = 0;
        int res = 0;
@@ -1996,7 +2544,7 @@ int ast_pickup_call(struct ast_channel *chan)
        struct ast_channel *cur = NULL;
        int res = -1;
 
-       while ( (cur = ast_channel_walk_locked(cur)) != NULL) {
+       while ((cur = ast_channel_walk_locked(cur)) != NULL) {
                if (!cur->pbx && 
                        (cur != chan) &&
                        (chan->pickupgroup & cur->callgroup) &&
@@ -2045,11 +2593,22 @@ static int load_config(void)
 {
        int start = 0, end = 0;
        int res;
+       int i;
        struct ast_context *con = NULL;
        struct ast_config *cfg = NULL;
        struct ast_variable *var = NULL;
+       struct feature_group *fg = NULL;
        char old_parking_ext[AST_MAX_EXTENSION];
        char old_parking_con[AST_MAX_EXTENSION] = "";
+       char *ctg; 
+       static const char *categories[] = { 
+               /* Categories in features.conf that are not
+                * to be parsed as group categories
+                */
+               "general",
+               "featuremap",
+               "applicationmap"
+       };
 
        if (!ast_strlen_zero(parking_con)) {
                strcpy(old_parking_ext, parking_ext);
@@ -2069,153 +2628,234 @@ static int load_config(void)
        parking_stop = 750;
        parkfindnext = 0;
        adsipark = 0;
+       comebacktoorigin = 1;
        parkaddhints = 0;
+       parkedcalltransfers = 0;
+       parkedcallreparking = 0;
 
        transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
        featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
        atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+       atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+       atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
+       atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
 
        cfg = ast_config_load("features.conf");
-       if (cfg) {
-               for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
-                       if (!strcasecmp(var->name, "parkext")) {
-                               ast_copy_string(parking_ext, var->value, sizeof(parking_ext));
-                       } else if (!strcasecmp(var->name, "context")) {
-                               ast_copy_string(parking_con, var->value, sizeof(parking_con));
-                       } else if (!strcasecmp(var->name, "parkingtime")) {
-                               if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
-                                       ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
-                                       parkingtime = DEFAULT_PARK_TIME;
-                               } else
-                                       parkingtime = parkingtime * 1000;
-                       } else if (!strcasecmp(var->name, "parkpos")) {
-                               if (sscanf(var->value, "%d-%d", &start, &end) != 2) {
-                                       ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of parking.conf\n", var->lineno);
-                               } else {
-                                       parking_start = start;
-                                       parking_stop = end;
-                               }
-                       } else if (!strcasecmp(var->name, "findslot")) {
-                               parkfindnext = (!strcasecmp(var->value, "next"));
-                       } else if (!strcasecmp(var->name, "parkinghints")) {
-                               parkaddhints = ast_true(var->value);
-                       } else if (!strcasecmp(var->name, "adsipark")) {
-                               adsipark = ast_true(var->value);
-                       } else if (!strcasecmp(var->name, "transferdigittimeout")) {
-                               if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
-                                       ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
-                                       transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
-                               } else
-                                       transferdigittimeout = transferdigittimeout * 1000;
-                       } else if (!strcasecmp(var->name, "featuredigittimeout")) {
-                               if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
-                                       ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
-                                       featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
-                               }
-                       } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) {
-                               if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) {
-                                       ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value);
-                                       atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
-                               } else
-                                       atxfernoanswertimeout = atxfernoanswertimeout * 1000;
-                       } else if (!strcasecmp(var->name, "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, "parkedmusicclass")) {
-                               ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass));
+       if (!cfg) {
+               ast_log(LOG_WARNING,"Could not load features.conf\n");
+               return AST_MODULE_LOAD_DECLINE;
+       }
+       for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+               if (!strcasecmp(var->name, "parkext")) {
+                       ast_copy_string(parking_ext, var->value, sizeof(parking_ext));
+               } else if (!strcasecmp(var->name, "context")) {
+                       ast_copy_string(parking_con, var->value, sizeof(parking_con));
+               } else if (!strcasecmp(var->name, "parkingtime")) {
+                       if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
+                               parkingtime = DEFAULT_PARK_TIME;
+                       } else
+                               parkingtime = parkingtime * 1000;
+               } else if (!strcasecmp(var->name, "parkpos")) {
+                       if (sscanf(var->value, "%d-%d", &start, &end) != 2) {
+                               ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of parking.conf\n", var->lineno);
+                       } else {
+                               parking_start = start;
+                               parking_stop = end;
                        }
+               } else if (!strcasecmp(var->name, "findslot")) {
+                       parkfindnext = (!strcasecmp(var->value, "next"));
+               } else if (!strcasecmp(var->name, "parkinghints")) {
+                       parkaddhints = ast_true(var->value);
+               } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
+                       if (!strcasecmp(var->value, "both"))
+                               parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+                       else if (!strcasecmp(var->value, "caller"))
+                               parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+                       else if (!strcasecmp(var->value, "callee"))
+                               parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
+               } else if (!strcasecmp(var->name, "parkedcallreparking")) {
+                       if (!strcasecmp(var->value, "both"))
+                               parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+                       else if (!strcasecmp(var->value, "caller"))
+                               parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+                       else if (!strcasecmp(var->value, "callee"))
+                               parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
+               } else if (!strcasecmp(var->name, "adsipark")) {
+                       adsipark = ast_true(var->value);
+               } else if (!strcasecmp(var->name, "transferdigittimeout")) {
+                       if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
+                               transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+                       } else
+                               transferdigittimeout = transferdigittimeout * 1000;
+               } else if (!strcasecmp(var->name, "featuredigittimeout")) {
+                       if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
+                               featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+                       }
+               } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) {
+                       if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value);
+                               atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+                       } else
+                               atxfernoanswertimeout = atxfernoanswertimeout * 1000;
+               } else if (!strcasecmp(var->name, "atxferloopdelay")) {
+                       if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value);
+                               atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+                       } else 
+                               atxferloopdelay *= 1000;
+               } else if (!strcasecmp(var->name, "atxferdropcall")) {
+                       atxferdropcall = ast_true(var->value);
+               } else if (!strcasecmp(var->name, "atxfercallbackretries")) {
+                       if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+                               ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value);
+                               atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
+                       }
+               } else if (!strcasecmp(var->name, "courtesytone")) {
+                       ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
+               }  else if (!strcasecmp(var->name, "parkedplay")) {
+                       if (!strcasecmp(var->value, "both"))
+                               parkedplay = 2;
+                       else if (!strcasecmp(var->value, "parked"))
+                               parkedplay = 1;
+                       else
+                               parkedplay = 0;
+               } else if (!strcasecmp(var->name, "xfersound")) {
+                       ast_copy_string(xfersound, var->value, sizeof(xfersound));
+               } else if (!strcasecmp(var->name, "xferfailsound")) {
+                       ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound));
+               } else if (!strcasecmp(var->name, "pickupexten")) {
+                       ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext));
+               } else if (!strcasecmp(var->name, "comebacktoorigin")) {
+                       comebacktoorigin = ast_true(var->value);
+               } else if (!strcasecmp(var->name, "parkedmusicclass")) {
+                       ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass));
                }
+       }
 
-               unmap_features();
-               for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) {
-                       if (remap_feature(var->name, var->value))
-                               ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+       unmap_features();
+       for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) {
+               if (remap_feature(var->name, var->value))
+                       ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+       }
+
+       /* Map a key combination to an application*/
+       ast_unregister_features();
+       for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) {
+               char *tmp_val = ast_strdupa(var->value);
+               char *exten, *activateon, *activatedby, *app, *app_args, *moh_class; 
+               struct ast_call_feature *feature;
+
+               /* strsep() sets the argument to NULL if match not found, and it
+                * is safe to use it with a NULL argument, so we don't check
+                * between calls.
+                */
+               exten = strsep(&tmp_val,",");
+               activatedby = strsep(&tmp_val,",");
+               app = strsep(&tmp_val,",");
+               app_args = strsep(&tmp_val,",");
+               moh_class = strsep(&tmp_val,",");
+
+               activateon = strsep(&activatedby, "/"); 
+
+               /*! \todo XXX var_name or app_args ? */
+               if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) {
+                       ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",
+                               app, exten, activateon, var->name);
+                       continue;
                }
 
-               /* Map a key combination to an application*/
-               ast_unregister_features();
-               for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) {
-                       char *tmp_val = ast_strdup(var->value);
-                       char *exten, *party=NULL, *app=NULL, *app_args=NULL; 
-
-                       if (!tmp_val) { 
-                               /*! \todo XXX No memory. We should probably break, but at least we do not
-                                * insist on this entry or we could be stuck in an
-                                * infinite loop.
-                                */
-                               continue;
-                       }
+               AST_LIST_LOCK(&feature_list);
+               if ((feature = find_dynamic_feature(var->name))) {
+                       AST_LIST_UNLOCK(&feature_list);
+                       ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name);
+                       continue;
+               }
+               AST_LIST_UNLOCK(&feature_list);
+                               
+               if (!(feature = ast_calloc(1, sizeof(*feature))))
+                       continue;                                       
 
-                       /* strsep() sets the argument to NULL if match not found, and it
-                        * is safe to use it with a NULL argument, so we don't check
-                        * between calls.
-                        */
-                       exten = strsep(&tmp_val,",");
-                       party = strsep(&tmp_val,",");
-                       app = strsep(&tmp_val,",");
-                       app_args = strsep(&tmp_val,",");
-
-                       /*! \todo XXX var_name or app_args ? */
-                       if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(party) || ast_strlen_zero(var->name)) {
-                               ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",app,exten,party,var->name);
-                               free(tmp_val);
-                               continue;
-                       }
+               ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN);
+               ast_copy_string(feature->app, app, FEATURE_APP_LEN);
+               ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN);
+               
+               if (app_args) 
+                       ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN);
 
-                       {
-                               struct ast_call_feature *feature;
-                               int mallocd = 0;
-                               
-                               if (!(feature = find_feature(var->name))) {
-                                       mallocd = 1;
-                                       
-                                       if (!(feature = ast_calloc(1, sizeof(*feature)))) {
-                                               free(tmp_val);
-                                               continue;                                       
-                                       }
-                               }
+               if (moh_class)
+                       ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN);
+                       
+               ast_copy_string(feature->exten, exten, sizeof(feature->exten));
+               feature->operation = feature_exec_app;
+               ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF);
+
+               /* Allow caller and calle to be specified for backwards compatability */
+               if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller"))
+                       ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF);
+               else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee"))
+                       ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER);
+               else {
+                       ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s',"
+                               " must be 'self', or 'peer'\n", var->name);
+                       continue;
+               }
 
-                               ast_copy_string(feature->sname,var->name,FEATURE_SNAME_LEN);
-                               ast_copy_string(feature->app,app,FEATURE_APP_LEN);
-                               ast_copy_string(feature->exten, exten,FEATURE_EXTEN_LEN);
-                               free(tmp_val);
-                               
-                               if (app_args) 
-                                       ast_copy_string(feature->app_args,app_args,FEATURE_APP_ARGS_LEN);
-                               
-                               ast_copy_string(feature->exten, exten,sizeof(feature->exten));
-                               feature->operation=feature_exec_app;
-                               ast_set_flag(feature,AST_FEATURE_FLAG_NEEDSDTMF);
-                               
-                               if (!strcasecmp(party,"caller"))
-                                       ast_set_flag(feature,AST_FEATURE_FLAG_CALLER);
-                               else if (!strcasecmp(party, "callee"))
-                                       ast_set_flag(feature,AST_FEATURE_FLAG_CALLEE);
-                               else {
-                                       ast_log(LOG_NOTICE, "Invalid party specification for feature '%s', must be caller, or callee\n", var->name);
-                                       continue;
-                               }
+               if (ast_strlen_zero(activatedby))
+                       ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
+               else if (!strcasecmp(activatedby, "caller"))
+                       ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER);
+               else if (!strcasecmp(activatedby, "callee"))
+                       ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE);
+               else if (!strcasecmp(activatedby, "both"))
+                       ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
+               else {
+                       ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s',"
+                               " must be 'caller', or 'callee', or 'both'\n", var->name);
+                       continue;
+               }
 
-                               ast_register_feature(feature);
-                               /* XXX do we need to free it if mallocd ? */
-                               
-                               if (option_verbose >=1)
-                                       ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s' with code '%s'\n", var->name, app, exten);  
+               ast_register_feature(feature);
+                       
+               if (option_verbose >= 1)
+                       ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);  
+       }
+
+       ast_unregister_groups();
+       AST_RWLIST_WRLOCK(&feature_groups);
+
+       ctg = NULL;
+       struct ast_call_feature *feature;
+       while ((ctg = ast_category_browse(cfg, ctg))) {
+               for (i = 0; i < ARRAY_LEN(categories); i++) {
+                       if (!strcasecmp(categories[i], ctg))
+                               break;
+               }
+
+               if (i < ARRAY_LEN(categories)) 
+                       continue;
+
+               if (!(fg = register_group(ctg)))
+                       continue;
+
+               for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
+                       AST_LIST_LOCK(&feature_list);
+                       if(!(feature = find_dynamic_feature(var->name)) && 
+                          !(feature = ast_find_call_feature(var->name))) {
+                               AST_LIST_UNLOCK(&feature_list);
+                               ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
+                               continue;
                        }
-               }        
+                       AST_LIST_UNLOCK(&feature_list);
+
+                       register_group_feature(fg, var->value, feature);
+               }
        }
+
+       AST_RWLIST_UNLOCK(&feature_groups);
+
        ast_config_destroy(cfg);
 
        /* Remove the old parking extension */
@@ -2230,7 +2870,7 @@ static int load_config(void)
                ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
                return -1;
        }
-       res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
+       res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar);
        if (parkaddhints)
                park_add_hints(parking_con, parking_start, parking_stop);
        if (!res)
@@ -2239,31 +2879,180 @@ static int load_config(void)
 
 }
 
-static int reload(void *mod)
+static char *app_bridge = "Bridge";
+static char *bridge_synopsis = "Bridge two channels";
+static char *bridge_descrip =
+"Usage: Bridge(channel[|options])\n"
+"      Allows the ability to bridge two channels via the dialplan.\n"
+"The current channel is bridged to the specified 'channel'.\n"
+"The following options are supported:\n"
+"   p - Play a courtesy tone to 'channel'.\n"
+"BRIDGERESULT dial plan variable will contain SUCCESS, FAILURE, LOOP, NONEXISTENT or INCOMPATIBLE.\n";
+
+enum {
+       BRIDGE_OPT_PLAYTONE = (1 << 0),
+};
+
+AST_APP_OPTIONS(bridge_exec_options, BEGIN_OPTIONS
+       AST_APP_OPTION('p', BRIDGE_OPT_PLAYTONE)
+END_OPTIONS );
+
+static int bridge_exec(struct ast_channel *chan, void *data)
+{
+       struct ast_module_user *u;
+       struct ast_channel *current_dest_chan, *final_dest_chan;
+       char *tmp_data  = NULL;
+       struct ast_flags opts = { 0, };
+       struct ast_bridge_config bconfig = { { 0, }, };
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(dest_chan);
+               AST_APP_ARG(options);
+       );
+       
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "Bridge require at least 1 argument specifying the other end of the bridge\n");
+               return -1;
+       }
+       
+       u = ast_module_user_add(chan);
+
+       tmp_data = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, tmp_data);
+       if (!ast_strlen_zero(args.options))
+               ast_app_parse_options(bridge_exec_options, &opts, NULL, args.options);
+
+       /* avoid bridge with ourselves */
+       if (!strncmp(chan->name, args.dest_chan, 
+               strlen(chan->name) < strlen(args.dest_chan) ? 
+               strlen(chan->name) : strlen(args.dest_chan))) {
+               ast_log(LOG_WARNING, "Unable to bridge channel %s with itself\n", chan->name);
+               manager_event(EVENT_FLAG_CALL, "BridgeExec",
+                                       "Response: Failed\r\n"
+                                       "Reason: Unable to bridge channel to itself\r\n"
+                                       "Channel1: %s\r\n"
+                                       "Channel2: %s\r\n",
+                                       chan->name, args.dest_chan);
+               pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "LOOP");
+               ast_module_user_remove(u);
+               return 0;
+       }
+
+       /* make sure we have a valid end point */
+       if (!(current_dest_chan = ast_get_channel_by_name_prefix_locked(args.dest_chan, 
+               strlen(args.dest_chan)))) {
+               ast_log(LOG_WARNING, "Bridge failed because channel %s does not exists or we "
+                       "cannot get its lock\n", args.dest_chan);
+               manager_event(EVENT_FLAG_CALL, "BridgeExec",
+                                       "Response: Failed\r\n"
+                                       "Reason: Cannot grab end point\r\n"
+                                       "Channel1: %s\r\n"
+                                       "Channel2: %s\r\n", chan->name, args.dest_chan);
+               pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "NONEXISTENT");
+               ast_module_user_remove(u);
+               return 0;
+       }
+       ast_mutex_unlock(&current_dest_chan->lock);
+
+       /* answer the channel if needed */
+       if (current_dest_chan->_state != AST_STATE_UP)
+               ast_answer(current_dest_chan);
+
+       /* try to allocate a place holder where current_dest_chan will be placed */
+       if (!(final_dest_chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, 
+               NULL, NULL, 0, "Bridge/%s", current_dest_chan->name))) {
+               ast_log(LOG_WARNING, "Cannot create placeholder channel for chan %s\n", args.dest_chan);
+               manager_event(EVENT_FLAG_CALL, "BridgeExec",
+                                       "Response: Failed\r\n"
+                                       "Reason: cannot create placeholder\r\n"
+                                       "Channel1: %s\r\n"
+                                       "Channel2: %s\r\n", chan->name, args.dest_chan);
+       }
+       do_bridge_masquerade(current_dest_chan, final_dest_chan);
+
+       /* now current_dest_chan is a ZOMBIE and with softhangup set to 1 and final_dest_chan is our end point */
+       /* try to make compatible, send error if we fail */
+       if (ast_channel_make_compatible(chan, final_dest_chan) < 0) {
+               ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, final_dest_chan->name);
+               manager_event(EVENT_FLAG_CALL, "BridgeExec",
+                                       "Response: Failed\r\n"
+                                       "Reason: Could not make channels compatible for bridge\r\n"
+                                       "Channel1: %s\r\n"
+                                       "Channel2: %s\r\n", chan->name, final_dest_chan->name);
+               ast_hangup(final_dest_chan); /* may be we should return this channel to the PBX? */
+               pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE");
+               ast_module_user_remove(u);
+               return 0;
+       }
+
+       /* Report that the bridge will be successfull */
+       manager_event(EVENT_FLAG_CALL, "BridgeExec",
+                               "Response: Success\r\n"
+                               "Channel1: %s\r\n"
+                               "Channel2: %s\r\n", chan->name, final_dest_chan->name);
+
+       /* we have 2 valid channels to bridge, now it is just a matter of setting up the bridge config and starting the bridge */       
+       if (ast_test_flag(&opts, BRIDGE_OPT_PLAYTONE) && !ast_strlen_zero(xfersound)) {
+               if (!ast_streamfile(final_dest_chan, xfersound, final_dest_chan->language)) {
+                       if (ast_waitstream(final_dest_chan, "") < 0)
+                               ast_log(LOG_WARNING, "Failed to play courtesy tone on %s\n", final_dest_chan->name);
+               }
+       }
+       
+       /* do the bridge */
+       ast_bridge_call(chan, final_dest_chan, &bconfig);
+
+       /* the bridge has ended, set BRIDGERESULT to SUCCESS. If the other channel has not been hung up, return it to the PBX */
+       pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS");
+       if (!ast_check_hangup(final_dest_chan)) {
+               if (option_debug) {
+                       ast_log(LOG_DEBUG, "starting new PBX in %s,%s,%d for chan %s\n", 
+                       final_dest_chan->context, final_dest_chan->exten, 
+                       final_dest_chan->priority, final_dest_chan->name);
+               }
+
+               if (ast_pbx_start(final_dest_chan) != AST_PBX_SUCCESS) {
+                       ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", final_dest_chan->name);
+                       ast_hangup(final_dest_chan);
+               } else if (option_debug)
+                       ast_log(LOG_DEBUG, "SUCCESS continuing PBX on chan %s\n", final_dest_chan->name);
+       } else {
+               if (option_debug)
+                       ast_log(LOG_DEBUG, "hangup chan %s since the other endpoint has hung up\n", final_dest_chan->name);
+               ast_hangup(final_dest_chan);
+       }
+
+       ast_module_user_remove(u);
+
+       return 0;
+}
+
+static int reload(void)
 {
        return load_config();
 }
 
-static int load_module(void *mod)
+static int load_module(void)
 {
        int res;
-       
-       __mod_desc = mod;
+
+       ast_register_application(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip);     
+
        memset(parking_ext, 0, sizeof(parking_ext));
        memset(parking_con, 0, sizeof(parking_con));
 
        if ((res = load_config()))
                return res;
-       ast_cli_register(&showparked);
-       ast_cli_register(&showfeatures);
+       ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
        ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL);
        res = ast_register_application(parkedcall, park_exec, synopsis, descrip);
        if (!res)
                res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2);
        if (!res) {
-               ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls" );
+               ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls");
                ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park,
                        "Park a channel", mandescr_park); 
+               ast_manager_register2("Bridge", EVENT_FLAG_COMMAND, action_bridge, "Bridge two channels already in the PBX", mandescr_bridge);
        }
 
        res |= ast_devstate_prov_add("Park", metermaidstate);
@@ -2272,27 +3061,22 @@ static int load_module(void *mod)
 }
 
 
-static int unload_module(void *mod)
+static int unload_module(void)
 {
-       STANDARD_HANGUP_LOCALUSERS;
+       ast_module_user_hangup_all();
 
        ast_manager_unregister("ParkedCalls");
+       ast_manager_unregister("Bridge");
        ast_manager_unregister("Park");
-       ast_cli_unregister(&showfeatures);
-       ast_cli_unregister(&showparked);
+       ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
        ast_unregister_application(parkcall);
+       ast_unregister_application(app_bridge);
        ast_devstate_prov_del("Park");
        return ast_unregister_application(parkedcall);
 }
 
-static const char *description(void)
-{
-       return "Call Features Resource";
-}
-
-static const char *key(void)
-{
-       return ASTERISK_GPL_KEY;
-}
-
-STD_MOD(MOD_0 | NO_UNLOAD, reload, NULL, NULL);
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource",
+               .load = load_module,
+               .unload = unload_module,
+               .reload = reload,
+             );