Add support for configuring named groups of custom call features in
[asterisk/asterisk.git] / res / res_features.c
index 0787ec8..28b41de 100644 (file)
  * \author Mark Spencer <markster@digium.com> 
  */
 
  * \author Mark Spencer <markster@digium.com> 
  */
 
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
 #include <pthread.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <pthread.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/signal.h>
 #include <netinet/in.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"
 #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/manager.h"
 #include "asterisk/utils.h"
 #include "asterisk/adsi.h"
+#include "asterisk/devicestate.h"
 #include "asterisk/monitor.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_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
 
 
 #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 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 AST_RWLIST_HEAD_STATIC(feature_groups, feature_group);
+
+static char *parkedcall = "ParkedCall";
+
+static int parkaddhints = 0;                               /*!< Add parking hints automatically */
+static int parkedcalltransfers = 0;                        /*!< Enable DTMF based transfers on bridge when picking up parked calls */
+static int parkedcallreparking = 0;                        /*!< Enable DTMF based parking on bridge when picking up parked calls */
+static int parkingtime = DEFAULT_PARK_TIME;                /*!< No more than 45 seconds parked before you do something with them */
+static char parking_con[AST_MAX_EXTENSION];                /*!< Context for which parking is made accessible */
+static char parking_con_dial[AST_MAX_EXTENSION];           /*!< Context for dialback for parking (KLUDGE) */
+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;
 
 static int parking_offset;
 static int parkfindnext;
@@ -95,8 +122,14 @@ static int adsipark;
 
 static int transferdigittimeout;
 static int featuredigittimeout;
 
 static int transferdigittimeout;
 static int featuredigittimeout;
+static int comebacktoorigin = 1;
+
+static int atxfernoanswertimeout;
+static unsigned int atxferdropcall;
+static unsigned int atxferloopdelay;
+static unsigned int atxfercallbackretries;
 
 
-static char *registrar = "res_features";               /*!< Registrar for operations */
+static char *registrar = "res_features";                  /*!< Registrar for operations */
 
 /* module and CLI command definitions */
 static char *synopsis = "Answer a parked call";
 
 /* module and CLI command definitions */
 static char *synopsis = "Answer a parked call";
@@ -111,25 +144,29 @@ static char *parkcall = "Park";
 
 static char *synopsis2 = "Park yourself";
 
 
 static char *synopsis2 = "Park yourself";
 
-static char *descrip2 = "Park(exten):"
+static char *descrip2 = "Park():"
 "Used to park yourself (typically in combination with a supervised\n"
 "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"
 "Used to park yourself (typically in combination with a supervised\n"
 "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 {
 
 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;
        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;
        int notquiteyet;
        char peername[1024];
        unsigned char moh_trys;
@@ -138,12 +175,10 @@ struct parkeduser {
 
 static struct parkeduser *parkinglot;
 
 
 static struct parkeduser *parkinglot;
 
-AST_MUTEX_DEFINE_STATIC(parking_lock);
+AST_MUTEX_DEFINE_STATIC(parking_lock); /*!< protects all static variables above */
 
 static pthread_t parking_thread;
 
 
 static pthread_t parking_thread;
 
-LOCAL_USER_DECL;
-
 char *ast_parking_ext(void)
 {
        return parking_ext;
 char *ast_parking_ext(void)
 {
        return parking_ext;
@@ -159,8 +194,19 @@ struct ast_bridge_thread_obj
        struct ast_bridge_config bconfig;
        struct ast_channel *chan;
        struct ast_channel *peer;
        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;
 static void check_goto_on_transfer(struct ast_channel *chan) 
 {
        struct ast_channel *xferchan;
@@ -168,40 +214,48 @@ static void check_goto_on_transfer(struct ast_channel *chan)
        char *x, *goto_on_transfer;
        struct ast_frame *f;
 
        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;
 
 
 static void *ast_bridge_call_thread(void *data) 
 {
        struct ast_bridge_thread_obj *tobj = data;
+       int res;
+
+       tobj->chan->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge";
+       tobj->chan->data = tobj->peer->name;
+       tobj->peer->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge";
+       tobj->peer->data = tobj->chan->name;
 
 
-       tobj->chan->appl = "Transferred Call";
-       tobj->chan->data = (char *) tobj->peer->name;
-       tobj->peer->appl = "Transferred Call";
-       tobj->peer->data = (char *) tobj->chan->name;
        if (tobj->chan->cdr) {
                ast_cdr_reset(tobj->chan->cdr, NULL);
                ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name);
        if (tobj->chan->cdr) {
                ast_cdr_reset(tobj->chan->cdr, NULL);
                ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name);
@@ -212,11 +266,29 @@ static void *ast_bridge_call_thread(void *data)
        }
 
        ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
        }
 
        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);
        free(tobj);
-       tobj=NULL;
+
        return NULL;
 }
 
        return NULL;
 }
 
