Add support for configuring named groups of custom call features in
[asterisk/asterisk.git] / res / res_features.c
index 468d656..28b41de 100644 (file)
  * \author Mark Spencer <markster@digium.com> 
  */
 
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
 #include <pthread.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/signal.h>
 #include <netinet/in.h>
 
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include "asterisk/lock.h"
 #include "asterisk/file.h"
 #include "asterisk/logger.h"
@@ -56,37 +56,64 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/manager.h"
 #include "asterisk/utils.h"
 #include "asterisk/adsi.h"
+#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
 
-static char *parkedcall = "ParkedCall";
+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),
+};
 
-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) */
-static char parking_ext[AST_MAX_EXTENSION];            /*!< Extension you type to park the call */
-static char pickup_ext[AST_MAX_EXTENSION];             /*!< Call pickup extension */
-static int parking_start;                              /*!< First available extension for parking */
-static int parking_stop;                               /*!< Last available extension for parking */
+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 char courtesytone[256];                         /*!< Courtesy tone */
-static int parkedplay = 0;                             /*!< Who to play the courtesy tone to */
-static char xfersound[256];                            /*!< Call transfer sound */
-static char xferfailsound[256];                                /*!< Call transfer failure sound */
+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) */
+static char parking_ext[AST_MAX_EXTENSION];                /*!< Extension you type to park the call */
+static char pickup_ext[AST_MAX_EXTENSION];                 /*!< Call pickup extension */
+static char parkmohclass[MAX_MUSICCLASS];                  /*!< Music class used for parking */
+static int parking_start;                                  /*!< First available extension for parking */
+static int parking_stop;                                   /*!< Last available extension for parking */
+
+static char courtesytone[256];                             /*!< Courtesy tone */
+static int parkedplay = 0;                                 /*!< Who to play the courtesy tone to */
+static char xfersound[256];                                /*!< Call transfer sound */
+static char xferfailsound[256];                            /*!< Call transfer failure sound */
 
 static int parking_offset;
 static int parkfindnext;
@@ -95,8 +122,14 @@ static int adsipark;
 
 static int transferdigittimeout;
 static int featuredigittimeout;
+static int comebacktoorigin = 1;
 
-static char *registrar = "res_features";               /*!< Registrar for operations */
+static int atxfernoanswertimeout;
+static unsigned int atxferdropcall;
+static unsigned int atxferloopdelay;
+static unsigned int atxfercallbackretries;
+
+static char *registrar = "res_features";                  /*!< Registrar for operations */
 
 /* module and CLI command definitions */
 static char *synopsis = "Answer a parked call";
@@ -116,20 +149,24 @@ static char *descrip2 = "Park():"
 "transfer to know the parking space). This application is always\n"
 "registered internally and does not need to be explicitly added\n"
 "into the dialplan, although you should include the 'parkedcalls'\n"
-"context.\n";
+"context (or the context specified in features.conf).\n\n"
+"If you set the PARKINGEXTEN variable to an extension in your\n"
+"parking context, park() will park the call on that extension, unless\n"
+"it already exists. In that case, execution will continue at next\n"
+"priority.\n" ;
 
 static struct ast_app *monitor_app = NULL;
 static int monitor_ok = 1;
 
 struct parkeduser {
-       struct ast_channel *chan;
-       struct timeval start;
-       int parkingnum;
-       /* Where to go if our parking time expires */
-       char context[AST_MAX_CONTEXT];
+       struct ast_channel *chan;                   /*!< Parking channel */
+       struct timeval start;                       /*!< Time the parking started */
+       int parkingnum;                             /*!< Parking lot */
+       char parkingexten[AST_MAX_EXTENSION];       /*!< If set beforehand, parking extension used for this call */
+       char context[AST_MAX_CONTEXT];              /*!< Where to go if our parking time expires */
        char exten[AST_MAX_EXTENSION];
        int priority;
-       int parkingtime;
+       int parkingtime;                            /*!< Maximum length in parking lot before return */
        int notquiteyet;
        char peername[1024];
        unsigned char moh_trys;
@@ -138,7 +175,7 @@ struct parkeduser {
 
 static struct parkeduser *parkinglot;
 
-AST_MUTEX_DEFINE_STATIC(parking_lock); /* protects all static variables above */
+AST_MUTEX_DEFINE_STATIC(parking_lock); /*!< protects all static variables above */
 
 static pthread_t parking_thread;
 
@@ -157,8 +194,19 @@ 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)
+{
+       ast_copy_string(chan->context, context, sizeof(chan->context));
+       ast_copy_string(chan->exten, ext, sizeof(chan->exten));
+       chan->priority = pri;
+}
+
 static void check_goto_on_transfer(struct ast_channel *chan) 
 {
        struct ast_channel *xferchan;
@@ -166,40 +214,48 @@ static void check_goto_on_transfer(struct ast_channel *chan)
        char *x, *goto_on_transfer;
        struct ast_frame *f;
 
-       if (!ast_strlen_zero(val) && (goto_on_transfer = ast_strdupa(val)) && (xferchan = ast_channel_alloc(0))) {
-               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;
-               ast_channel_masquerade(xferchan, chan);
-               ast_parseable_goto(xferchan, goto_on_transfer);
-               xferchan->_state = AST_STATE_UP;
-               ast_clear_flag(xferchan, AST_FLAGS_ALL);        
-               xferchan->_softhangup = 0;
-               if ((f = ast_read(xferchan))) {
-                       ast_frfree(f);
-                       f = NULL;
-                       ast_pbx_start(xferchan);
-               } else {
-                       ast_hangup(xferchan);
-               }
+       if (ast_strlen_zero(val))
+               return;
+
+       goto_on_transfer = ast_strdupa(val);
+
+       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 = '|';
+       }
+       /* Make formats okay */
+       xferchan->readformat = chan->readformat;
+       xferchan->writeformat = chan->writeformat;
+       ast_channel_masquerade(xferchan, chan);
+       ast_parseable_goto(xferchan, goto_on_transfer);
+       xferchan->_state = AST_STATE_UP;
+       ast_clear_flag(xferchan, AST_FLAGS_ALL);        
+       xferchan->_softhangup = 0;
+       if ((f = ast_read(xferchan))) {
+               ast_frfree(f);
+               f = NULL;
+               ast_pbx_start(xferchan);
+       } else {
+               ast_hangup(xferchan);
        }
 }
 
-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);
@@ -210,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);
-       tobj->chan = tobj->peer = NULL;
+
+       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;
 }
 
@@ -231,75 +306,125 @@ static void ast_bridge_call_thread_launch(void *data)
        pthread_setschedparam(thread, SCHED_RR, &sched);
 }
 
-
-
-static int adsi_announce_park(struct ast_channel *chan, int parkingnum)
+static int adsi_announce_park(struct ast_channel *chan, char *parkingexten)
 {
        int res;
        int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT};
        char tmp[256];
        char *message[5] = {NULL, NULL, NULL, NULL, NULL};
 