@@ -234,95 +306,136 @@ static void ast_bridge_call_thread_launch(void *data)
        pthread_setschedparam(thread, SCHED_RR, &sched);
 }
 
        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};
 
 {
        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;
        message[0] = tmp;
-       res = adsi_load_session(chan, NULL, 0, 1);
-       if (res == -1) {
+       res = ast_adsi_load_session(chan, NULL, 0, 1);
+       if (res == -1)
                return res;
                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 
 }
 
 /*! \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;
        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;
        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;
                return -1;
-       }
+
+       /* Lock parking lot */
        ast_mutex_lock(&parking_lock);
        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;
                                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;
        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) {
        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;
        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 (extout)
                *extout = x;
+
        if (peer) 
                ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
 
        /* Remember what had been dialed, so that if the parking
           expires, we try to come back to the same place */
        if (peer) 
                ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
 
        /* Remember what had been dialed, so that if the parking
           expires, we try to come back to the same place */
-       if (!ast_strlen_zero(chan->macrocontext))
-               ast_copy_string(pu->context, chan->macrocontext, sizeof(pu->context));
-       else
-               ast_copy_string(pu->context, chan->context, sizeof(pu->context));
-       if (!ast_strlen_zero(chan->macroexten))
-               ast_copy_string(pu->exten, chan->macroexten, sizeof(pu->exten));
-       else
-               ast_copy_string(pu->exten, chan->exten, sizeof(pu->exten));
-       if (chan->macropriority)
-               pu->priority = chan->macropriority;
-       else
-               pu->priority = chan->priority;
+       ast_copy_string(pu->context, S_OR(chan->macrocontext, chan->context), sizeof(pu->context));
+       ast_copy_string(pu->exten, S_OR(chan->macroexten, chan->exten), sizeof(pu->exten));
+       pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
        pu->next = parkinglot;
        parkinglot = pu;
        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;
        /* If parking a channel directly, don't quiet yet get parking running on it */
        if (peer == chan) 
                pu->notquiteyet = 1;
@@ -330,45 +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) 
        /* 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",
        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"
                "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);
        con = ast_context_find(parking_con);
-       if (!con) {
+       if (!con) 
                con = ast_context_create(NULL, parking_con, registrar);
                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 (peer) 
+       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);
+       }
+       /* 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_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);
        }
                pu->notquiteyet = 0;
                pthread_kill(parking_thread, SIGURG);
        }
@@ -381,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 */
        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;
        }
                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;
 }
 
        return 0;
 }
 
@@ -419,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)
 
 #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;
 
 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_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_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;
                }
                if (ast_autoservice_stop(callee_chan))
                        return -1;
@@ -486,23 +633,24 @@ 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 {
                        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);
                        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] = '-';
                        if (args[x] == '/')
                                args[x] = '-';
+               }
                
                if (option_verbose > 3)
                        ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to record call. filename: %s\n", code, args);
 
                
                if (option_verbose > 3)
                        ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to record call. filename: %s\n", code, args);
 
-               pbx_exec(callee_chan, monitor_app, args, 1);
+               pbx_exec(callee_chan, monitor_app, args);
                pbx_builtin_setvar_helper(callee_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
                pbx_builtin_setvar_helper(caller_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
        
                pbx_builtin_setvar_helper(callee_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
                pbx_builtin_setvar_helper(caller_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
        
@@ -520,124 +668,110 @@ static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer
        return FEATURE_RETURN_HANGUP;
 }
 
        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;
 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;
 
        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_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 */
        
        /* 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);
 
        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;
        }
                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;
                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! */
 
                        /* We return non-zero, but tell the PBX not to hang the channel when
                           the thread dies -- We have to be careful now though.  We are responsible for 
                           hanging up the channel, else it will never be hung up! */
 
-                       if (transferer == peer)
-                               res = AST_PBX_KEEPALIVE;
-                       else
-                               res = AST_PBX_NO_HANGUP_PEER;
-                       return res;
+                       return (transferer == peer) ? AST_PBX_KEEPALIVE : AST_PBX_NO_HANGUP_PEER;
                } else {
                        ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
                }
                } 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);
                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"
                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_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) 
                }
                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_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);
        if (res) {
                if (option_verbose > 1)
                        ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
@@ -646,208 +780,264 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
        return FEATURE_RETURN_SUCCESS;
 }
 
        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;
 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;
        const char *transferer_real_context;
-       char xferto[256],dialstr[265];
-       char *cid_num;
-       char *cid_name;
+       char xferto[256] = "";
+       char callbackto[256] = "";
        int res;
        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_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_autoservice_start(transferee);
-       ast_moh_start(transferee, NULL);
-       memset(xferto, 0, sizeof(xferto));
+       ast_indicate(transferee, AST_CONTROL_HOLD);
+       
        /* Transfer */
        /* 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;
        }
                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;
                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;
                                        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_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)) {
-                                               if (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;
                }
                        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 */
 /* 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)
 
 /*! \brief register new feature into feature_list*/
 void ast_register_feature(struct ast_call_feature *feature)
@@ -865,10 +1055,74 @@ void ast_register_feature(struct ast_call_feature *feature)
                ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
 }
 
                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)
 {
 /*! \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);
 
        AST_LIST_LOCK(&feature_list);
        AST_LIST_REMOVE(&feature_list,feature,feature_entry);
@@ -876,6 +1130,7 @@ void ast_unregister_feature(struct ast_call_feature *feature)
        free(feature);
 }
 
        free(feature);
 }
 
+/*! \brief Remove all features in the list */
 static void ast_unregister_features(void)
 {
        struct ast_call_feature *feature;
 static void ast_unregister_features(void)
 {
        struct ast_call_feature *feature;
@@ -886,31 +1141,94 @@ static void ast_unregister_features(void)
        AST_LIST_UNLOCK(&feature_list);
 }
 
        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;
 
 {
        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_TRAVERSE(&feature_list, tmp, feature_entry) {
                if (!strcasecmp(tmp->sname, name))
                        break;
        }
-       AST_LIST_UNLOCK(&feature_list);
 
        return tmp;
 }
 
 
        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;
 /*! \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);
        int res;
 
        AST_LIST_LOCK(&feature_list);
-       AST_LIST_TRAVERSE(&feature_list,feature,feature_entry) {
-               if (!strcasecmp(feature->exten,code)) break;
+       AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
+               if (!strcasecmp(feature->exten, code))
+                       break;
        }
        AST_LIST_UNLOCK(&feature_list);
 
        }
        AST_LIST_UNLOCK(&feature_list);
 
@@ -918,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; 
        }
                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;
                        work = peer;
-               res = pbx_exec(work, app, feature->app_args, 1);
-               if (res < 0)
-                       return res; 
+                       idle = chan;
+               }
        } else {
        } 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_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;
 }
 
 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);
        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)
 {
 }
 
 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++) {
        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;
 }
 
        return res;
 }
 
@@ -964,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 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");
        const char *dynamic_features=pbx_builtin_getvar_helper(chan,"DYNAMIC_FEATURES");
+       char *tmp, *tok;
 
        if (sense == FEATURE_SENSE_CHAN)
 
        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
        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 */
                if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
                    !ast_strlen_zero(builtin_features[x].exten)) {
                        /* Feature is up for consideration */
@@ -985,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;
        }
        
        return res;
@@ -1018,16 +1392,20 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
 {
        int x;
        
 {
        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++) {
        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");
        
        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");
@@ -1037,27 +1415,23 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
                        char *tok;
                        struct ast_call_feature *feature;
 
                        char *tok;
                        struct ast_call_feature *feature;
 
-                       if (!tmp) {
-                               return;
-                       }
-
                        /* while we have a feature */
                        /* 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;
 {
        int state = 0;
        int cause = 0;
@@ -1065,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_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);    
        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;
                if (!ast_call(chan, data, timeout)) {
                        struct timeval started;
                        int x, len = 0;
@@ -1078,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 */
 
                        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;
 
                                if (strcasecmp(builtin_features[x].sname, "disconnect"))
                                        continue;
 
@@ -1088,10 +1471,13 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                memset(dialed_code, 0, len);
                                break;
                        }
                                memset(dialed_code, 0, len);
                                break;
                        }
+                       ast_rwlock_unlock(&features_lock);
                        x = 0;
                        started = ast_tvnow();
                        to = timeout;
                        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);
                                monitor_chans[0] = caller;
                                monitor_chans[1] = chan;
                                active_channel = ast_waitfor_n(monitor_chans, 2, &to);