-       snprintf(tmp, sizeof(tmp), "Parked on %d", parkingnum);
+       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 */
+static void notify_metermaids(char *exten, char *context)
+{
+       if (option_debug > 3)
+               ast_log(LOG_DEBUG, "Notification of state change to metermaids %s@%s\n", exten, context);
+
+       /* Send notification to devicestate subsystem */
+       ast_device_state_changed("park:%s@%s", exten, context);
+       return;
+}
+
+/*! \brief metermaids callback from devicestate.c */
+static enum ast_device_state metermaidstate(const char *data)
+{
+       enum ast_device_state res = AST_DEVICE_INVALID;
+       char *context = ast_strdupa(data);
+       char *exten;
+
+       exten = strsep(&context, "@");
+       if (!context)
+               return res;
+       
+       if (option_debug > 3)
+               ast_log(LOG_DEBUG, "Checking state of exten %s in context %s\n", exten, context);
+
+       res = ast_exists_extension(NULL, context, exten, 1, NULL);
+
+       if (res == AST_DEVICE_UNKNOWN)
+               return AST_DEVICE_NOT_INUSE;
+       else
+               return AST_DEVICE_INUSE;
 }
 
 /*! \brief Park a call 
-       We put the user in the parking list, then wake up the parking thread to be sure it looks
+       \note We put the user in the parking list, then wake up the parking thread to be sure it looks
        after these channels too */
 int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
 {
        struct parkeduser *pu, *cur;
-       int i,x,parking_range;
-       char exten[AST_MAX_EXTENSION];
+       int i, x = -1, parking_range;
        struct ast_context *con;
+       const char *parkingexten;
        
-       if (!(pu = ast_calloc(1, sizeof(*pu)))) {
+       /* Allocate memory for parking data */
+       if (!(pu = ast_calloc(1, sizeof(*pu)))) 
                return -1;
-       }
+
+       /* Lock parking lot */
        ast_mutex_lock(&parking_lock);
-       parking_range = parking_stop - parking_start+1;
-       for (i = 0; i < parking_range; i++) {
-               x = (i + parking_offset) % parking_range + parking_start;
-               cur = parkinglot;
-               while(cur) {
-                       if (cur->parkingnum == x) 
+       /* Check for channel variable PARKINGEXTEN */
+       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;
+               for (i = 0; i < parking_range; i++) {
+                       x = (i + parking_offset) % parking_range + parking_start;
+                       cur = parkinglot;
+                       while(cur) {
+                               if (cur->parkingnum == x) 
+                                       break;
+                               cur = cur->next;
+                       }
+                       if (!cur)
                                break;
-                       cur = cur->next;
                }
-               if (!cur)
-                       break;
-       }
 
-       if (!(i < parking_range)) {
-               ast_log(LOG_WARNING, "No more parking spaces\n");
-               free(pu);
-               ast_mutex_unlock(&parking_lock);
-               return -1;
+               if (!(i < parking_range)) {
+                       ast_log(LOG_WARNING, "No more parking spaces\n");
+                       free(pu);
+                       ast_mutex_unlock(&parking_lock);
+                       return -1;
+               }
+               /* Set pointer for next parking */
+               if (parkfindnext) 
+                       parking_offset = x - parking_start + 1;
        }
-       if (parkfindnext) 
-               parking_offset = x - parking_start + 1;
+       
        chan->appl = "Parked Call";
        chan->data = NULL; 
 
        pu->chan = chan;
-       /* Start music on hold */
+       
+       /* Put the parked channel on hold if we have two different channels */
        if (chan != peer) {
-               ast_indicate(pu->chan, AST_CONTROL_HOLD);
-               ast_moh_start(pu->chan, NULL);
+               ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
+                       S_OR(parkmohclass, NULL),
+                       !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
        }
+       
        pu->start = ast_tvnow();
        pu->parkingnum = x;
-       if (timeout > 0)
-               pu->parkingtime = timeout;
-       else
-               pu->parkingtime = parkingtime;
+       pu->parkingtime = (timeout > 0) ? timeout : parkingtime;
        if (extout)
                *extout = x;
+
        if (peer) 
                ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
 
@@ -310,6 +435,7 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
        pu->next = parkinglot;
        parkinglot = pu;
+
        /* If parking a channel directly, don't quiet yet get parking running on it */
        if (peer == chan) 
                pu->notquiteyet = 1;
@@ -317,42 +443,45 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        /* Wake up the (presumably select()ing) thread */
        pthread_kill(parking_thread, SIGURG);
        if (option_verbose > 1) 
-               ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+               ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d@%s. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parking_con, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
 
+       if (pu->parkingnum != -1)
+               snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
        manager_event(EVENT_FLAG_CALL, "ParkedCall",
-               "Exten: %d\r\n"
+               "Exten: %s\r\n"
                "Channel: %s\r\n"
                "From: %s\r\n"
                "Timeout: %ld\r\n"
-               "CallerID: %s\r\n"
-               "CallerIDName: %s\r\n"
-               ,pu->parkingnum, pu->chan->name, peer ? peer->name : ""
-               ,(long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL)
-               ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
-               ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
+               "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),
+               S_OR(pu->chan->cid.cid_num, "<unknown>"),
+               S_OR(pu->chan->cid.cid_name, "<unknown>")
                );
 
-       if (peer) {
-               if (adsipark && adsi_available(peer))
-                       adsi_announce_park(peer, pu->parkingnum);
-               if (adsipark && adsi_available(peer))
-                       adsi_unload_session(peer);
+       if (peer && adsipark && ast_adsi_available(peer)) {
+               adsi_announce_park(peer, pu->parkingexten);     /* Only supports parking numbers */
+               ast_adsi_unload_session(peer);
        }
+
        con = ast_context_find(parking_con);
-       if (!con) {
+       if (!con) 
                con = ast_context_create(NULL, parking_con, registrar);
-               if (!con)
-                       ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
-       }
-       if (con) {
-               snprintf(exten, sizeof(exten), "%d", x);
-               ast_add_extension2(con, 1, exten, 1, NULL, NULL, parkedcall, strdup(exten), FREE, registrar);
+       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), ast_free, registrar))
+                       notify_metermaids(pu->parkingexten, parking_con);
        }
-       if (peer) 
+       /* Tell the peer channel the number of the parking space */
+       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 */
-               ast_moh_start(pu->chan, NULL);
+               ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
+                       S_OR(parkmohclass, NULL),
+                       !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
                pu->notquiteyet = 0;
                pthread_kill(parking_thread, SIGURG);
        }
@@ -365,29 +494,25 @@ 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))) {
-               /* 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;
-               chan->writeformat = rchan->writeformat;
-               ast_channel_masquerade(chan, rchan);
-
-               /* Setup the extensions and such */
-               ast_copy_string(chan->context, rchan->context, sizeof(chan->context));
-               ast_copy_string(chan->exten, rchan->exten, sizeof(chan->exten));
-               chan->priority = rchan->priority;
-
-               /* Make the masq execute */
-               f = ast_read(chan);
-               if (f)
-                       ast_frfree(f);
-               ast_park_call(chan, peer, timeout, extout);
-       } else {
+       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;
        }
+
+       /* Make formats okay */
+       chan->readformat = rchan->readformat;
+       chan->writeformat = rchan->writeformat;
+       ast_channel_masquerade(chan, rchan);
+
+       /* Setup the extensions and such */
+       set_c_e_p(chan, rchan->context, rchan->exten, rchan->priority);
+
+       /* Make the masq execute */
+       f = ast_read(chan);
+       if (f)
+               ast_frfree(f);
+
+       ast_park_call(chan, peer, timeout, extout);
        return 0;
 }
 
@@ -403,44 +528,82 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
 #define FEATURE_SENSE_CHAN     (1 << 0)
 #define FEATURE_SENSE_PEER     (1 << 1)
 