@@ -1103,9 +1489,8 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                        break; /*doh! timeout*/
                                }
 
                                        break; /*doh! timeout*/
                                }
 
-                               if (!active_channel) {
+                               if (!active_channel)
                                        continue;
                                        continue;
-                               }
 
                                if (chan && (chan == active_channel)){
                                        f = ast_read(chan);
 
                                if (chan && (chan == active_channel)){
                                        f = ast_read(chan);
@@ -1119,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)
                                                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_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;
                                                        ast_indicate(caller, AST_CONTROL_BUSY);
                                                        ast_frfree(f);
                                                        f = NULL;
@@ -1145,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?*/
                                } 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;
                                                }
                                                        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';
                                                        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);
                                        ast_frfree(f);
-                               }
-                       }
+                       } /* end while */
                } else
                        ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data);
        } else {
                } else
                        ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data);
        } else {
@@ -1240,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;
        int hasfeatures=0;
        int hadfeatures=0;
        struct ast_option_header *aoh;
-       struct timeval start = { 0 , 0 };
        struct ast_bridge_config backup_config;
        struct ast_bridge_config backup_config;
+       struct ast_cdr *bridge_cdr;
 
        memset(&backup_config, 0, sizeof(backup_config));
 
 
        memset(&backup_config, 0, sizeof(backup_config));
 
@@ -1266,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);
                        src = peer;
                if (monitor_app && src) {
                        char *tmp = ast_strdupa(monitor_exec);
-                       if (tmp) {
-                               pbx_exec(src, monitor_app, tmp, 1);
-                       } else {
-                               ast_log(LOG_ERROR, "Monitor failed: out of memory\n");
-                       }
+                       pbx_exec(src, monitor_app, tmp);
                }
        }
        
                }
        }
        
@@ -1281,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";
        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)) {
 
        /* copy the userfield from the B-leg to A-leg if applicable */
        if (chan->cdr && peer->cdr && !ast_strlen_zero(peer->cdr->userfield)) {
@@ -1295,22 +1678,23 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                free(peer->cdr);
                peer->cdr = NULL;
        }
                free(peer->cdr);
                peer->cdr = NULL;
        }
+
        for (;;) {
        for (;;) {
-               if (config->feature_timer)
-                       start = ast_tvnow();
+               struct ast_channel *other;      /* used later */
 
                res = ast_channel_bridge(chan, peer, config, &f, &who);
 
                if (config->feature_timer) {
                        /* Update time limit for next pass */
 
                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)) {
                        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)
                                        config->feature_timer = 0;
                                        who = chan;
                                        if (f)
@@ -1320,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. */
                                } 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));
                                        if (!ast_strlen_zero(peer_featurecode)) {
                                                ast_dtmf_stream(chan, peer, peer_featurecode, 0);
                                                memset(peer_featurecode, 0, sizeof(peer_featurecode));
@@ -1340,7 +1725,12 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                                        hadfeatures = hasfeatures;
                                        /* Continue as we were */
                                        continue;
                                        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 */
                        } else {
                                if (config->feature_timer <=0) {
                                        /* We ran out of time */
@@ -1358,57 +1748,50 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                        return -1;
                }
                
                        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->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RINGING)) {
-                       if (who == chan)
-                               ast_indicate(peer, AST_CONTROL_RINGING);
-                       else
-                               ast_indicate(chan, AST_CONTROL_RINGING);
-               }
-               if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == -1)) {
-                       if (who == chan)
-                               ast_indicate(peer, -1);
-                       else
-                               ast_indicate(chan, -1);
-               }
-               if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_FLASH)) {
-                       if (who == chan)
-                               ast_indicate(peer, AST_CONTROL_FLASH);
-                       else
-                               ast_indicate(chan, AST_CONTROL_FLASH);
+               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->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_OPTION)) {
-                       aoh = f->data;
-                       /* Forward option Requests */
-                       if (aoh && (aoh->flag == AST_OPTION_FLAG_REQUEST)) {
-                               if (who == chan)
-                                       ast_channel_setoption(peer, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0);
-                               else
-                                       ast_channel_setoption(chan, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0);
+               /* many things should be sent to the 'other' channel */
+               other = (who == chan) ? peer : chan;
+               if (f->frametype == AST_FRAME_CONTROL) {
+                       if (f->subclass == AST_CONTROL_RINGING)
+                               ast_indicate(other, AST_CONTROL_RINGING);
+                       else if (f->subclass == -1)
+                               ast_indicate(other, -1);
+                       else if (f->subclass == AST_CONTROL_FLASH)
+                               ast_indicate(other, AST_CONTROL_FLASH);
+                       else if (f->subclass == AST_CONTROL_OPTION) {
+                               aoh = f->data;
+                               /* Forward option Requests */
+                               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;
                        char *featurecode;
                        int sense;
-                       struct ast_channel *other;
 
                        hadfeatures = hasfeatures;
                        /* This cannot overrun because the longest feature is one shorter than our buffer */
                        if (who == chan) {
 
                        hadfeatures = hasfeatures;
                        /* This cannot overrun because the longest feature is one shorter than our buffer */
                        if (who == chan) {
-                               other = peer;
                                sense = FEATURE_SENSE_CHAN;
                                featurecode = chan_featurecode;
                        } else  {
                                sense = FEATURE_SENSE_CHAN;
                                featurecode = chan_featurecode;
                        } else  {
-                               other = chan;
                                sense = FEATURE_SENSE_PEER;
                                featurecode = peer_featurecode;
                        }
                                sense = FEATURE_SENSE_PEER;
                                featurecode = peer_featurecode;
                        }
+                       /*! 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;
                        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) {
                        config->feature_timer = backup_config.feature_timer;
                        res = ast_feature_interpret(chan, peer, config, featurecode, sense);
                        switch(res) {
@@ -1421,10 +1804,8 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
                        }
                        if (res >= FEATURE_RETURN_PASSDIGITS) {
                                res = 0;
                        }
                        if (res >= FEATURE_RETURN_PASSDIGITS) {
                                res = 0;
-                       } else {
-                               ast_frfree(f);
+                       } else 
                                break;
                                break;
-                       }
                        hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
                        if (hadfeatures && !hasfeatures) {
                                /* Restore backup */
                        hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
                        if (hadfeatures && !hasfeatures) {
                                /* Restore backup */
@@ -1444,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_sound = NULL;
                                        config->firstpass = 0;
                                }
+                               config->start_time = ast_tvnow();
                                config->feature_timer = featuredigittimeout;
                                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);
                        }
                }
                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;
 }
 
        }
        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)
 {
 /*! \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 (;;) {
        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;
                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) {
                                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]) {
                                /* 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 (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);
                                                        ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial);
-                                               }
                                        }
                                        if (con) {
                                        }
                                        if (con) {
+                                               char returnexten[AST_MAX_EXTENSION];
                                                snprintf(returnexten, sizeof(returnexten), "%s||t", peername);
                                                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 */
                                } 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) 
 
                                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 */
                                /* 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) 
                                }
                                /* And take them out of the parking lot */
                                if (pl) 