+/*! \brief
+ * set caller and callee according to the direction
+ */
+static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
+       struct ast_channel *peer, struct ast_channel *chan, int sense)
+{
+       if (sense == FEATURE_SENSE_PEER) {
+               *caller = peer;
+               *callee = chan;
+       } else {
+               *callee = peer;
+               *caller = chan;
+       }
+}
+
+/*! \brief support routing for one touch call parking */
+static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
+{
+       struct ast_channel *parker;
+        struct ast_channel *parkee;
+       int res = 0;
+       struct ast_module_user *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
+          where this call should return */
+       strcpy(chan->exten, "s");
+       chan->priority = 1;
+       if (chan->_state != AST_STATE_UP)
+               res = ast_answer(chan);
+       if (!res)
+               res = ast_safe_sleep(chan, 1000);
+       if (!res)
+               res = ast_park_call(parkee, parker, 0, NULL);
+
+       ast_module_user_remove(u);
+
+       if (!res) {
+               if (sense == FEATURE_SENSE_CHAN)
+                       res = AST_PBX_NO_HANGUP_PEER;
+               else
+                       res = AST_PBX_KEEPALIVE;
+       }
+       return res;
+
+}
 
 static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
 {
        char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL;
        int x = 0;
        size_t len;
-       struct ast_channel *caller_chan = NULL, *callee_chan = NULL;
-
+       struct ast_channel *caller_chan, *callee_chan;
 
-       if(sense == 2) {
-               caller_chan = peer;
-               callee_chan = chan;
-       } else {
-               callee_chan = peer;
-               caller_chan = chan;
-       }
-       
        if (!monitor_ok) {
                ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
                return -1;
        }
 
-       if (!monitor_app) { 
-               if (!(monitor_app = pbx_findapp("Monitor"))) {
-                       monitor_ok=0;
-                       ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
-                       return -1;
-               }
+       if (!monitor_app && !(monitor_app = pbx_findapp("Monitor"))) {
+               monitor_ok = 0;
+               ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
+               return -1;
        }
+
+       set_peers(&caller_chan, &callee_chan, peer, chan, sense);
+
        if (!ast_strlen_zero(courtesytone)) {
                if (ast_autoservice_start(callee_chan))
                        return -1;
-               if (!ast_streamfile(caller_chan, courtesytone, caller_chan->language)) {
-                       if (ast_waitstream(caller_chan, "") < 0) {
-                               ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
-                               ast_autoservice_stop(callee_chan);
-                               return -1;
-                       }
+               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;
                }
                if (ast_autoservice_stop(callee_chan))
                        return -1;
@@ -470,16 +633,16 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
                        snprintf(touch_filename, len, "auto-%ld-%s", (long)time(NULL), touch_monitor);
                        snprintf(args, len, "%s|%s|m", (touch_format) ? touch_format : "wav", touch_filename);
                } else {
-                       caller_chan_id = ast_strdupa(caller_chan->cid.cid_num ? caller_chan->cid.cid_num : caller_chan->name);
-                       callee_chan_id = ast_strdupa(callee_chan->cid.cid_num ? callee_chan->cid.cid_num : callee_chan->name);
+                       caller_chan_id = ast_strdupa(S_OR(caller_chan->cid.cid_num, caller_chan->name));
+                       callee_chan_id = ast_strdupa(S_OR(callee_chan->cid.cid_num, callee_chan->name));
                        len = strlen(caller_chan_id) + strlen(callee_chan_id) + 50;
                        args = alloca(len);
                        touch_filename = alloca(len);
                        snprintf(touch_filename, len, "auto-%ld-%s-%s", (long)time(NULL), caller_chan_id, callee_chan_id);
-                       snprintf(args, len, "%s|%s|m", (touch_format) ? touch_format : "wav", touch_filename);
+                       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] = '-';
                }
@@ -505,70 +668,62 @@ static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer
        return FEATURE_RETURN_HANGUP;
 }
 