@@ -1546,136 +1980,147 @@ static void *do_parking_thread(void *ignore)
                                pu = pu->next;
                                con = ast_context_find(parking_con);
                                if (con) {
                                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");
                                                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
                                        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++) {
                                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
                                                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
                                                        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_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) {
                                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;
                                }
                        }
                                                ms = tms;
                                        pl = pu;
                                        pu = pu->next;
                                }
                        }
-               }
+               } /* end while */
                ast_mutex_unlock(&parking_lock);
                rfds = nrfds;
                efds = nefds;
                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 */
 }
 
                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 */
 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;
        /* 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);
        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);
        if (!res)
                res = ast_safe_sleep(chan, 1000);
+       /* Park the call */
        if (!res)
                res = ast_park_call(chan, chan, 0, NULL);
        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)
 {
 static int park_exec(struct ast_channel *chan, void *data)
 {
-       int res=0;
-       struct localuser *u;
+       int res = 0;
+       struct ast_module_user *u;
        struct ast_channel *peer=NULL;
        struct parkeduser *pu, *pl=NULL;
        struct ast_channel *peer=NULL;
        struct parkeduser *pu, *pl=NULL;
-       char exten[AST_MAX_EXTENSION];
        struct ast_context *con;
        struct ast_context *con;
+
        int park;
        int park;
-       int dres;
        struct ast_bridge_config config;
 
        if (!data) {
        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;
        }
                return -1;
        }
-       LOCAL_USER_ADD(u);
+       
+       u = ast_module_user_add(chan);
+
        park = atoi((char *)data);
        ast_mutex_lock(&parking_lock);
        pu = parkinglot;
        park = atoi((char *)data);
        ast_mutex_lock(&parking_lock);
        pu = parkinglot;
@@ -1695,70 +2140,59 @@ static int park_exec(struct ast_channel *chan, void *data)
                peer = pu->chan;
                con = ast_context_find(parking_con);
                if (con) {
                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");
                                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",
                } 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"
                        "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 */
                        );
 
                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);
                ast_answer(chan);
-       }
 
        if (peer) {
                /* Play a courtesy to the source(s) configured to prefix the bridge connecting */
                
                if (!ast_strlen_zero(courtesytone)) {
 
        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 (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);
                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);
@@ -1770,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);
 
                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));
                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);
 
                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 {
                /* 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);
                        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;
        }
                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;
        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";
 
        struct ast_call_feature *feature;
        char format[] = "%-25s %-7s %-7s\n";
 
@@ -1812,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 */
 
 
        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_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, "---------------", "-------", "-------");
        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");
                ast_cli(fd, "(none)\n");