+static int finishup(struct ast_channel *chan)
+{
+        ast_indicate(chan, AST_CONTROL_UNHOLD);
+  
+        return ast_autoservice_stop(chan);
+}
+
+/*! \brief Find the context for the transfer */
+static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee)
+{
+        const char *s = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
+        if (ast_strlen_zero(s))
+                s = pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT");
+        if (ast_strlen_zero(s)) /* Use the non-macro context to transfer the call XXX ? */
+                s = transferer->macrocontext;
+        if (ast_strlen_zero(s))
+                s = transferer->context;
+        return s;  
+}
+
 static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
 {
        struct ast_channel *transferer;
        struct ast_channel *transferee;
        const char *transferer_real_context;
-       char newext[256];
+       char xferto[256];
        int res;
 
-       if (sense == FEATURE_SENSE_PEER) {
-               transferer = peer;
-               transferee = chan;
-       } else {
-               transferer = chan;
-               transferee = peer;
-       }
-       if (!(transferer_real_context = pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
-          !(transferer_real_context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
-               /* Use the non-macro context to transfer the call */
-               if (!ast_strlen_zero(transferer->macrocontext))
-                       transferer_real_context = transferer->macrocontext;
-               else
-                       transferer_real_context = transferer->context;
-       }
-       /* Start autoservice on chan while we talk
-          to the originator */
-       ast_indicate(transferee, AST_CONTROL_HOLD);
+       set_peers(&transferer, &transferee, peer, chan, sense);
+       transferer_real_context = real_ctx(transferer, transferee);
+       /* Start autoservice on chan while we talk to the originator */
        ast_autoservice_start(transferee);
-       ast_moh_start(transferee, NULL);
+       ast_indicate(transferee, AST_CONTROL_HOLD);
 
-       memset(newext, 0, sizeof(newext));
+       memset(xferto, 0, sizeof(xferto));
        
        /* Transfer */
-       if ((res=ast_streamfile(transferer, "pbx-transfer", transferer->language))) {
-               ast_moh_stop(transferee);
-               ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
-               return res;
-       }
-       if ((res=ast_waitstream(transferer, AST_DIGIT_ANY)) < 0) {
-               ast_moh_stop(transferee);
-               ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
-               return res;
-       } else if (res > 0) {
-               /* If they've typed a digit already, handle it */
-               newext[0] = (char) res;
+       res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
+       if (res < 0) {
+               finishup(transferee);
+               return -1; /* error ? */
        }
+       if (res > 0)    /* If they've typed a digit already, handle it */
+               xferto[0] = (char) res;
 
        ast_stopstream(transferer);
-       res = ast_app_dtget(transferer, transferer_real_context, newext, sizeof(newext), 100, transferdigittimeout);
-       if (res < 0) {
-               ast_moh_stop(transferee);
-               ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
+       res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
+       if (res < 0) {  /* hangup, would be 0 for invalid and 1 for valid */
+               finishup(transferee);
                return res;
        }
-       if (!strcmp(newext, ast_parking_ext())) {
-               ast_moh_stop(transferee);
-
-               res = ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
+       if (!strcmp(xferto, ast_parking_ext())) {
+               res = finishup(transferee);
                if (res)
                        res = -1;
-               else if (!ast_park_call(transferee, transferer, 0, NULL)) {
+               else if (!ast_park_call(transferee, transferer, 0, NULL)) {     /* success */
                        /* We return non-zero, but tell the PBX not to hang the channel when
                           the thread dies -- We have to be careful now though.  We are responsible for 
                           hanging up the channel, else it will never be hung up! */
@@ -577,48 +732,46 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
                } else {
                        ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
                }
-               /* XXX Maybe we should have another message here instead of invalid extension XXX */
-       } else if (ast_exists_extension(transferee, transferer_real_context, newext, 1, transferer->cid.cid_num)) {
-               pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
+               /*! \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", transferee->name);
                pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
-               ast_moh_stop(transferee);
-               res=ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
+               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) 
                                ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n"
-                                                               ,transferee->name, newext, transferer_real_context);
-                       if (ast_async_goto(transferee, transferer_real_context, newext, 1))
+                                                               ,transferee->name, xferto, transferer_real_context);
+                       if (ast_async_goto(transferee, transferer_real_context, xferto, 1))
                                ast_log(LOG_WARNING, "Async goto failed :-(\n");
                        res = -1;
                } else {
                        /* Set the channel's new extension, since it exists, using transferer context */
-                       ast_copy_string(transferee->exten, newext, sizeof(transferee->exten));
-                       ast_copy_string(transferee->context, transferer_real_context, sizeof(transferee->context));
-                       transferee->priority = 0;
+                       set_c_e_p(transferee, transferer_real_context, xferto, 0);
                }
                check_goto_on_transfer(transferer);
                return res;
        } else {
                if (option_verbose > 2) 
-                       ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", newext, transferer_real_context);
+                       ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", xferto, transferer_real_context);
        }
-       if (!ast_strlen_zero(xferfailsound))
-               res = ast_streamfile(transferer, xferfailsound, transferee->language);
-       else
-               res = 0;
-       if (res) {
-               ast_moh_stop(transferee);
-               ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
-               return res;
+       if (ast_stream_and_wait(transferer, xferfailsound, AST_DIGIT_ANY) < 0) {
+               finishup(transferee);
+               return -1;
        }
-       res = ast_waitstream(transferer, AST_DIGIT_ANY);
        ast_stopstream(transferer);
-       ast_moh_stop(transferee);
-       res = ast_autoservice_stop(transferee);
-       ast_indicate(transferee, AST_CONTROL_UNHOLD);
+       res = finishup(transferee);
        if (res) {
                if (option_verbose > 1)
                        ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
@@ -627,207 +780,264 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
        return FEATURE_RETURN_SUCCESS;
 }
 
+static int check_compat(struct ast_channel *c, struct ast_channel *newchan)
+{
+       if (ast_channel_make_compatible(c, newchan) < 0) {
+               ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n",
+                       c->name, newchan->name);
+               ast_hangup(newchan);
+               return -1;
+       }
+       return 0;
+}
+
 static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
 {
        struct ast_channel *transferer;
        struct ast_channel *transferee;
-       struct ast_channel *newchan, *xferchan=NULL;
-       int outstate=0;
-       struct ast_bridge_config bconfig;
        const char *transferer_real_context;
-       char xferto[256],dialstr[265];
-       char *cid_num;
-       char *cid_name;
+       char xferto[256] = "";
+       char callbackto[256] = "";
        int res;
-       struct ast_frame *f = NULL;
+       int outstate=0;
+       struct ast_channel *newchan;
+       struct ast_channel *xferchan;
        struct ast_bridge_thread_obj *tobj;
+       struct ast_bridge_config bconfig;
+       struct ast_frame *f;
+       int l;
 
-       ast_log(LOG_DEBUG, "Executing Attended Transfer %s, %s (sense=%d) XXX\n", chan->name, peer->name, sense);
-       if (sense == FEATURE_SENSE_PEER) {
-               transferer = peer;
-               transferee = chan;
-       } else {
-               transferer = chan;
-               transferee = peer;
-       }
-       if (!(transferer_real_context=pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
-          !(transferer_real_context=pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
-               /* Use the non-macro context to transfer the call */
-               if (!ast_strlen_zero(transferer->macrocontext))
-                       transferer_real_context = transferer->macrocontext;
-               else
-                       transferer_real_context = transferer->context;
-       }
-       /* Start autoservice on chan while we talk
-          to the originator */
-       ast_indicate(transferee, AST_CONTROL_HOLD);
+       if (option_debug)
+               ast_log(LOG_DEBUG, "Executing Attended Transfer %s, %s (sense=%d) \n", chan->name, peer->name, sense);
+       set_peers(&transferer, &transferee, peer, chan, sense);
+        transferer_real_context = real_ctx(transferer, transferee);
+       /* Start autoservice on chan while we talk to the originator */
        ast_autoservice_start(transferee);
-       ast_moh_start(transferee, NULL);
-       memset(xferto, 0, sizeof(xferto));
+       ast_indicate(transferee, AST_CONTROL_HOLD);
+       
        /* Transfer */
-       if ((res = ast_streamfile(transferer, "pbx-transfer", transferer->language))) {
-               ast_moh_stop(transferee);
-               ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
+       res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
+       if (res < 0) {
+               finishup(transferee);
                return res;
        }
-       if ((res=ast_waitstream(transferer, AST_DIGIT_ANY)) < 0) {
-               ast_moh_stop(transferee);
-               ast_autoservice_stop(transferee);
-               ast_indicate(transferee, AST_CONTROL_UNHOLD);
-               return res;
-       } else if(res > 0) {
-               /* If they've typed a digit already, handle it */
+       if (res > 0) /* If they've typed a digit already, handle it */
                xferto[0] = (char) res;
+
+       /* this is specific of atxfer */
+       res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
+        if (res < 0) {  /* hangup, would be 0 for invalid and 1 for valid */
+                finishup(transferee);
+                return res;
+        }
+       if (res == 0) {
+               ast_log(LOG_WARNING, "Did not read data.\n");
+               finishup(transferee);
+               if (ast_stream_and_wait(transferer, "beeperr", ""))
+                       return -1;
+               return FEATURE_RETURN_SUCCESS;
        }
-       if ((ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout))) {
-               cid_num = transferer->cid.cid_num;
-               cid_name = transferer->cid.cid_name;
-               if (ast_exists_extension(transferer, transferer_real_context,xferto, 1, cid_num)) {
-                       snprintf(dialstr, sizeof(dialstr), "%s@%s/n", xferto, transferer_real_context);
-                       newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats), dialstr, 15000, &outstate, cid_num, cid_name);
-                       ast_indicate(transferer, -1);
-                       if (newchan) {
-                               res = ast_channel_make_compatible(transferer, newchan);
-                               if (res < 0) {
-                                       ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", transferer->name, newchan->name);
-                                       ast_hangup(newchan);
+
+       /* valid extension, res == 1 */
+       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", ""))
+                       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, 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;
+               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 (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 if (!ast_check_hangup(transferee)) {
+               /* act as blind transfer */
+               if (ast_autoservice_stop(transferee) < 0) {
+                       ast_hangup(newchan);
+                       return -1;
+               }
+
+               if (!newchan) {
+                       unsigned int tries = 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;
-                               }
-                               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 (f) {
-                                               ast_frfree(f);
-                                               f = NULL;
-                                       }
-                                       if (!ast_strlen_zero(xfersound) && !ast_streamfile(transferer, xfersound, transferer->language)) {
-                                               if (ast_waitstream(transferer, "") < 0) {
-                                                       ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
-                                               }
-                                       }
-                                       ast_moh_stop(transferee);
-                                       ast_autoservice_stop(transferee);
-                                       ast_indicate(transferee, AST_CONTROL_UNHOLD);
-                                       transferer->_softhangup = 0;
-                                       return FEATURE_RETURN_SUCCESS;
-                               }
-                               
-                               res = ast_channel_make_compatible(transferee, newchan);
-                               if (res < 0) {
-                                       ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", transferee->name, newchan->name);
+                               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;
                                }
-                               
-                               
-                               ast_moh_stop(transferee);
-                               
-                               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);
-                                       res = -1;
-                                       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 ((xferchan = ast_channel_alloc(0))) {
-                                       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;
-
-                                       if ((f = ast_read(xferchan))) {
-                                               ast_frfree(f);
-                                               f = NULL;
-                                       }
-                                       
-                               } else {
-                                       ast_hangup(newchan);
-                                       return -1;
-                               }
+               /* 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 ((tobj = ast_calloc(1, sizeof(*tobj)))) {
-                                       tobj->chan = xferchan;
-                                       tobj->peer = newchan;
-                                       tobj->bconfig = *config;
-       
-                                       if (!ast_strlen_zero(xfersound) && !ast_streamfile(newchan, xfersound, newchan->language) &&
-                                                       ast_waitstream(newchan, "") < 0) {
-                                               ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
-                                       }
-                                       ast_bridge_call_thread_launch(tobj);
-                               } else {
-                                       ast_hangup(xferchan);
-                                       ast_hangup(newchan);
-                               }
-                               return -1;
-                               
-                       } else {
-                               ast_moh_stop(transferee);
-                               ast_autoservice_stop(transferee);
-                               ast_indicate(transferee, AST_CONTROL_UNHOLD);
-                               /* any reason besides user requested cancel and busy triggers the failed sound */
-                               if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY && !ast_strlen_zero(xferfailsound)) {
-                                       res = ast_streamfile(transferer, xferfailsound, transferer->language);
-                                       if (!res && (ast_waitstream(transferer, "") < 0)) {
-                                               return -1;
-                                       }
-                               }
-                               return FEATURE_RETURN_SUCCESS;
-                       }
-               } else {
-                       ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context);
-                       ast_moh_stop(transferee);
-                       ast_autoservice_stop(transferee);
-                       ast_indicate(transferee, AST_CONTROL_UNHOLD);
-                       res = ast_streamfile(transferer, "beeperr", transferer->language);
-                       if (!res && (ast_waitstream(transferer, "") < 0)) {
-                               return -1;
-                       }
+               if ((ast_waitfordigit(transferee, 100) < 0)
+                  || (ast_waitfordigit(newchan, 100) < 0)
+                  || ast_check_hangup(transferee)
+                  || ast_check_hangup(newchan)) {
+                       ast_hangup(newchan);
+                       return -1;
                }
-       }  else {
-               ast_log(LOG_WARNING, "Did not read data.\n");
-               res = ast_streamfile(transferer, "beeperr", transferer->language);
-               if (ast_waitstream(transferer, "") < 0) {
+
+               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;
        }
-       ast_moh_stop(transferee);
-       ast_autoservice_stop(transferee);
-       ast_indicate(transferee, AST_CONTROL_UNHOLD);
-
-       return FEATURE_RETURN_SUCCESS;
 }
 
-
 /* 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 },
+#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, "" },
 };
 
 
-static AST_LIST_HEAD(feature_list,ast_call_feature) feature_list;
+static AST_LIST_HEAD_STATIC(feature_list,ast_call_feature);
 
 /*! \brief register new feature into feature_list*/
 void ast_register_feature(struct ast_call_feature *feature)
@@ -845,10 +1055,74 @@ 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)
 {
-       if (!feature) return;
+       if (!feature)
+               return;
 
        AST_LIST_LOCK(&feature_list);
        AST_LIST_REMOVE(&feature_list,feature,feature_entry);
@@ -856,6 +1130,7 @@ void ast_unregister_feature(struct ast_call_feature *feature)
        free(feature);
 }
 
+/*! \brief Remove all features in the list */
 static void ast_unregister_features(void)
 {
        struct ast_call_feature *feature;
@@ -866,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);
@@ -899,43 +1236,81 @@ 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 = chan;
-               if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLEE))
+
+       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;
-               res = pbx_exec(work, app, feature->app_args);
-               if (res < 0)
-                       return res; 
+                       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);
        
-       return FEATURE_RETURN_SUCCESS;
+       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 */
 }
 
 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;
 }
 
@@ -945,15 +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_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features.flags);
+               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 */
@@ -966,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);
 
-               if (!tmp)
-                       return res;
-               
-               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);
-                                       res = feature->operation(chan, peer, 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;
@@ -999,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");
@@ -1018,27 +1415,23 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
                        char *tok;
                        struct ast_call_feature *feature;
 
-                       if (!tmp) {
-                               return;
-                       }
-
                        /* while we have a feature */
-                       while (NULL != (tok = strsep(&tmp, "#"))) {
-                               if ((feature = find_feature(tok))) {
-                                       if (ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
-                                               if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLER))
-                                                       ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
-                                               if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLEE))
-                                                       ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
-                                       }
+                       while ((tok = strsep(&tmp, "#"))) {
+                               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_BYCALLEE))
+                                               ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
                                }
+                               AST_LIST_UNLOCK(&feature_list);
                        }
                }
        }
 }
 
-
-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)
+/*! \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, 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;
@@ -1046,12 +1439,20 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
        struct ast_channel *chan;
        struct ast_channel *monitor_chans[2];
        struct ast_channel *active_channel;
-       struct ast_frame *f = NULL;
        int res = 0, ready = 0;
        
        if ((chan = ast_request(type, format, data, &cause))) {
                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;
@@ -1059,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;
 
@@ -1069,10 +1471,13 @@ 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;
                                monitor_chans[1] = chan;
                                active_channel = ast_waitfor_n(monitor_chans, 2, &to);
@@ -1084,9 +1489,8 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                        break; /*doh! timeout*/
                                }
 
-                               if (!active_channel) {
+                               if (!active_channel)
                                        continue;
-                               }
 
                                if (chan && (chan == active_channel)){
                                        f = ast_read(chan);
@@ -1100,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;
@@ -1126,38 +1530,40 @@ 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;
+                                                       }
                                                }
                                        }
                                }
-                               if (f) {
+                               if (f)
                                        ast_frfree(f);
-                               }
-                       }
+                       } /* end while */
                } else
                        ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data);
        } else {
@@ -1221,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));
 
@@ -1247,11 +1653,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                        src = peer;
                if (monitor_app && src) {
                        char *tmp = ast_strdupa(monitor_exec);
-                       if (tmp) {
-                               pbx_exec(src, monitor_app, tmp);
-                       } else {
-                               ast_log(LOG_ERROR, "Monitor failed: out of memory\n");
-                       }
+                       pbx_exec(src, monitor_app, tmp);
                }
        }
        
@@ -1262,7 +1664,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
        if (ast_answer(chan))
                return -1;
        peer->appl = "Bridged Call";