-       }
        else {
                AST_LIST_LOCK(&feature_list);
        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_cli(fd, format, feature->sname, "no def", feature->exten);  
-               }
                AST_LIST_UNLOCK(&feature_list);
        }
        ast_cli(fd, "\nCall parking\n");
                AST_LIST_UNLOCK(&feature_list);
        }
        ast_cli(fd, "\nCall parking\n");
@@ -1841,13 +2275,129 @@ static int handle_showfeatures(int fd, int argc, char *argv[])
        return RESULT_SUCCESS;
 }
 
        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[] =
 static char showfeatures_help[] =
-"Usage: show features\n"
+"Usage: feature list\n"
 "       Lists currently configured features.\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;
 static int handle_parkedcalls(int fd, int argc, char *argv[])
 {
        struct parkeduser *cur;
@@ -1858,18 +2408,16 @@ static int handle_parkedcalls(int fd, int argc, char *argv[])
 
        ast_mutex_lock(&parking_lock);
 
 
        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->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
 
-               cur = cur->next;
                numparked++;
        }
                numparked++;
        }
-       ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : "");
-
        ast_mutex_unlock(&parking_lock);
        ast_mutex_unlock(&parking_lock);
+       ast_cli(fd, "%d parked call%s.\n", numparked, ESS(numparked));
+
 
        return RESULT_SUCCESS;
 }
 
        return RESULT_SUCCESS;
 }
@@ -1878,51 +2426,55 @@ static char showparked_help[] =
 "Usage: show parkedcalls\n"
 "       Lists currently parked calls.\n";
 
 "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 */
 
 /*! \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;
 {
        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))
        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");
 
 
        astman_send_ack(s, m, "Parked calls will follow");
 
-        ast_mutex_lock(&parking_lock);
+       ast_mutex_lock(&parking_lock);
 
 
-        cur=parkinglot;
-        while(cur) {
-                       ast_cli(s->fd, "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"
                        "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"
                        "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);
+       }
 
 
-       ast_cli(s->fd,
-       "Event: ParkedCallsComplete\r\n"
-       "%s"
-       "\r\n",idText);
+       astman_append(s,
+               "Event: ParkedCallsComplete\r\n"
+               "%s"
+               "\r\n",idText);
 
 
-        ast_mutex_unlock(&parking_lock);
+       ast_mutex_unlock(&parking_lock);
 
 
-        return RESULT_SUCCESS;
+       return RESULT_SUCCESS;
 }
 
 static char mandescr_park[] =
 }
 
 static char mandescr_park[] =
@@ -1932,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";  
 
 "      *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;
        char buf[BUFSIZ];
        int to = 0;
        int res = 0;
@@ -1964,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);
        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;
        }
 
                return 0;
        }
 
@@ -1980,8 +2532,8 @@ static int manager_park(struct mansession *s, struct message *m)
                astman_send_error(s, m, "Park failure");
        }
 
                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;
 }
 
        return 0;
 }
@@ -1992,7 +2544,7 @@ int ast_pickup_call(struct ast_channel *chan)
        struct ast_channel *cur = NULL;
        int res = -1;
 
        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) &&
                if (!cur->pbx && 
                        (cur != chan) &&
                        (chan->pickupgroup & cur->callgroup) &&
@@ -2000,7 +2552,7 @@ int ast_pickup_call(struct ast_channel *chan)
                         (cur->_state == AST_STATE_RING))) {
                                break;
                }
                         (cur->_state == AST_STATE_RING))) {
                                break;
                }
-               ast_mutex_unlock(&cur->lock);
+               ast_channel_unlock(cur);
        }
        if (cur) {
                if (option_debug)
        }
        if (cur) {
                if (option_debug)
@@ -2014,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 */
                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");
        } else  {
                if (option_debug)
                        ast_log(LOG_DEBUG, "No call pickup possible...\n");
@@ -2022,14 +2574,41 @@ int ast_pickup_call(struct ast_channel *chan)
        return res;
 }
 
        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;
 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 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 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);
 
        if (!ast_strlen_zero(parking_con)) {
                strcpy(old_parking_ext, parking_ext);
@@ -2041,225 +2620,463 @@ static int load_config(void)
        strcpy(parking_con_dial, "park-dial");
        strcpy(parking_ext, "700");
        strcpy(pickup_ext, "*8");
        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");
        parking_start = 701;
        parking_stop = 750;
        parkfindnext = 0;
        courtesytone[0] = '\0';
        strcpy(xfersound, "beep");
        strcpy(xferfailsound, "pbx-invalid");
        parking_start = 701;
        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;
 
        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");
 
        cfg = ast_config_load("features.conf");
-       if (cfg) {
-               var = ast_variable_browse(cfg, "general");
-               while(var) {
-                       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;
                        }
                        }
-                       var = var->next;
+               } 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();
-               var = ast_variable_browse(cfg, "featuremap");
-               while(var) {
-                       if (remap_feature(var->name, var->value))
-                               ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
-                       var = var->next;
+       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();
-               var = ast_variable_browse(cfg, "applicationmap");
-               while(var) {
-                       char *tmp_val=strdup(var->value);
-                       char *exten, *party=NULL, *app=NULL, *app_args=NULL; 
+               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;                                       
 
 
-                       if (!tmp_val) { 
-                               ast_log(LOG_ERROR, "res_features: strdup failed");
-                               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);
+
+               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;
+               }
 
 
-                       exten=strsep(&tmp_val,",");
-                       if (exten) party=strsep(&tmp_val,",");
-                       if (party) app=strsep(&tmp_val,",");
+               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;
+               }
 
 
-                       if (app) app_args=strsep(&tmp_val,",");
+               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);  
+       }
 
 
-                       if (!(app && strlen(app)) || !(exten && strlen(exten)) || !(party && strlen(party)) || !(var->name && strlen(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);
-                               var = var->next;
-                               continue;
-                       }
+       ast_unregister_groups();
+       AST_RWLIST_WRLOCK(&feature_groups);
 
 
-                       {
-                               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);
-                                               var = var->next;
-                                               continue;                                       
-                                       }
-                               }
+       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;
+               }
 
 
-                               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);
-                                       var = var->next;
-                                       continue;
-                               }
+               if (i < ARRAY_LEN(categories)) 
+                       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);  
+               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;
                        }
                        }