-       peer->data = (char *) chan->name;
+       peer->data = chan->name;
 
        /* copy the userfield from the B-leg to A-leg if applicable */
        if (chan->cdr && peer->cdr && !ast_strlen_zero(peer->cdr->userfield)) {
@@ -1276,23 +1678,23 @@ 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
                                   activated, but that's no excuse to keep things going 
                                   indefinitely! */
                                if (backup_config.feature_timer && ((backup_config.feature_timer -= diff) <= 0)) {
-                                       ast_log(LOG_DEBUG, "Timed out, realtime this time!\n");
+                                       if (option_debug)
+                                               ast_log(LOG_DEBUG, "Timed out, realtime this time!\n");
                                        config->feature_timer = 0;
                                        who = chan;
                                        if (f)
@@ -1302,7 +1704,8 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                                } else if (config->feature_timer <= 0) {
                                        /* Not *really* out of time, just out of time for
                                           digits to come in for features. */
-                                       ast_log(LOG_DEBUG, "Timed out for feature!\n");
+                                       if (option_debug)
+                                               ast_log(LOG_DEBUG, "Timed out for feature!\n");
                                        if (!ast_strlen_zero(peer_featurecode)) {
                                                ast_dtmf_stream(chan, peer, peer_featurecode, 0);
                                                memset(peer_featurecode, 0, sizeof(peer_featurecode));
@@ -1322,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 */
@@ -1340,10 +1748,11 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                        return -1;
                }
                
-               if (!f || ((f->frametype == AST_FRAME_CONTROL) && ((f->subclass == AST_CONTROL_HANGUP) || (f->subclass == AST_CONTROL_BUSY) || 
-                       (f->subclass == AST_CONTROL_CONGESTION)))) {
-                               res = -1;
-                               break;
+               if (!f || (f->frametype == AST_FRAME_CONTROL &&
+                               (f->subclass == AST_CONTROL_HANGUP || f->subclass == AST_CONTROL_BUSY || 
+                                       f->subclass == AST_CONTROL_CONGESTION))) {
+                       res = -1;
+                       break;
                }
                /* many things should be sent to the 'other' channel */
                other = (who == chan) ? peer : chan;
@@ -1357,12 +1766,12 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                        else if (f->subclass == AST_CONTROL_OPTION) {
                                aoh = f->data;
                                /* Forward option Requests */
-                               if (aoh && (aoh->flag == AST_OPTION_FLAG_REQUEST))
+                               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 && (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;
 
@@ -1375,10 +1784,14 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                                sense = FEATURE_SENSE_PEER;
                                featurecode = peer_featurecode;
                        }
-                       /* append the event to featurecode. we rely on the string being zero-filled, and
-                        * not overflowing it. XXX how do we guarantee the latter ?
+                       /*! append the event to featurecode. we rely on the string being zero-filled, and
+                        * not overflowing it. 
+                        * \todo XXX how do we guarantee the latter ?
                         */
                        featurecode[strlen(featurecode)] = f->subclass;
+                       /* Get rid of the frame before we start doing "stuff" with the channels */
+                       ast_frfree(f);
+                       f = NULL;
                        config->feature_timer = backup_config.feature_timer;
                        res = ast_feature_interpret(chan, peer, config, featurecode, sense);
                        switch(res) {
@@ -1391,10 +1804,8 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                        }
                        if (res >= FEATURE_RETURN_PASSDIGITS) {
                                res = 0;
-                       } else {
-                               ast_frfree(f);
+                       } else 
                                break;
-                       }
                        hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
                        if (hadfeatures && !hasfeatures) {
                                /* Restore backup */
@@ -1414,98 +1825,151 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                                        config->start_sound = NULL;
                                        config->firstpass = 0;
                                }
+                               config->start_time = ast_tvnow();
                                config->feature_timer = featuredigittimeout;
-                               ast_log(LOG_DEBUG, "Set time limit to %ld\n", config->feature_timer);
+                               if (option_debug)
+                                       ast_log(LOG_DEBUG, "Set time limit to %ld\n", config->feature_timer);
                        }
                }
                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;
 }
 
+/*! \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"
+               "CallerIDNum: %s\r\n"
+               "CallerIDName: %s\r\n\r\n",
+               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)
 {
-       int ms, tms, max;
-       struct parkeduser *pu, *pl, *pt = NULL;
-       struct timeval tv;
-       struct ast_frame *f;
-       char exten[AST_MAX_EXTENSION];
-       char *peername,*cp;
-       char returnexten[AST_MAX_EXTENSION];
-       struct ast_context *con;
-       int x;
-       fd_set rfds, efds;
-       fd_set nrfds, nefds;
+       char parkingslot[AST_MAX_EXTENSION];
+       fd_set rfds, efds;      /* results from previous select, to be preserved across loops. */
+
        FD_ZERO(&rfds);
        FD_ZERO(&efds);
 
        for (;;) {
-               ms = -1;
-               max = -1;
+               struct parkeduser *pu, *pl, *pt = NULL;
+               int ms = -1;    /* select timeout, uninitialized */
+               int max = -1;   /* max fd, none there yet */
+               fd_set nrfds, nefds;    /* args for the next select */
+               FD_ZERO(&nrfds);
+               FD_ZERO(&nefds);
+
                ast_mutex_lock(&parking_lock);
                pl = NULL;
                pu = parkinglot;
-               FD_ZERO(&nrfds);
-               FD_ZERO(&nefds);
-               while(pu) {
-                       if (pu->notquiteyet) {
-                               /* Pretend this one isn't here yet */
+               /* navigate the list with prev-cur pointers to support removals */
+               while (pu) {
+                       struct ast_channel *chan = pu->chan;    /* shorthand */
+                       int tms;        /* timeout for this item */
+                       int x;          /* fd index in channel */
+                       struct ast_context *con;
+
+                       if (pu->notquiteyet) { /* Pretend this one isn't here yet */
                                pl = pu;
                                pu = pu->next;
                                continue;
                        }
                        tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
                        if (tms > pu->parkingtime) {
-                               /* Stop music on hold */
-                               ast_moh_stop(pu->chan);
-                               ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
+                               ast_indicate(chan, AST_CONTROL_UNHOLD);
                                /* Get chan, exten from derived kludge */
                                if (pu->peername[0]) {
-                                       peername = ast_strdupa(pu->peername);
-                                       cp = strrchr(peername, '-');
+                                       char *peername = ast_strdupa(pu->peername);
+                                       char *cp = strrchr(peername, '-');
                                        if (cp) 
                                                *cp = 0;
                                        con = ast_context_find(parking_con_dial);
                                        if (!con) {
                                                con = ast_context_create(NULL, parking_con_dial, registrar);
-                                               if (!con) {
+                                               if (!con)
                                                        ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial);
-                                               }
                                        }
                                        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);
                                        }
-                                       ast_copy_string(pu->chan->exten, peername, sizeof(pu->chan->exten));
-                                       ast_copy_string(pu->chan->context, parking_con_dial, sizeof(pu->chan->context));
-                                       pu->chan->priority = 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 */
-                                       ast_copy_string(pu->chan->exten, pu->exten, sizeof(pu->chan->exten));
-                                       ast_copy_string(pu->chan->context, pu->context, sizeof(pu->chan->context));
-                                       pu->chan->priority = pu->priority;
+                                       set_c_e_p(chan, pu->context, pu->exten, pu->priority);
                                }
 
-                               manager_event(EVENT_FLAG_CALL, "ParkedCallTimeOut",
-                                       "Exten: %d\r\n"
-                                       "Channel: %s\r\n"
-                                       "CallerID: %s\r\n"
-                                       "CallerIDName: %s\r\n"
-                                       ,pu->parkingnum, pu->chan->name
-                                       ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
-                                       ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
-                                       );
+                               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", pu->chan->name, pu->parkingnum, pu->chan->context, pu->chan->exten, pu->chan->priority);
+                                       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);
                                /* Start up the PBX, or hang them up */
-                               if (ast_pbx_start(pu->chan))  {
-                                       ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", pu->chan->name);
-                                       ast_hangup(pu->chan);
+                               if (ast_pbx_start(chan))  {
+                                       ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", chan->name);
+                                       ast_hangup(chan);
                                }
                                /* And take them out of the parking lot */
                                if (pl) 
@@ -1516,136 +1980,147 @@ static void *do_parking_thread(void *ignore)
                                pu = pu->next;
                                con = ast_context_find(parking_con);
                                if (con) {
-                                       snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
-                                       if (ast_context_remove_extension2(con, exten, 1, NULL))
+                                       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
                                        ast_log(LOG_WARNING, "Whoa, no parking context?\n");
                                free(pt);
-                       } else {
+                       } else {        /* still within parking time, process descriptors */
                                for (x = 0; x < AST_MAX_FDS; x++) {
-                                       if ((pu->chan->fds[x] > -1) && (FD_ISSET(pu->chan->fds[x], &rfds) || FD_ISSET(pu->chan->fds[x], &efds))) {
-                                               if (FD_ISSET(pu->chan->fds[x], &efds))
-                                                       ast_set_flag(pu->chan, AST_FLAG_EXCEPTION);
+                                       struct ast_frame *f;
+
+                                       if (chan->fds[x] == -1 || (!FD_ISSET(chan->fds[x], &rfds) && !FD_ISSET(chan->fds[x], &efds)))
+                                               continue;       /* nothing on this descriptor */
+
+                                       if (FD_ISSET(chan->fds[x], &efds))
+                                               ast_set_flag(chan, AST_FLAG_EXCEPTION);
+                                       else
+                                               ast_clear_flag(chan, AST_FLAG_EXCEPTION);
+                                       chan->fdno = x;
+
+                                       /* See if they need servicing */
+                                       f = ast_read(chan);
+                                       if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass ==  AST_CONTROL_HANGUP)) {
+                                               if (f)
+                                                       ast_frfree(f);
+                                               post_manager_event("ParkedCallGiveUp", pu);
+
+                                               /* There's a problem, hang them up*/
+                                               if (option_verbose > 1) 
+                                                       ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", chan->name);
+                                               ast_hangup(chan);
+                                               /* And take them out of the parking lot */
+                                               if (pl) 
+                                                       pl->next = pu->next;
                                                else
-                                                       ast_clear_flag(pu->chan, AST_FLAG_EXCEPTION);
-                                               pu->chan->fdno = x;
-                                               /* See if they need servicing */
-                                               f = ast_read(pu->chan);
-                                               if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass ==  AST_CONTROL_HANGUP))) {
-                                                       if (f)
-                                                               ast_frfree(f);
-                                                       manager_event(EVENT_FLAG_CALL, "ParkedCallGiveUp",
-                                                               "Exten: %d\r\n"
-                                                               "Channel: %s\r\n"
-                                                               "CallerID: %s\r\n"
-                                                               "CallerIDName: %s\r\n"
-                                                               ,pu->parkingnum, pu->chan->name
-                                                               ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
-                                                               ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
-                                                               );
-
-                                                       /* There's a problem, hang them up*/
-                                                       if (option_verbose > 1) 
-                                                               ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", pu->chan->name);
-                                                       ast_hangup(pu->chan);
-                                                       /* And take them out of the parking lot */
-                                                       if (pl) 
-                                                               pl->next = pu->next;
+                                                       parkinglot = pu->next;
+                                               pt = pu;
+                                               pu = pu->next;
+                                               con = ast_context_find(parking_con);
+                                               if (con) {
+                                                       if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
+                                                               ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
                                                        else
-                                                               parkinglot = pu->next;
-                                                       pt = pu;
-                                                       pu = pu->next;
-                                                       con = ast_context_find(parking_con);
-                                                       if (con) {
-                                                               snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
-                                                               if (ast_context_remove_extension2(con, exten, 1, NULL))
-                                                                       ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
-                                                       } else
-                                                               ast_log(LOG_WARNING, "Whoa, no parking context?\n");
-                                                       free(pt);
-                                                       break;
-                                               } else {
-                                                       /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
-                                                       ast_frfree(f);
-                                                       if (pu->moh_trys < 3 && !pu->chan->generatordata) {
+                                                               notify_metermaids(pt->parkingexten, parking_con);
+                                               } else
+                                                       ast_log(LOG_WARNING, "Whoa, no parking context?\n");
+                                               free(pt);
+                                               break;
+                                       } else {
+                                               /*! \todo XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
+                                               ast_frfree(f);
+                                               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_moh_start(pu->chan, NULL);
-                                                               pu->moh_trys++;
-                                                       }
-                                                       goto std;       /* XXX Ick: jumping into an else statement??? XXX */
+                                                       ast_indicate_data(chan, AST_CONTROL_HOLD, 
+                                                               S_OR(parkmohclass, NULL),
+                                                               !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
+                                                       pu->moh_trys++;
                                                }
+                                               goto std;       /*! \todo XXX Ick: jumping into an else statement??? XXX */
                                        }
-                               }
+
+                               } /* end for */
                                if (x >= AST_MAX_FDS) {
-std:                                   for (x=0; x<AST_MAX_FDS; x++) {
-                                               /* Keep this one for next one */
-                                               if (pu->chan->fds[x] > -1) {
-                                                       FD_SET(pu->chan->fds[x], &nrfds);
-                                                       FD_SET(pu->chan->fds[x], &nefds);
-                                                       if (pu->chan->fds[x] > max)
-                                                               max = pu->chan->fds[x];
+std:                                   for (x=0; x<AST_MAX_FDS; x++) { /* mark fds for next round */
+                                               if (chan->fds[x] > -1) {
+                                                       FD_SET(chan->fds[x], &nrfds);
+                                                       FD_SET(chan->fds[x], &nefds);
+                                                       if (chan->fds[x] > max)
+                                                               max = chan->fds[x];
                                                }
                                        }
-                                       /* Keep track of our longest wait */
-                                       if ((tms < ms) || (ms < 0))
+                                       /* Keep track of our shortest wait */
+                                       if (tms < ms || ms < 0)
                                                ms = tms;
                                        pl = pu;
                                        pu = pu->next;
                                }
                        }
-               }
+               } /* end while */
                ast_mutex_unlock(&parking_lock);
                rfds = nrfds;
                efds = nefds;
-               tv = ast_samp2tv(ms, 1000);
-               /* Wait for something to happen */
-               ast_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL);
+               {
+                       struct timeval tv = ast_samp2tv(ms, 1000);
+                       /* Wait for something to happen */
+                       ast_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL);
+               }
                pthread_testcancel();
        }
        return NULL;    /* Never reached */
 }
 
+/*! \brief Park a call */
 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");
        chan->priority = 1;
+       /* Answer if call is not up */
        if (chan->_state != AST_STATE_UP)
                res = ast_answer(chan);
+       /* Sleep to allow VoIP streams to settle down */
        if (!res)
                res = ast_safe_sleep(chan, 1000);
+       /* 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;
-       char exten[AST_MAX_EXTENSION];
        struct ast_context *con;
+
        int park;
-       int dres;
        struct ast_bridge_config config;
 
        if (!data) {
-               ast_log(LOG_WARNING, "Park requires an argument (extension number)\n");
+               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;
@@ -1665,70 +2140,59 @@ static int park_exec(struct ast_channel *chan, void *data)
                peer = pu->chan;
                con = ast_context_find(parking_con);
                if (con) {
-                       snprintf(exten, sizeof(exten), "%d", pu->parkingnum);
-                       if (ast_context_remove_extension2(con, exten, 1, NULL))
+                       if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL))
                                ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+                       else
+                               notify_metermaids(pu->parkingexten, parking_con);
                } else
                        ast_log(LOG_WARNING, "Whoa, no parking context?\n");
 
                manager_event(EVENT_FLAG_CALL, "UnParkedCall",
-                       "Exten: %d\r\n"
+                       "Exten: %s\r\n"
                        "Channel: %s\r\n"
                        "From: %s\r\n"
-                       "CallerID: %s\r\n"
-                       "CallerIDName: %s\r\n"
-                       ,pu->parkingnum, pu->chan->name, chan->name
-                       ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
-                       ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
+                       "CallerIDNum: %s\r\n"
+                       "CallerIDName: %s\r\n",
+                       pu->parkingexten, pu->chan->name, chan->name,
+                       S_OR(pu->chan->cid.cid_num, "<unknown>"),
+                       S_OR(pu->chan->cid.cid_name, "<unknown>")
                        );
 
                free(pu);
        }
        /* JK02: it helps to answer the channel if not already up */