-                       var = var->next;
-               }        
+                       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_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))) {
-               if (!(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;
+       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;
+       }
+       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);
        }
        }
-       return ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
+
+       ast_module_user_remove(u);
+
+       return 0;
 }
 
 }
 
-int reload(void) {
+static int reload(void)
+{
        return load_config();
 }
 
        return load_config();
 }
 
-int load_module(void)
+static int load_module(void)
 {
        int res;
 {
        int res;
-       
-       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;
        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_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("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;
 }
 
 
        return res;
 }
 
 
-int unload_module(void)
+static int unload_module(void)
 {
 {
-       STANDARD_HANGUP_LOCALUSERS;
+       ast_module_user_hangup_all();
 
        ast_manager_unregister("ParkedCalls");
 
        ast_manager_unregister("ParkedCalls");
+       ast_manager_unregister("Bridge");
        ast_manager_unregister("Park");
        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(parkcall);
+       ast_unregister_application(app_bridge);
+       ast_devstate_prov_del("Park");
        return ast_unregister_application(parkedcall);
 }
 
        return ast_unregister_application(parkedcall);
 }
 
-char *description(void)
-{
-       return "Call Features Resource";
-}
-
-int usecount(void)
-{
-       /* Never allow parking to be unloaded because it will
-          unresolve needed symbols in the dialer */
-#if 0
-       int res;
-       STANDARD_USECOUNT(res);
-       return res;
-#else
-       return 1;
-#endif
-}
-
-char *key()
-{
-       return ASTERISK_GPL_KEY;
-}
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource",
+               .load = load_module,
+               .unload = unload_module,
+               .reload = reload,
+             );