-       if (chan->_state != AST_STATE_UP) {
+       if (chan->_state != AST_STATE_UP)
                ast_answer(chan);
-       }
 
        if (peer) {
                /* Play a courtesy to the source(s) configured to prefix the bridge connecting */
                
                if (!ast_strlen_zero(courtesytone)) {
+                       int error = 0;
+                       ast_indicate(peer, AST_CONTROL_UNHOLD);
                        if (parkedplay == 0) {
-                               if (!ast_streamfile(chan, courtesytone, chan->language)) {
-                                       if (ast_waitstream(chan, "") < 0) {
-                                               ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
-                                               ast_hangup(peer);
-                                               return -1;
-                                       }
-                               }
-                               ast_moh_stop(peer);
-                               ast_indicate(peer, AST_CONTROL_UNHOLD);
-                       } else {
-                               ast_moh_stop(peer);
-                               ast_indicate(peer, AST_CONTROL_UNHOLD);
-                               if (parkedplay == 2) {
-                                       if (!ast_streamfile(chan, courtesytone, chan->language) && !ast_streamfile(peer, courtesytone, chan->language)) {
-                                               res = ast_waitstream(chan, "");
-                                               if (res >= 0)
-                                                       res = ast_waitstream(peer, "");
-                                               if (res < 0) {
-                                                       ast_log(LOG_WARNING, "Failed to play courtesy tones!\n");
-                                                       ast_hangup(peer);
-                                                       return -1;
-                                               }
-                                       }
-                               } else if (parkedplay == 1) {
-                                       if (!ast_streamfile(peer, courtesytone, chan->language)) {
-                                               if (ast_waitstream(peer, "") < 0) {
-                                                       ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
-                                                       ast_hangup(peer);
-                                                       return -1;
-                                               }
-                                       }
+                               error = ast_stream_and_wait(chan, courtesytone, "");
+                       } else if (parkedplay == 1) {
+                               error = ast_stream_and_wait(peer, courtesytone, "");
+                       } else if (parkedplay == 2) {
+                               if (!ast_streamfile(chan, courtesytone, chan->language) &&
+                                               !ast_streamfile(peer, courtesytone, chan->language)) {
+                                       /*! \todo XXX we would like to wait on both! */
+                                       res = ast_waitstream(chan, "");
+                                       if (res >= 0)
+                                               res = ast_waitstream(peer, "");
+                                       if (res < 0)
+                                               error = 1;
                                }
+                        }
+                       if (error) {
+                               ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
+                               ast_hangup(peer);
+                               return -1;
                        }
-               }
+               } else
+                       ast_indicate(peer, AST_CONTROL_UNHOLD); 
+
                res = ast_channel_make_compatible(chan, peer);
                if (res < 0) {
                        ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name);
@@ -1740,40 +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 {
-               /* XXX Play a message XXX */
-               dres = ast_streamfile(chan, "pbx-invalidpark", chan->language);
-               if (!dres)
-                       dres = ast_waitstream(chan, "");
-               else {
+               /*! \todo XXX Play a message XXX */
+               if (ast_stream_and_wait(chan, "pbx-invalidpark", ""))
                        ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name);
-                       dres = 0;
-               }
                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";
 
@@ -1782,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");
@@ -1811,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;
@@ -1828,18 +2408,16 @@ static int handle_parkedcalls(int fd, int argc, char *argv[])
 
        ast_mutex_lock(&parking_lock);
 
-       cur = parkinglot;
-       while(cur) {
-               ast_cli(fd, "%4d %25s (%-15s %-12s %-4d) %6lds\n"
-                       ,cur->parkingnum, cur->chan->name, cur->context, cur->exten
+       for (cur = parkinglot; cur; cur = cur->next) {
+               ast_cli(fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n"
+                       ,cur->parkingexten, cur->chan->name, cur->context, cur->exten
                        ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
 
-               cur = cur->next;
                numparked++;
        }
-       ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : "");
-
        ast_mutex_unlock(&parking_lock);
+       ast_cli(fd, "%d parked call%s.\n", numparked, ESS(numparked));
+
 
        return RESULT_SUCCESS;
 }
@@ -1848,51 +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);
 
-        cur=parkinglot;
-        while(cur) {
-                       astman_append(s, "Event: ParkedCall\r\n"
+       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->chan->cid.cid_num ? cur->chan->cid.cid_num : "")
-                       ,(cur->chan->cid.cid_name ? cur->chan->cid.cid_name : "")
-                       ,idText);
-
-            cur = cur->next;
-        }
+                       "\r\n",
+                       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);
+               "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[] =
@@ -1902,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;
@@ -1934,7 +2516,7 @@ static int manager_park(struct mansession *s, struct message *m)
        if (!ch2) {
                snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2);
                astman_send_error(s, m, buf);
-               ast_mutex_unlock(&ch1->lock);
+               ast_channel_unlock(ch1);
                return 0;
        }
 
@@ -1950,8 +2532,8 @@ static int manager_park(struct mansession *s, struct message *m)
                astman_send_error(s, m, "Park failure");
        }
 
-       ast_mutex_unlock(&ch1->lock);
-       ast_mutex_unlock(&ch2->lock);
+       ast_channel_unlock(ch1);
+       ast_channel_unlock(ch2);
 
        return 0;
 }
@@ -1962,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) &&
@@ -1970,7 +2552,7 @@ int ast_pickup_call(struct ast_channel *chan)
                         (cur->_state == AST_STATE_RING))) {
                                break;
                }
-               ast_mutex_unlock(&cur->lock);
+               ast_channel_unlock(cur);
        }
        if (cur) {
                if (option_debug)
@@ -1984,7 +2566,7 @@ int ast_pickup_call(struct ast_channel *chan)
                res = ast_channel_masquerade(cur, chan);
                if (res)
                        ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name);           /* Done */
-               ast_mutex_unlock(&cur->lock);
+               ast_channel_unlock(cur);
        } else  {
                if (option_debug)
                        ast_log(LOG_DEBUG, "No call pickup possible...\n");
@@ -1992,14 +2574,41 @@ int ast_pickup_call(struct ast_channel *chan)
        return res;
 }
 
+/*! \brief Add parking hints for all defined parking lots */
+static void park_add_hints(char *context, int start, int stop)
+{
+       int numext;
+       char device[AST_MAX_EXTENSION];
+       char exten[10];
+
+       for (numext = start; numext <= stop; numext++) {
+               snprintf(exten, sizeof(exten), "%d", numext);
+               snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+               ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+       }
+}
+
+
 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);
@@ -2011,6 +2620,7 @@ static int load_config(void)
        strcpy(parking_con_dial, "park-dial");
        strcpy(parking_ext, "700");
        strcpy(pickup_ext, "*8");
+       strcpy(parkmohclass, "default");
        courtesytone[0] = '\0';
        strcpy(xfersound, "beep");
        strcpy(xferfailsound, "pbx-invalid");
@@ -2018,206 +2628,455 @@ 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, "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, "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));
+       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) { 
-                               /* 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,",");
-
-                       /* 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);
-                               
-                               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 */
        if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con)))     {
-               ast_context_remove_extension2(con, old_parking_ext, 1, registrar);
-               ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
+               if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
+                               notify_metermaids(old_parking_ext, old_parking_con);
+               if (option_debug)
+                       ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
        }
        
        if (!(con = ast_context_find(parking_con)) && !(con = ast_context_create(NULL, parking_con, registrar))) {
                ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
                return -1;
        }
-       return 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)
+               notify_metermaids(ast_parking_ext(), parking_con);
+       return res;
+
+}
+
+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 *mod)
+static int reload(void)
 {
        return load_config();
 }
 
-static int load_module(void *mod)
+static int load_module(void)
 {
        int res;
-       
-       __mod_desc = mod;
-       AST_LIST_HEAD_INIT(&feature_list);
+
+       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);
+
        return res;
 }
 
 
-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,
+             );