Merge "astobj2: Create function to copy weak proxied objects from container."
[asterisk/asterisk.git] / apps / app_meetme.c
index f0ab259..6984bd2 100644 (file)
  *
  * \author Mark Spencer <markster@digium.com>
  * \author (SLA) Russell Bryant <russell@digium.com>
- * 
+ *
  * \ingroup applications
  */
 
+/*! \li \ref app_meetme.c uses configuration file \ref meetme.conf
+ * \addtogroup configuration_file Configuration Files
+ */
+
+/*!
+ * \page meetme.conf meetme.conf
+ * \verbinclude meetme.conf.sample
+ */
+
 /*** MODULEINFO
        <depend>dahdi</depend>
        <defaultenabled>no</defaultenabled>
-       <support_level>deprecated</support_level>
+       <support_level>extended</support_level>
        <replacement>app_confbridge</replacement>
  ***/
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include <dahdi/user.h>
 
 #include "asterisk/lock.h"
@@ -62,8 +69,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/dial.h"
 #include "asterisk/causes.h"
 #include "asterisk/paths.h"
-#include "asterisk/data.h"
 #include "asterisk/test.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_message_router.h"
+#include "asterisk/json.h"
+#include "asterisk/format_compatibility.h"
 
 #include "enter.h"
 #include "leave.h"
@@ -124,6 +135,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <option name="I">
                                                <para>Announce user join/leave without review.</para>
                                        </option>
+                                       <option name="k">
+                                               <para>Close the conference if there's only one active participant left at exit.</para>
+                                       </option>
                                        <option name="l">
                                                <para>Set listen only mode (Listen only, no talking).</para>
                                        </option>
@@ -136,6 +150,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                channel's currently set music class, or <literal>default</literal>.</para>
                                                <argument name="class" required="true" />
                                        </option>
+                                       <option name="n">
+                                               <para>Disable the denoiser. By default, if <literal>func_speex</literal> is loaded, Asterisk
+                                               will apply a denoiser to channels in the MeetMe conference. However, channel
+                                               drivers that present audio with a varying rate will experience degraded
+                                               performance with a denoiser attached. This parameter allows a channel joining
+                                               the conference to choose not to have a denoiser attached without having to
+                                               unload <literal>func_speex</literal>.</para>
+                                       </option>
                                        <option name="o">
                                                <para>Set talker optimization - treats talkers who aren't speaking as
                                                being muted, meaning (a) No encode is done on transmission and (b)
@@ -144,10 +166,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        </option>
                                        <option name="p" hasparams="optional">
                                                <para>Allow user to exit the conference by pressing <literal>#</literal> (default)
-                                               or any of the defined keys. If keys contain <literal>*</literal> this will override
-                                               option <literal>s</literal>. The key used is set to channel variable
+                                               or any of the defined keys. Dial plan execution will continue at the next
+                                               priority following MeetMe. The key used is set to channel variable
                                                <variable>MEETME_EXIT_KEY</variable>.</para>
                                                <argument name="keys" required="true" />
+                                               <note>
+                                                       <para>Option <literal>s</literal> has priority for <literal>*</literal>
+                                                       since it cannot change its activation code.</para>
+                                               </note>
                                        </option>
                                        <option name="P">
                                                <para>Always prompt for the pin even if it is specified.</para>
@@ -183,12 +209,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                <argument name="secs" required="true" />
                                        </option>
                                        <option name="x">
-                                               <para>Close the conference when last marked user exits</para>
+                                               <para>Leave the conference when the last marked user leaves.</para>
                                        </option>
                                        <option name="X">
                                                <para>Allow user to exit the conference by entering a valid single digit
                                                extension <variable>MEETME_EXIT_CONTEXT</variable> or the current context
                                                if that variable is not defined.</para>
+                                               <note>
+                                                       <para>Option <literal>s</literal> has priority for <literal>*</literal>
+                                                       since it cannot change its activation code.</para>
+                                               </note>
                                        </option>
                                        <option name="1">
                                                <para>Do not play message when first person enters</para>
@@ -223,7 +253,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Enters the user into a specified MeetMe conference.  If the <replaceable>confno</replaceable>
                        is omitted, the user will be prompted to enter one.  User can exit the conference by hangup, or
                        if the <literal>p</literal> option is specified, by pressing <literal>#</literal>.</para>
-                       <note><para>The DAHDI kernel modules and at least one hardware driver (or dahdi_dummy)
+                       <note><para>The DAHDI kernel modules and a functional DAHDI timing source (see dahdi_test)
                        must be present for conferencing to operate properly. In addition, the chan_dahdi channel driver
                        must be loaded for the <literal>i</literal> and <literal>r</literal> options to operate at
                        all.</para></note>
@@ -525,10 +555,93 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                MeetmeListRoomsComplete.</para>
                </description>
        </manager>
+       <managerEvent language="en_US" name="MeetmeJoin">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a user joins a MeetMe conference.</synopsis>
+                       <syntax>
+                               <parameter name="Meetme">
+                                       <para>The identifier for the MeetMe conference.</para>
+                               </parameter>
+                               <parameter name="User">
+                                       <para>The identifier of the MeetMe user who joined.</para>
+                               </parameter>
+                               <channel_snapshot/>
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">MeetmeLeave</ref>
+                               <ref type="application">MeetMe</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="MeetmeLeave">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a user leaves a MeetMe conference.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeJoin']/managerEventInstance/syntax/parameter)" />
+                               <channel_snapshot/>
+                               <parameter name="Duration">
+                                       <para>The length of time in seconds that the Meetme user was in the conference.</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">MeetmeJoin</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="MeetmeEnd">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a MeetMe conference ends.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeJoin']/managerEventInstance/syntax/parameter[@name='Meetme'])" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">MeetmeJoin</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="MeetmeTalkRequest">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a MeetMe user has started talking.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeJoin']/managerEventInstance/syntax/parameter)" />
+                               <channel_snapshot/>
+                               <parameter name="Duration">
+                                       <para>The length of time in seconds that the Meetme user has been in the conference at the time of this event.</para>
+                               </parameter>
+                               <parameter name="Status">
+                                       <enumlist>
+                                               <enum name="on"/>
+                                               <enum name="off"/>
+                                       </enumlist>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="MeetmeTalking">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a MeetMe user begins or ends talking.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeJoin']/managerEventInstance/syntax/parameter)" />
+                               <channel_snapshot/>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeTalkRequest']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="MeetmeMute">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a MeetMe user is muted or unmuted.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeJoin']/managerEventInstance/syntax/parameter)" />
+                               <channel_snapshot/>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='MeetmeTalkRequest']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
  ***/
 
-#define CONFIG_FILE_NAME "meetme.conf"
-#define SLA_CONFIG_FILE  "sla.conf"
+#define CONFIG_FILE_NAME       "meetme.conf"
+#define SLA_CONFIG_FILE                "sla.conf"
+#define STR_CONCISE                    "concise"
 
 /*! each buffer is 20ms, so this is 640ms total */
 #define DEFAULT_AUDIO_BUFFERS  32
@@ -542,6 +655,7 @@ enum {
        ADMINFLAG_KICKME =    (1 << 3),  /*!< User has been kicked */
        /*! User has requested to speak */
        ADMINFLAG_T_REQUEST = (1 << 4),
+       ADMINFLAG_HANGUP = (1 << 5),    /*!< User will be leaving the conference */
 };
 
 #define MEETME_DELAYDETECTTALK     300
@@ -581,14 +695,14 @@ enum {
        CONFFLAG_TALKER = (1 << 4),
        /*! If set there will be no enter or leave sounds */
        CONFFLAG_QUIET = (1 << 5),
-       /*! If set, when user joins the conference, they will be told the number 
+       /*! If set, when user joins the conference, they will be told the number
         *  of users that are already in */
        CONFFLAG_ANNOUNCEUSERCOUNT = (1 << 6),
        /*! Set to run AGI Script in Background */
        CONFFLAG_AGI = (1 << 7),
        /*! Set to have music on hold when user is alone in conference */
        CONFFLAG_MOH = (1 << 8),
-       /*! If set the MeetMe will return if all marked with this flag left */
+       /*! If set, the channel will leave the conference if all marked users leave */
        CONFFLAG_MARKEDEXIT = (1 << 9),
        /*! If set, the MeetMe will wait until a marked user enters */
        CONFFLAG_WAITMARKED = (1 << 10),
@@ -609,10 +723,10 @@ enum {
        CONFFLAG_ALWAYSPROMPT = (1 << 20),
        /*! If set, treat talking users as muted users */
        CONFFLAG_OPTIMIZETALKER = (1 << 21),
-       /*! If set, won't speak the extra prompt when the first person 
+       /*! If set, won't speak the extra prompt when the first person
         *  enters the conference */
        CONFFLAG_NOONLYPERSON = (1 << 22),
-       /*! If set, user will be asked to record name on entry of conference 
+       /*! If set, user will be asked to record name on entry of conference
         *  without review */
        CONFFLAG_INTROUSERNOREVIEW = (1 << 23),
        /*! If set, the user will be initially self-muted */
@@ -625,14 +739,18 @@ enum {
        CONFFLAG_KICK_CONTINUE = (1 << 28),
        CONFFLAG_DURATION_STOP = (1 << 29),
        CONFFLAG_DURATION_LIMIT = (1 << 30),
-       /*! Do not write any audio to this channel until the state is up. */
-       CONFFLAG_NO_AUDIO_UNTIL_UP = (1 << 31),
 };
 
-/* These flags are defined separately because we ran out of bits that an enum can be used to represent. 
-   If you add new flags, be sure to do it in the same way that CONFFLAG_INTROMSG is. */
-#define CONFFLAG_INTROMSG ((uint64_t)1 << 32)   /*!< If set play an intro announcement at start of conference */
-#define CONFFLAG_INTROUSER_VMREC ((uint64_t)1 << 33)
+/* These flags are defined separately because we ran out of bits that an enum can be used to represent.
+   If you add new flags, be sure to do it in the same way that these are. */
+/*! Do not write any audio to this channel until the state is up. */
+#define CONFFLAG_NO_AUDIO_UNTIL_UP  (1ULL << 31)
+#define CONFFLAG_INTROMSG           (1ULL << 32) /*!< If set play an intro announcement at start of conference */
+#define CONFFLAG_INTROUSER_VMREC    (1ULL << 33)
+/*! If there's only one person left in a conference when someone leaves, kill the conference */
+#define CONFFLAG_KILL_LAST_MAN_STANDING (1ULL << 34)
+/*! If set, don't enable a denoiser for the channel */
+#define CONFFLAG_DONT_DENOISE       (1ULL << 35)
 
 enum {
        OPT_ARG_WAITMARKED = 0,
@@ -660,8 +778,10 @@ AST_APP_OPTIONS(meetme_opts, BEGIN_OPTIONS
        AST_APP_OPTION_ARG('v', CONFFLAG_INTROUSER_VMREC , OPT_ARG_INTROUSER_VMREC),
        AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
        AST_APP_OPTION('I', CONFFLAG_INTROUSERNOREVIEW ),
+       AST_APP_OPTION('k', CONFFLAG_KILL_LAST_MAN_STANDING ),
        AST_APP_OPTION_ARG('M', CONFFLAG_MOH, OPT_ARG_MOH_CLASS ),
        AST_APP_OPTION('m', CONFFLAG_STARTMUTED ),
+       AST_APP_OPTION('n', CONFFLAG_DONT_DENOISE ),
        AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ),
        AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
        AST_APP_OPTION_ARG('p', CONFFLAG_KEYEXIT, OPT_ARG_EXITKEYS ),
@@ -822,9 +942,9 @@ struct sla_trunk_ref;
 struct sla_station {
        AST_RWLIST_ENTRY(sla_station) entry;
        AST_DECLARE_STRING_FIELDS(
-               AST_STRING_FIELD(name); 
-               AST_STRING_FIELD(device);       
-               AST_STRING_FIELD(autocontext);  
+               AST_STRING_FIELD(name);
+               AST_STRING_FIELD(device);
+               AST_STRING_FIELD(autocontext);
        );
        AST_LIST_HEAD_NOLOCK(, sla_trunk_ref) trunks;
        struct ast_dial *dial;
@@ -839,21 +959,30 @@ struct sla_station {
        /*! This option uses the values in the sla_hold_access enum and sets the
         * access control type for hold on this station. */
        unsigned int hold_access:1;
-       /*! Use count for inside sla_station_exec */
-       unsigned int ref_count;
+       /*! Mark used during reload processing */
+       unsigned int mark:1;
 };
 
+/*!
+ * \brief A reference to a station
+ *
+ * This struct looks near useless at first glance.  However, its existence
+ * in the list of stations in sla_trunk means that this station references
+ * that trunk.  We use the mark to keep track of whether it needs to be
+ * removed from the sla_trunk's list of stations during a reload.
+ */
 struct sla_station_ref {
        AST_LIST_ENTRY(sla_station_ref) entry;
        struct sla_station *station;
+       /*! Mark used during reload processing */
+       unsigned int mark:1;
 };
 
 struct sla_trunk {
-       AST_RWLIST_ENTRY(sla_trunk) entry;
        AST_DECLARE_STRING_FIELDS(
                AST_STRING_FIELD(name);
                AST_STRING_FIELD(device);
-               AST_STRING_FIELD(autocontext);  
+               AST_STRING_FIELD(autocontext);
        );
        AST_LIST_HEAD_NOLOCK(, sla_station_ref) stations;
        /*! Number of stations that use this trunk */
@@ -873,10 +1002,16 @@ struct sla_trunk {
        /*! Whether this trunk is currently on hold, meaning that once a station
         *  connects to it, the trunk channel needs to have UNHOLD indicated to it. */
        unsigned int on_hold:1;
-       /*! Use count for inside sla_trunk_exec */
-       unsigned int ref_count;
+       /*! Mark used during reload processing */
+       unsigned int mark:1;
 };
 
+/*!
+ * \brief A station's reference to a trunk
+ *
+ * An sla_station keeps a list of trunk_refs.  This holds metadata about the
+ * stations usage of the trunk.
+ */
 struct sla_trunk_ref {
        AST_LIST_ENTRY(sla_trunk_ref) entry;
        struct sla_trunk *trunk;
@@ -890,10 +1025,12 @@ struct sla_trunk_ref {
         *  station.  This takes higher priority than a ring delay set at
         *  the station level. */
        unsigned int ring_delay;
+       /*! Mark used during reload processing */
+       unsigned int mark:1;
 };
 
-static AST_RWLIST_HEAD_STATIC(sla_stations, sla_station);
-static AST_RWLIST_HEAD_STATIC(sla_trunks, sla_trunk);
+static struct ao2_container *sla_stations;
+static struct ao2_container *sla_trunks;
 
 static const char sla_registrar[] = "SLA";
 
@@ -905,10 +1042,6 @@ enum sla_event_type {
        SLA_EVENT_DIAL_STATE,
        /*! The state of a ringing trunk has changed */
        SLA_EVENT_RINGING_TRUNK,
-       /*! A reload of configuration has been requested */
-       SLA_EVENT_RELOAD,
-       /*! Poke the SLA thread so it can check if it can perform a reload */
-       SLA_EVENT_CHECK_RELOAD,
 };
 
 struct sla_event {
@@ -918,7 +1051,7 @@ struct sla_event {
        AST_LIST_ENTRY(sla_event) entry;
 };
 
-/*! \brief A station that failed to be dialed 
+/*! \brief A station that failed to be dialed
  * \note Only used by the SLA thread. */
 struct sla_failed_station {
        struct sla_station *station;
@@ -964,8 +1097,6 @@ static struct {
        /*! Attempt to handle CallerID, even though it is known not to work
         *  properly in some situations. */
        unsigned int attempt_callerid:1;
-       /*! A reload has been requested */
-       unsigned int reload:1;
 } sla = {
        .thread = AST_PTHREADT_NULL,
 };
@@ -974,7 +1105,7 @@ static struct {
  *  when in a conference */
 static int audio_buffers;
 
-/*! \brief Map 'volume' levels from -5 through +5 into decibel (dB) 
+/*! \brief Map 'volume' levels from -5 through +5 into decibel (dB)
  *    settings for channel drivers.
  *
  *  \note these are not a straight linear-to-dB
@@ -995,6 +1126,271 @@ static const char gain_map[] = {
        15,
 };
 
+/* Routes the various meetme message types to the meetme stasis callback function to turn them into events */
+static struct stasis_message_router *meetme_event_message_router;
+
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(meetme_join_type);
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(meetme_leave_type);
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(meetme_end_type);
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(meetme_mute_type);
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(meetme_talking_type);
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(meetme_talk_request_type);
+
+static void meetme_stasis_cb(void *data, struct stasis_subscription *sub,
+       struct stasis_message *message);
+
+static void meetme_stasis_cleanup(void)
+{
+       if (meetme_event_message_router) {
+               stasis_message_router_unsubscribe(meetme_event_message_router);
+               meetme_event_message_router = NULL;
+       }
+
+       STASIS_MESSAGE_TYPE_CLEANUP(meetme_join_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(meetme_leave_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(meetme_end_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(meetme_mute_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(meetme_talking_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(meetme_talk_request_type);
+}
+
+static int meetme_stasis_init(void)
+{
+
+       STASIS_MESSAGE_TYPE_INIT(meetme_join_type);
+       STASIS_MESSAGE_TYPE_INIT(meetme_leave_type);
+       STASIS_MESSAGE_TYPE_INIT(meetme_end_type);
+       STASIS_MESSAGE_TYPE_INIT(meetme_mute_type);
+       STASIS_MESSAGE_TYPE_INIT(meetme_talking_type);
+       STASIS_MESSAGE_TYPE_INIT(meetme_talk_request_type);
+
+       meetme_event_message_router = stasis_message_router_create(
+               ast_channel_topic_all_cached());
+
+       if (!meetme_event_message_router) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       if (stasis_message_router_add(meetme_event_message_router,
+                       meetme_join_type(),
+                       meetme_stasis_cb,
+                       NULL)) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       if (stasis_message_router_add(meetme_event_message_router,
+                       meetme_leave_type(),
+                       meetme_stasis_cb,
+                       NULL)) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       if (stasis_message_router_add(meetme_event_message_router,
+                       meetme_end_type(),
+                       meetme_stasis_cb,
+                       NULL)) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       if (stasis_message_router_add(meetme_event_message_router,
+                       meetme_mute_type(),
+                       meetme_stasis_cb,
+                       NULL)) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       if (stasis_message_router_add(meetme_event_message_router,
+                       meetme_talking_type(),
+                       meetme_stasis_cb,
+                       NULL)) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       if (stasis_message_router_add(meetme_event_message_router,
+                       meetme_talk_request_type(),
+                       meetme_stasis_cb,
+                       NULL)) {
+               meetme_stasis_cleanup();
+               return -1;
+       }
+
+       return 0;
+}
+
+static void meetme_stasis_cb(void *data, struct stasis_subscription *sub,
+       struct stasis_message *message)
+{
+       struct ast_channel_blob *channel_blob = stasis_message_data(message);
+       struct stasis_message_type *message_type;
+       const char *event;
+       const char *conference_num;
+       const char *status;
+       struct ast_json *json_cur;
+       RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+       RAII_VAR(struct ast_str *, extra_text, NULL, ast_free);
+
+       if (!channel_blob) {
+               ast_assert(0);
+               return;
+       }
+
+       message_type = stasis_message_type(message);
+
+       if (!message_type) {
+               ast_assert(0);
+               return;
+       }
+
+       if (message_type == meetme_join_type()) {
+               event = "MeetmeJoin";
+       } else if (message_type == meetme_leave_type()) {
+               event = "MeetmeLeave";
+       } else if (message_type == meetme_end_type()) {
+               event = "MeetmeEnd";
+       } else if (message_type == meetme_mute_type()) {
+               event = "MeetmeMute";
+       } else if (message_type == meetme_talking_type()) {
+               event = "MeetmeTalking";
+       } else if (message_type == meetme_talk_request_type()) {
+               event = "MeetmeTalkRequest";
+       } else {
+               ast_assert(0);
+               return;
+       }
+
+       if (!event) {
+               ast_assert(0);
+               return;
+       }
+
+       conference_num = ast_json_string_get(ast_json_object_get(channel_blob->blob, "Meetme"));
+       if (!conference_num) {
+               ast_assert(0);
+               return;
+       }
+
+       status = ast_json_string_get(ast_json_object_get(channel_blob->blob, "status"));
+       if (status) {
+               ast_str_append_event_header(&extra_text, "Status", status);
+       }
+
+       if (channel_blob->snapshot) {
+               channel_text = ast_manager_build_channel_state_string(channel_blob->snapshot);
+       }
+
+       if ((json_cur = ast_json_object_get(channel_blob->blob, "user"))) {
+               int user_number = ast_json_integer_get(json_cur);
+               RAII_VAR(struct ast_str *, user_prop_str, ast_str_create(32), ast_free);
+               if (!user_prop_str) {
+                       return;
+               }
+
+               ast_str_set(&user_prop_str, 0, "%d", user_number);
+               ast_str_append_event_header(&extra_text, "User", ast_str_buffer(user_prop_str));
+
+               if ((json_cur = ast_json_object_get(channel_blob->blob, "duration"))) {
+                       int duration = ast_json_integer_get(json_cur);
+                       ast_str_set(&user_prop_str, 0, "%d", duration);
+                       ast_str_append_event_header(&extra_text, "Duration", ast_str_buffer(user_prop_str));
+               }
+
+               json_cur = NULL;
+       }
+
+       manager_event(EVENT_FLAG_CALL, event,
+               "Meetme: %s\r\n"
+               "%s"
+               "%s",
+               conference_num,
+               channel_text ? ast_str_buffer(channel_text) : "",
+               extra_text ? ast_str_buffer(extra_text) : "");
+}
+
+/*!
+ * \internal
+ * \brief Build a json object from a status value for inclusion in json extras for meetme_stasis_generate_msg
+ * \since 12.0.0
+ *
+ * \param on if true, then status is on. Otherwise status is off
+ * \retval NULL on failure to allocate the JSON blob.
+ * \retval pointer to the JSON blob if successful.
+ */
+static struct ast_json *status_to_json(int on)
+{
+       struct ast_json *json_object = ast_json_pack("{s: s}",
+               "status", on ? "on" : "off");
+
+       return json_object;
+}
+
+/*!
+ * \internal
+ * \brief Generate a stasis message associated with a meetme event
+ * \since 12.0.0
+ *
+ * \param meetme_confere The conference responsible for generating this message
+ * \param chan The channel involved in the message (NULL allowed)
+ * \param user The conference user involved in the message (NULL allowed)
+ * \param message_type the type the stasis message being generated
+ * \param extras Additional json fields desired for inclusion
+ */
+static void meetme_stasis_generate_msg(struct ast_conference *meetme_conference, struct ast_channel *chan,
+       struct ast_conf_user *user, struct stasis_message_type *message_type, struct ast_json *extras)
+{
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
+
+       json_object = ast_json_pack("{s: s}",
+               "Meetme", meetme_conference->confno);
+
+       if (!json_object) {
+               return;
+       }
+
+       if (extras) {
+               ast_json_object_update(json_object, extras);
+       }
+
+       if (user) {
+               struct timeval now = ast_tvnow();
+               long duration = (long)(now.tv_sec - user->jointime);
+               struct ast_json *json_user;
+               struct ast_json *json_user_duration;
+
+               json_user = ast_json_integer_create(user->user_no);
+               if (!json_user || ast_json_object_set(json_object, "user", json_user)) {
+                       return;
+               }
+
+               if (duration > 0) {
+                       json_user_duration = ast_json_integer_create(duration);
+                       if (!json_user_duration
+                               || ast_json_object_set(json_object, "duration", json_user_duration)) {
+                               return;
+                       }
+               }
+       }
+
+       if (chan) {
+               ast_channel_lock(chan);
+       }
+       msg = ast_channel_blob_create(chan, message_type, json_object);
+       if (chan) {
+               ast_channel_unlock(chan);
+       }
+
+       if (!msg) {
+               return;
+       }
+
+       stasis_publish(ast_channel_topic(chan), msg);
+}
 
 static int admin_exec(struct ast_channel *chan, const char *data);
 static void *recordthread(void *args);
@@ -1005,7 +1401,7 @@ static const char *istalking(int x)
                return "(talking)";
        else if (x < 0)
                return "(unmonitored)";
-       else 
+       else
                return "(not talking)";
 }
 
@@ -1064,7 +1460,7 @@ static void tweak_volume(struct volume *vol, enum volume_action action)
 {
        switch (action) {
        case VOL_UP:
-               switch (vol->desired) { 
+               switch (vol->desired) {
                case 5:
                        break;
                case 0:
@@ -1133,6 +1529,13 @@ static void conf_play(struct ast_channel *chan, struct ast_conference *conf, enu
        int len;
        int res = -1;
 
+       ast_test_suite_event_notify("CONFPLAY", "Channel: %s\r\n"
+               "Conference: %s\r\n"
+               "Marked: %d",
+               ast_channel_name(chan),
+               conf->confno,
+               conf->markedusers);
+
        if (!ast_check_hangup(chan))
                res = ast_autoservice_start(chan);
 
@@ -1157,7 +1560,7 @@ static void conf_play(struct ast_channel *chan, struct ast_conference *conf, enu
 
        AST_LIST_UNLOCK(&confs);
 
-       if (!res) 
+       if (!res)
                ast_autoservice_stop(chan);
 }
 
@@ -1195,6 +1598,7 @@ static int user_max_cmp(void *obj, void *arg, int flags)
  * \param dynamic Mark the newly created conference as dynamic
  * \param refcount How many references to mark on the conference
  * \param chan The asterisk channel
+ * \param test
  *
  * \return A pointer to the conference struct, or NULL if it wasn't found and
  *         make or dynamic were not set.
@@ -1206,23 +1610,28 @@ static struct ast_conference *build_conf(const char *confno, const char *pin,
        struct ast_conference *cnf;
        struct dahdi_confinfo dahdic = { 0, };
        int confno_int = 0;
-       struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
-       struct ast_format tmp_fmt;
+       struct ast_format_cap *cap_slin = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 
        AST_LIST_LOCK(&confs);
 
        AST_LIST_TRAVERSE(&confs, cnf, list) {
-               if (!strcmp(confno, cnf->confno)) 
+               if (!strcmp(confno, cnf->confno))
                        break;
        }
 
        if (cnf || (!make && !dynamic) || !cap_slin)
                goto cnfout;
 
-       ast_format_cap_add(cap_slin, ast_format_set(&tmp_fmt, AST_FORMAT_SLINEAR, 0));
+       ast_format_cap_append(cap_slin, ast_format_slin, 0);
        /* Make a new one */
-       if (!(cnf = ast_calloc(1, sizeof(*cnf))) ||
-               !(cnf->usercontainer = ao2_container_alloc(1, NULL, user_no_cmp))) {
+       cnf = ast_calloc(1, sizeof(*cnf));
+       if (!cnf) {
+               goto cnfout;
+       }
+
+       cnf->usercontainer = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+               NULL, user_no_cmp);
+       if (!cnf->usercontainer) {
                goto cnfout;
        }
 
@@ -1235,7 +1644,7 @@ static struct ast_conference *build_conf(const char *confno, const char *pin,
        ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
        ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
        ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
-       ast_copy_string(cnf->uniqueid, chan->uniqueid, sizeof(cnf->uniqueid));
+       ast_copy_string(cnf->uniqueid, ast_channel_uniqueid(chan), sizeof(cnf->uniqueid));
 
        /* Setup a new dahdi conference */
        dahdic.confno = -1;
@@ -1265,14 +1674,14 @@ static struct ast_conference *build_conf(const char *confno, const char *pin,
        cnf->dahdiconf = dahdic.confno;
 
        /* Setup a new channel for playback of audio files */
-       cnf->chan = ast_request("DAHDI", cap_slin, chan, "pseudo", NULL);
+       cnf->chan = ast_request("DAHDI", cap_slin, NULL, chan, "pseudo", NULL);
        if (cnf->chan) {
-               ast_set_read_format_by_id(cnf->chan, AST_FORMAT_SLINEAR);
-               ast_set_write_format_by_id(cnf->chan, AST_FORMAT_SLINEAR);
+               ast_set_read_format(cnf->chan, ast_format_slin);
+               ast_set_write_format(cnf->chan, ast_format_slin);
                dahdic.chan = 0;
                dahdic.confno = cnf->dahdiconf;
                dahdic.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
-               if (ioctl(cnf->chan->fds[0], DAHDI_SETCONF, &dahdic)) {
+               if (ioctl(ast_channel_fd(cnf->chan, 0), DAHDI_SETCONF, &dahdic)) {
                        if (test) {
                                ast_test_status_update(test, "Error setting conference on pseudo channel\n");
                        }
@@ -1302,9 +1711,9 @@ static struct ast_conference *build_conf(const char *confno, const char *pin,
        /* Reserve conference number in map */
        if ((sscanf(cnf->confno, "%30d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024))
                conf_map[confno_int] = 1;
-       
+
 cnfout:
-       cap_slin = ast_format_cap_destroy(cap_slin);
+       ao2_cleanup(cap_slin);
        if (cnf)
                ast_atomic_fetchadd_int(&cnf->refcount, refcount);
 
@@ -1313,71 +1722,131 @@ cnfout:
        return cnf;
 }
 
-static char *complete_meetmecmd(const char *line, const char *word, int pos, int state)
+static char *complete_confno(const char *word, int state)
 {
-       static const char * const cmds[] = {"concise", "lock", "unlock", "mute", "unmute", "kick", "list", NULL};
-
+       struct ast_conference *cnf;
+       char *ret = NULL;
+       int which = 0;
        int len = strlen(word);
+
+       AST_LIST_LOCK(&confs);
+       AST_LIST_TRAVERSE(&confs, cnf, list) {
+               if (!strncmp(word, cnf->confno, len) && ++which > state) {
+                       /* dup before releasing the lock */
+                       ret = ast_strdup(cnf->confno);
+                       break;
+               }
+       }
+       AST_LIST_UNLOCK(&confs);
+       return ret;
+}
+
+static char *complete_userno(struct ast_conference *cnf, const char *word, int state)
+{
+       char usrno[50];
+       struct ao2_iterator iter;
+       struct ast_conf_user *usr;
+       char *ret = NULL;
        int which = 0;
-       struct ast_conference *cnf = NULL;
-       struct ast_conf_user *usr = NULL;
-       char *confno = NULL;
-       char usrno[50] = "";
-       char *myline, *ret = NULL;
-       
-       if (pos == 1) {         /* Command */
-               return ast_cli_complete(word, cmds, state);
-       } else if (pos == 2) {  /* Conference Number */
+       int len = strlen(word);
+
+       iter = ao2_iterator_init(cnf->usercontainer, 0);
+       for (; (usr = ao2_iterator_next(&iter)); ao2_ref(usr, -1)) {
+               snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
+               if (!strncmp(word, usrno, len) && ++which > state) {
+                       ao2_ref(usr, -1);
+                       ret = ast_strdup(usrno);
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&iter);
+       return ret;
+}
+
+static char *complete_meetmecmd_mute_kick(const char *line, const char *word, int pos, int state)
+{
+       if (pos == 2) {
+               return complete_confno(word, state);
+       }
+       if (pos == 3) {
+               int len = strlen(word);
+               char *ret = NULL;
+               char *saved = NULL;
+               char *myline;
+               char *confno;
+               struct ast_conference *cnf;
+
+               if (!strncasecmp(word, "all", len)) {
+                       if (state == 0) {
+                               return ast_strdup("all");
+                       }
+                       --state;
+               }
+
+               /* Extract the confno from the command line. */
+               myline = ast_strdupa(line);
+               strtok_r(myline, " ", &saved);
+               strtok_r(NULL, " ", &saved);
+               confno = strtok_r(NULL, " ", &saved);
+
                AST_LIST_LOCK(&confs);
                AST_LIST_TRAVERSE(&confs, cnf, list) {
-                       if (!strncasecmp(word, cnf->confno, len) && ++which > state) {
-                               ret = cnf->confno;
+                       if (!strcmp(confno, cnf->confno)) {
+                               ret = complete_userno(cnf, word, state);
                                break;
                        }
                }
-               ret = ast_strdup(ret); /* dup before releasing the lock */
                AST_LIST_UNLOCK(&confs);
+
                return ret;
-       } else if (pos == 3) {
-               /* User Number || Conf Command option*/
-               if (strstr(line, "mute") || strstr(line, "kick")) {
-                       if (state == 0 && (strstr(line, "kick") || strstr(line, "mute")) && !strncasecmp(word, "all", len))
-                               return ast_strdup("all");
-                       which++;
-                       AST_LIST_LOCK(&confs);
+       }
+       return NULL;
+}
 
-                       /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
-                       myline = ast_strdupa(line);
-                       if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
-                               while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
-                                       ;
-                       }
-                       
-                       AST_LIST_TRAVERSE(&confs, cnf, list) {
-                               if (!strcmp(confno, cnf->confno))
-                                   break;
-                       }
+static char *complete_meetmecmd_lock(const char *word, int pos, int state)
+{
+       if (pos == 2) {
+               return complete_confno(word, state);
+       }
+       return NULL;
+}
 
-                       if (cnf) {
-                               struct ao2_iterator user_iter;
-                               user_iter = ao2_iterator_init(cnf->usercontainer, 0);
+static char *complete_meetmecmd_list(const char *line, const char *word, int pos, int state)
+{
+       int len;
 
-                               while((usr = ao2_iterator_next(&user_iter))) {
-                                       snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
-                                       if (!strncasecmp(word, usrno, len) && ++which > state) {
-                                               ao2_ref(usr, -1);
-                                               break;
-                                       }
-                                       ao2_ref(usr, -1);
-                               }
-                               ao2_iterator_destroy(&user_iter);
-                               AST_LIST_UNLOCK(&confs);
-                               return usr ? ast_strdup(usrno) : NULL;
+       if (pos == 2) {
+               len = strlen(word);
+               if (!strncasecmp(word, STR_CONCISE, len)) {
+                       if (state == 0) {
+                               return ast_strdup(STR_CONCISE);
                        }
-                       AST_LIST_UNLOCK(&confs);
+                       --state;
                }
+
+               return complete_confno(word, state);
        }
+       if (pos == 3 && state == 0) {
+               char *saved = NULL;
+               char *myline;
+               char *confno;
+
+               /* Extract the confno from the command line. */
+               myline = ast_strdupa(line);
+               strtok_r(myline, " ", &saved);
+               strtok_r(NULL, " ", &saved);
+               confno = strtok_r(NULL, " ", &saved);
+
+               if (!strcasecmp(confno, STR_CONCISE)) {
+                       /* There is nothing valid in this position now. */
+                       return NULL;
+               }
 
+               len = strlen(word);
+               if (!strncasecmp(word, STR_CONCISE, len)) {
+                       return ast_strdup(STR_CONCISE);
+               }
+       }
        return NULL;
 }
 
@@ -1387,37 +1856,31 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
        struct ast_conf_user *user;
        struct ast_conference *cnf;
        int hr, min, sec;
-       int i = 0, total = 0;
+       int total = 0;
        time_t now;
-       struct ast_str *cmdline = NULL;
 #define MC_HEADER_FORMAT "%-14s %-14s %-10s %-8s  %-8s  %-6s\n"
 #define MC_DATA_FORMAT "%-12.12s   %4.4d             %4.4s       %02d:%02d:%02d  %-8s  %-6s\n"
 
        switch (cmd) {
        case CLI_INIT:
-               e->command = "meetme list [concise]";
+               e->command = "meetme list";
                e->usage =
-                       "Usage: meetme list [concise] <confno> \n"
-                       "       List all or a specific conference.\n";
+                       "Usage: meetme list [<confno>] [" STR_CONCISE "]\n"
+                       "       List all conferences or a specific conference.\n";
                return NULL;
        case CLI_GENERATE:
-               return complete_meetmecmd(a->line, a->word, a->pos, a->n);
+               return complete_meetmecmd_list(a->line, a->word, a->pos, a->n);
        }
 
-       /* Check for length so no buffer will overflow... */
-       for (i = 0; i < a->argc; i++) {
-               if (strlen(a->argv[i]) > 100)
-                       ast_cli(a->fd, "Invalid Arguments.\n");
-       }
+       if (a->argc == 2 || (a->argc == 3 && !strcasecmp(a->argv[2], STR_CONCISE))) {
+               /* List all the conferences */
+               int concise = (a->argc == 3);
+               struct ast_str *marked_users;
 
-       /* Max confno length */
-       if (!(cmdline = ast_str_create(MAX_CONFNUM))) {
-               return CLI_FAILURE;
-       }
+               if (!(marked_users = ast_str_create(30))) {
+                       return CLI_FAILURE;
+               }
 
-       if (a->argc == 2 || (a->argc == 3 && !strcasecmp(a->argv[2], "concise"))) {
-               /* List all the conferences */  
-               int concise = (a->argc == 3 && !strcasecmp(a->argv[2], "concise"));
                now = time(NULL);
                AST_LIST_LOCK(&confs);
                if (AST_LIST_EMPTY(&confs)) {
@@ -1425,23 +1888,25 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                                ast_cli(a->fd, "No active MeetMe conferences.\n");
                        }
                        AST_LIST_UNLOCK(&confs);
-                       ast_free(cmdline);
+                       ast_free(marked_users);
                        return CLI_SUCCESS;
                }
                if (!concise) {
                        ast_cli(a->fd, MC_HEADER_FORMAT, "Conf Num", "Parties", "Marked", "Activity", "Creation", "Locked");
                }
                AST_LIST_TRAVERSE(&confs, cnf, list) {
-                       if (cnf->markedusers == 0) {
-                               ast_str_set(&cmdline, 0, "N/A ");
-                       } else {
-                               ast_str_set(&cmdline, 0, "%4.4d", cnf->markedusers);
-                       }
                        hr = (now - cnf->start) / 3600;
                        min = ((now - cnf->start) % 3600) / 60;
                        sec = (now - cnf->start) % 60;
                        if (!concise) {
-                               ast_cli(a->fd, MC_DATA_FORMAT, cnf->confno, cnf->users, ast_str_buffer(cmdline), hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static", cnf->locked ? "Yes" : "No");
+                               if (cnf->markedusers == 0) {
+                                       ast_str_set(&marked_users, 0, "N/A ");
+                               } else {
+                                       ast_str_set(&marked_users, 0, "%4.4d", cnf->markedusers);
+                               }
+                               ast_cli(a->fd, MC_DATA_FORMAT, cnf->confno, cnf->users,
+                                       ast_str_buffer(marked_users), hr, min, sec,
+                                       cnf->isdynamic ? "Dynamic" : "Static", cnf->locked ? "Yes" : "No");
                        } else {
                                ast_cli(a->fd, "%s!%d!%d!%02d:%02d:%02d!%d!%d\n",
                                        cnf->confno,
@@ -1458,18 +1923,19 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                if (!concise) {
                        ast_cli(a->fd, "* Total number of MeetMe users: %d\n", total);
                }
-               ast_free(cmdline);
+               ast_free(marked_users);
                return CLI_SUCCESS;
-       } else if (strcmp(a->argv[1], "list") == 0) {
+       }
+       if (a->argc == 3 || (a->argc == 4 && !strcasecmp(a->argv[3], STR_CONCISE))) {
                struct ao2_iterator user_iter;
-               int concise = (a->argc == 4 && (!strcasecmp(a->argv[3], "concise")));
+               int concise = (a->argc == 4);
+
                /* List all the users in a conference */
                if (AST_LIST_EMPTY(&confs)) {
                        if (!concise) {
                                ast_cli(a->fd, "No active MeetMe conferences.\n");
                        }
-                       ast_free(cmdline);
-                       return CLI_SUCCESS;     
+                       return CLI_SUCCESS;
                }
                /* Find the right conference */
                AST_LIST_LOCK(&confs);
@@ -1482,7 +1948,6 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                        if (!concise)
                                ast_cli(a->fd, "No such conference: %s.\n", a->argv[2]);
                        AST_LIST_UNLOCK(&confs);
-                       ast_free(cmdline);
                        return CLI_SUCCESS;
                }
                /* Show all the users */
@@ -1495,20 +1960,20 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                        if (!concise) {
                                ast_cli(a->fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %s %02d:%02d:%02d\n",
                                        user->user_no,
-                                       S_COR(user->chan->caller.id.number.valid, user->chan->caller.id.number.str, "<unknown>"),
-                                       S_COR(user->chan->caller.id.name.valid, user->chan->caller.id.name.str, "<no name>"),
-                                       user->chan->name,
+                                       S_COR(ast_channel_caller(user->chan)->id.number.valid, ast_channel_caller(user->chan)->id.number.str, "<unknown>"),
+                                       S_COR(ast_channel_caller(user->chan)->id.name.valid, ast_channel_caller(user->chan)->id.name.str, "<no name>"),
+                                       ast_channel_name(user->chan),
                                        ast_test_flag64(&user->userflags, CONFFLAG_ADMIN) ? "(Admin)" : "",
                                        ast_test_flag64(&user->userflags, CONFFLAG_MONITOR) ? "(Listen only)" : "",
                                        user->adminflags & ADMINFLAG_MUTED ? "(Admin Muted)" : user->adminflags & ADMINFLAG_SELFMUTED ? "(Muted)" : "",
                                        user->adminflags & ADMINFLAG_T_REQUEST ? "(Request to Talk)" : "",
-                                       istalking(user->talking), hr, min, sec); 
+                                       istalking(user->talking), hr, min, sec);
                        } else {
                                ast_cli(a->fd, "%d!%s!%s!%s!%s!%s!%s!%s!%d!%02d:%02d:%02d\n",
                                        user->user_no,
-                                       S_COR(user->chan->caller.id.number.valid, user->chan->caller.id.number.str, ""),
-                                       S_COR(user->chan->caller.id.name.valid, user->chan->caller.id.name.str, ""),
-                                       user->chan->name,
+                                       S_COR(ast_channel_caller(user->chan)->id.number.valid, ast_channel_caller(user->chan)->id.number.str, ""),
+                                       S_COR(ast_channel_caller(user->chan)->id.name.valid, ast_channel_caller(user->chan)->id.name.str, ""),
+                                       ast_channel_name(user->chan),
                                        ast_test_flag64(&user->userflags, CONFFLAG_ADMIN) ? "1" : "",
                                        ast_test_flag64(&user->userflags, CONFFLAG_MONITOR) ? "1" : "",
                                        user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED) ? "1" : "",
@@ -1522,93 +1987,49 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                        ast_cli(a->fd, "%d users in that conference.\n", cnf->users);
                }
                AST_LIST_UNLOCK(&confs);
-               ast_free(cmdline);
                return CLI_SUCCESS;
        }
-       if (a->argc < 2) {
-               ast_free(cmdline);
-               return CLI_SHOWUSAGE;
-       }
-
-       ast_debug(1, "Cmdline: %s\n", ast_str_buffer(cmdline));
-
-       admin_exec(NULL, ast_str_buffer(cmdline));
-       ast_free(cmdline);
-
-       return CLI_SUCCESS;
+       return CLI_SHOWUSAGE;
 }
 
 
-static char *meetme_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static char *meetme_cmd_helper(struct ast_cli_args *a)
 {
        /* Process the command */
-       struct ast_str *cmdline = NULL;
-       int i = 0;
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "meetme {lock|unlock|mute|unmute|kick}";
-               e->usage =
-                       "Usage: meetme (un)lock|(un)mute|kick <confno> <usernumber>\n"
-                       "       Executes a command for the conference or on a conferee\n";
-               return NULL;
-       case CLI_GENERATE:
-               return complete_meetmecmd(a->line, a->word, a->pos, a->n);
-       }
-
-       if (a->argc > 8)
-               ast_cli(a->fd, "Invalid Arguments.\n");
-       /* Check for length so no buffer will overflow... */
-       for (i = 0; i < a->argc; i++) {
-               if (strlen(a->argv[i]) > 100)
-                       ast_cli(a->fd, "Invalid Arguments.\n");
-       }
+       struct ast_str *cmdline;
 
        /* Max confno length */
        if (!(cmdline = ast_str_create(MAX_CONFNUM))) {
                return CLI_FAILURE;
        }
 
-       if (a->argc < 1) {
-               ast_free(cmdline);
-               return CLI_SHOWUSAGE;
-       }
-
        ast_str_set(&cmdline, 0, "%s", a->argv[2]);     /* Argv 2: conference number */
-       if (strstr(a->argv[1], "lock")) {
-               if (strcmp(a->argv[1], "lock") == 0) {
+       if (strcasestr(a->argv[1], "lock")) {
+               if (strcasecmp(a->argv[1], "lock") == 0) {
                        /* Lock */
                        ast_str_append(&cmdline, 0, ",L");
                } else {
                        /* Unlock */
                        ast_str_append(&cmdline, 0, ",l");
                }
-       } else if (strstr(a->argv[1], "mute")) { 
-               if (a->argc < 4) {
-                       ast_free(cmdline);
-                       return CLI_SHOWUSAGE;
-               }
-               if (strcmp(a->argv[1], "mute") == 0) {
+       } else if (strcasestr(a->argv[1], "mute")) {
+               if (strcasecmp(a->argv[1], "mute") == 0) {
                        /* Mute */
-                       if (strcmp(a->argv[3], "all") == 0) {
+                       if (strcasecmp(a->argv[3], "all") == 0) {
                                ast_str_append(&cmdline, 0, ",N");
                        } else {
-                               ast_str_append(&cmdline, 0, ",M,%s", a->argv[3]);       
+                               ast_str_append(&cmdline, 0, ",M,%s", a->argv[3]);
                        }
                } else {
                        /* Unmute */
-                       if (strcmp(a->argv[3], "all") == 0) {
+                       if (strcasecmp(a->argv[3], "all") == 0) {
                                ast_str_append(&cmdline, 0, ",n");
                        } else {
                                ast_str_append(&cmdline, 0, ",m,%s", a->argv[3]);
                        }
                }
-       } else if (strcmp(a->argv[1], "kick") == 0) {
-               if (a->argc < 4) {
-                       ast_free(cmdline);
-                       return CLI_SHOWUSAGE;
-               }
-               if (strcmp(a->argv[3], "all") == 0) {
+       } else if (strcasecmp(a->argv[1], "kick") == 0) {
+               if (strcasecmp(a->argv[3], "all") == 0) {
                        /* Kick all */
                        ast_str_append(&cmdline, 0, ",K");
                } else {
@@ -1616,6 +2037,10 @@ static char *meetme_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
                        ast_str_append(&cmdline, 0, ",k,%s", a->argv[3]);
                }
        } else {
+               /*
+                * Should never get here because it is already filtered by the
+                * callers.
+                */
                ast_free(cmdline);
                return CLI_SHOWUSAGE;
        }
@@ -1628,36 +2053,97 @@ static char *meetme_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
        return CLI_SUCCESS;
 }
 
-static const char *sla_hold_str(unsigned int hold_access)
+static char *meetme_lock_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       const char *hold = "Unknown";
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "meetme {lock|unlock}";
+               e->usage =
+                       "Usage: meetme lock|unlock <confno>\n"
+                       "       Lock or unlock a conference to new users.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return complete_meetmecmd_lock(a->word, a->pos, a->n);
+       }
 
-       switch (hold_access) {
-       case SLA_HOLD_OPEN:
-               hold = "Open";
-               break;
-       case SLA_HOLD_PRIVATE:
-               hold = "Private";
-       default:
-               break;
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
        }
 
-       return hold;
+       return meetme_cmd_helper(a);
 }
 
-static char *sla_show_trunks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static char *meetme_kick_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       const struct sla_trunk *trunk;
-
        switch (cmd) {
        case CLI_INIT:
-               e->command = "sla show trunks";
+               e->command = "meetme kick";
                e->usage =
-                       "Usage: sla show trunks\n"
-                       "       This will list all trunks defined in sla.conf\n";
+                       "Usage: meetme kick <confno> all|<userno>\n"
+                       "       Kick a conference or a user in a conference.\n";
                return NULL;
        case CLI_GENERATE:
-               return NULL;
+               return complete_meetmecmd_mute_kick(a->line, a->word, a->pos, a->n);
+       }
+
+       if (a->argc != 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       return meetme_cmd_helper(a);
+}
+
+static char *meetme_mute_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "meetme {mute|unmute}";
+               e->usage =
+                       "Usage: meetme mute|unmute <confno> all|<userno>\n"
+                       "       Mute or unmute a conference or a user in a conference.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return complete_meetmecmd_mute_kick(a->line, a->word, a->pos, a->n);
+       }
+
+       if (a->argc != 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       return meetme_cmd_helper(a);
+}
+
+static const char *sla_hold_str(unsigned int hold_access)
+{
+       const char *hold = "Unknown";
+
+       switch (hold_access) {
+       case SLA_HOLD_OPEN:
+               hold = "Open";
+               break;
+       case SLA_HOLD_PRIVATE:
+               hold = "Private";
+       default:
+               break;
+       }
+
+       return hold;
+}
+
+static char *sla_show_trunks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ao2_iterator i;
+       struct sla_trunk *trunk;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "sla show trunks";
+               e->usage =
+                       "Usage: sla show trunks\n"
+                       "       This will list all trunks defined in sla.conf\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
        }
 
        ast_cli(a->fd, "\n"
@@ -1665,12 +2151,17 @@ static char *sla_show_trunks(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                    "=== Configured SLA Trunks ===================================\n"
                    "=============================================================\n"
                    "===\n");
-       AST_RWLIST_RDLOCK(&sla_trunks);
-       AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
+       i = ao2_iterator_init(sla_trunks, 0);
+       for (; (trunk = ao2_iterator_next(&i)); ao2_ref(trunk, -1)) {
                struct sla_station_ref *station_ref;
                char ring_timeout[16] = "(none)";
-               if (trunk->ring_timeout)
+
+               ao2_lock(trunk);
+
+               if (trunk->ring_timeout) {
                        snprintf(ring_timeout, sizeof(ring_timeout), "%u Seconds", trunk->ring_timeout);
+               }
+
                ast_cli(a->fd, "=== ---------------------------------------------------------\n"
                            "=== Trunk Name:       %s\n"
                            "=== ==> Device:       %s\n"
@@ -1679,18 +2170,21 @@ static char *sla_show_trunks(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                            "=== ==> BargeAllowed: %s\n"
                            "=== ==> HoldAccess:   %s\n"
                            "=== ==> Stations ...\n",
-                           trunk->name, trunk->device, 
-                           S_OR(trunk->autocontext, "(none)"), 
+                           trunk->name, trunk->device,
+                           S_OR(trunk->autocontext, "(none)"),
                            ring_timeout,
                            trunk->barge_disabled ? "No" : "Yes",
                            sla_hold_str(trunk->hold_access));
-               AST_RWLIST_RDLOCK(&sla_stations);
-               AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry)
+
+               AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) {
                        ast_cli(a->fd, "===    ==> Station name: %s\n", station_ref->station->name);
-               AST_RWLIST_UNLOCK(&sla_stations);
+               }
+
                ast_cli(a->fd, "=== ---------------------------------------------------------\n===\n");
+
+               ao2_unlock(trunk);
        }
-       AST_RWLIST_UNLOCK(&sla_trunks);
+       ao2_iterator_destroy(&i);
        ast_cli(a->fd, "=============================================================\n\n");
 
        return CLI_SUCCESS;
@@ -1712,7 +2206,8 @@ static const char *trunkstate2str(enum sla_trunk_state state)
 
 static char *sla_show_stations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       const struct sla_station *station;
+       struct ao2_iterator i;
+       struct sla_station *station;
 
        switch (cmd) {
        case CLI_INIT:
@@ -1725,22 +2220,25 @@ static char *sla_show_stations(struct ast_cli_entry *e, int cmd, struct ast_cli_
                return NULL;
        }
 
-       ast_cli(a->fd, "\n" 
+       ast_cli(a->fd, "\n"
                    "=============================================================\n"
                    "=== Configured SLA Stations =================================\n"
                    "=============================================================\n"
                    "===\n");
-       AST_RWLIST_RDLOCK(&sla_stations);
-       AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
+       i = ao2_iterator_init(sla_stations, 0);
+       for (; (station = ao2_iterator_next(&i)); ao2_ref(station, -1)) {
                struct sla_trunk_ref *trunk_ref;
                char ring_timeout[16] = "(none)";
                char ring_delay[16] = "(none)";
+
+               ao2_lock(station);
+
                if (station->ring_timeout) {
-                       snprintf(ring_timeout, sizeof(ring_timeout), 
+                       snprintf(ring_timeout, sizeof(ring_timeout),
                                "%u", station->ring_timeout);
                }
                if (station->ring_delay) {
-                       snprintf(ring_delay, sizeof(ring_delay), 
+                       snprintf(ring_delay, sizeof(ring_delay),
                                "%u", station->ring_delay);
                }
                ast_cli(a->fd, "=== ---------------------------------------------------------\n"
@@ -1752,34 +2250,37 @@ static char *sla_show_stations(struct ast_cli_entry *e, int cmd, struct ast_cli_
                            "=== ==> HoldAccess:  %s\n"
                            "=== ==> Trunks ...\n",
                            station->name, station->device,
-                           S_OR(station->autocontext, "(none)"), 
+                           S_OR(station->autocontext, "(none)"),
                            ring_timeout, ring_delay,
                            sla_hold_str(station->hold_access));
-               AST_RWLIST_RDLOCK(&sla_trunks);
                AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
                        if (trunk_ref->ring_timeout) {
                                snprintf(ring_timeout, sizeof(ring_timeout),
                                        "%u", trunk_ref->ring_timeout);
-                       } else
+                       } else {
                                strcpy(ring_timeout, "(none)");
+                       }
                        if (trunk_ref->ring_delay) {
                                snprintf(ring_delay, sizeof(ring_delay),
                                        "%u", trunk_ref->ring_delay);
-                       } else
+                       } else {
                                strcpy(ring_delay, "(none)");
-                               ast_cli(a->fd, "===    ==> Trunk Name: %s\n"
-                                   "===       ==> State:       %s\n"
-                                   "===       ==> RingTimeout: %s\n"
-                                   "===       ==> RingDelay:   %s\n",
-                                   trunk_ref->trunk->name,
-                                   trunkstate2str(trunk_ref->state),
-                                   ring_timeout, ring_delay);
-               }
-               AST_RWLIST_UNLOCK(&sla_trunks);
+                       }
+
+                       ast_cli(a->fd, "===    ==> Trunk Name: %s\n"
+                   "===       ==> State:       %s\n"
+                   "===       ==> RingTimeout: %s\n"
+                   "===       ==> RingDelay:   %s\n",
+                   trunk_ref->trunk->name,
+                   trunkstate2str(trunk_ref->state),
+                   ring_timeout, ring_delay);
+               }
                ast_cli(a->fd, "=== ---------------------------------------------------------\n"
                            "===\n");
+
+               ao2_unlock(station);
        }
-       AST_RWLIST_UNLOCK(&sla_stations);
+       ao2_iterator_destroy(&i);
        ast_cli(a->fd, "============================================================\n"
                    "\n");
 
@@ -1787,8 +2288,10 @@ static char *sla_show_stations(struct ast_cli_entry *e, int cmd, struct ast_cli_
 }
 
 static struct ast_cli_entry cli_meetme[] = {
-       AST_CLI_DEFINE(meetme_cmd, "Execute a command on a conference or conferee"),
-       AST_CLI_DEFINE(meetme_show_cmd, "List all or one conference"),
+       AST_CLI_DEFINE(meetme_kick_cmd, "Kick a conference or a user in a conference."),
+       AST_CLI_DEFINE(meetme_show_cmd, "List all conferences or a specific conference."),
+       AST_CLI_DEFINE(meetme_lock_cmd, "Lock or unlock a conference to new users."),
+       AST_CLI_DEFINE(meetme_mute_cmd, "Mute or unmute a conference or a user in a conference."),
        AST_CLI_DEFINE(sla_show_trunks, "Show SLA Trunks"),
        AST_CLI_DEFINE(sla_show_stations, "Show SLA Stations"),
 };
@@ -1806,7 +2309,7 @@ static void conf_flush(int fd, struct ast_channel *chan)
                /* when no frames are available, this will wait
                   for 1 millisecond maximum
                */
-               while (ast_waitfor(chan, 1)) {
+               while (ast_waitfor(chan, 1) > 0) {
                        f = ast_read(chan);
                        if (f)
                                ast_frfree(f);
@@ -1829,9 +2332,10 @@ static int conf_free(struct ast_conference *conf)
 {
        int x;
        struct announce_listitem *item;
-       
+
        AST_LIST_REMOVE(&confs, conf, list);
-       manager_event(EVENT_FLAG_CALL, "MeetmeEnd", "Meetme: %s\r\n", conf->confno);
+
+       meetme_stasis_generate_msg(conf, NULL, NULL, meetme_end_type(), NULL);
 
        if (conf->recording == MEETME_RECORD_ACTIVE) {
                conf->recording = MEETME_RECORD_TERMINATE;
@@ -1858,7 +2362,7 @@ static int conf_free(struct ast_conference *conf)
                ast_cond_signal(&conf->announcelist_addition);
                ast_mutex_unlock(&conf->announcelistlock);
                pthread_join(conf->announcethread, NULL);
-       
+
                while ((item = AST_LIST_REMOVE_HEAD(&conf->announcelist, entry))) {
                        /* If it's a voicemail greeting file we don't want to remove it */
                        if (!item->vmrec){
@@ -1871,10 +2375,8 @@ static int conf_free(struct ast_conference *conf)
 
        if (conf->origframe)
                ast_frfree(conf->origframe);
-       if (conf->lchan)
-               ast_hangup(conf->lchan);
-       if (conf->chan)
-               ast_hangup(conf->chan);
+       ast_hangup(conf->lchan);
+       ast_hangup(conf->chan);
        if (conf->fd >= 0)
                close(conf->fd);
        if (conf->recordingfilename) {
@@ -1908,23 +2410,28 @@ static void conf_queue_dtmf(const struct ast_conference *conf,
                        continue;
                }
                if (ast_write(user->chan, f) < 0)
-                       ast_log(LOG_WARNING, "Error writing frame to channel %s\n", user->chan->name);
+                       ast_log(LOG_WARNING, "Error writing frame to channel %s\n", ast_channel_name(user->chan));
                ao2_ref(user, -1);
        }
        ao2_iterator_destroy(&user_iter);
 }
 
-static void sla_queue_event_full(enum sla_event_type type, 
+static void sla_queue_event_full(enum sla_event_type type,
        struct sla_trunk_ref *trunk_ref, struct sla_station *station, int lock)
 {
        struct sla_event *event;
 
        if (sla.thread == AST_PTHREADT_NULL) {
+               ao2_ref(station, -1);
+               ao2_ref(trunk_ref, -1);
                return;
        }
 
-       if (!(event = ast_calloc(1, sizeof(*event))))
+       if (!(event = ast_calloc(1, sizeof(*event)))) {
+               ao2_ref(station, -1);
+               ao2_ref(trunk_ref, -1);
                return;
+       }
 
        event->type = type;
        event->trunk_ref = trunk_ref;
@@ -1958,6 +2465,7 @@ static void sla_queue_event_conf(enum sla_event_type type, struct ast_channel *c
        struct sla_station *station;
        struct sla_trunk_ref *trunk_ref = NULL;
        char *trunk_name;
+       struct ao2_iterator i;
 
        trunk_name = ast_strdupa(conf->confno);
        strsep(&trunk_name, "_");
@@ -1966,16 +2474,23 @@ static void sla_queue_event_conf(enum sla_event_type type, struct ast_channel *c
                return;
        }
 
-       AST_RWLIST_RDLOCK(&sla_stations);
-       AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
+       i = ao2_iterator_init(sla_stations, 0);
+       while ((station = ao2_iterator_next(&i))) {
+               ao2_lock(station);
                AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
-                       if (trunk_ref->chan == chan && !strcmp(trunk_ref->trunk->name, trunk_name))
+                       if (trunk_ref->chan == chan && !strcmp(trunk_ref->trunk->name, trunk_name)) {
+                               ao2_ref(trunk_ref, 1);
                                break;
+                       }
                }
-               if (trunk_ref)
+               ao2_unlock(station);
+               if (trunk_ref) {
+                       /* station reference given to sla_queue_event_full() */
                        break;
+               }
+               ao2_ref(station, -1);
        }
-       AST_RWLIST_UNLOCK(&sla_stations);
+       ao2_iterator_destroy(&i);
 
        if (!trunk_ref) {
                ast_debug(1, "Trunk not found for event!\n");
@@ -2072,14 +2587,14 @@ static void conf_start_moh(struct ast_channel *chan, const char *musicclass)
        char *original_moh;
 
        ast_channel_lock(chan);
-       original_moh = ast_strdupa(chan->musicclass);
-       ast_string_field_set(chan, musicclass, musicclass);
+       original_moh = ast_strdupa(ast_channel_musicclass(chan));
+       ast_channel_musicclass_set(chan, musicclass);
        ast_channel_unlock(chan);
 
        ast_moh_start(chan, original_moh, NULL);
 
        ast_channel_lock(chan);
-       ast_string_field_set(chan, musicclass, original_moh);
+       ast_channel_musicclass_set(chan, original_moh);
        ast_channel_unlock(chan);
 }
 
@@ -2160,18 +2675,13 @@ static int can_write(struct ast_channel *chan, struct ast_flags64 *confflags)
                return 1;
        }
 
-       return (chan->_state == AST_STATE_UP);
+       return (ast_channel_state(chan) == AST_STATE_UP);
 }
 
 static void send_talking_event(struct ast_channel *chan, struct ast_conference *conf, struct ast_conf_user *user, int talking)
 {
-       ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeTalking",
-             "Channel: %s\r\n"
-             "Uniqueid: %s\r\n"
-             "Meetme: %s\r\n"
-             "Usernum: %d\r\n"
-             "Status: %s\r\n",
-             chan->name, chan->uniqueid, conf->confno, user->user_no, talking ? "on" : "off");
+       RAII_VAR(struct ast_json *, status_blob, status_to_json(talking), ast_json_unref);
+       meetme_stasis_generate_msg(conf, chan, user, meetme_talking_type(), status_blob);
 }
 
 static void set_user_talking(struct ast_channel *chan, struct ast_conference *conf, struct ast_conf_user *user, int talking, int monitor)
@@ -2192,6 +2702,17 @@ static void set_user_talking(struct ast_channel *chan, struct ast_conference *co
        }
 }
 
+static int user_set_hangup_cb(void *obj, void *check_admin_arg, int flags)
+{
+       struct ast_conf_user *user = obj;
+       /* actual pointer contents of check_admin_arg is irrelevant */
+
+       if (!check_admin_arg || (check_admin_arg && !ast_test_flag64(&user->userflags, CONFFLAG_ADMIN))) {
+               user->adminflags |= ADMINFLAG_HANGUP;
+       }
+       return 0;
+}
+
 static int user_set_kickme_cb(void *obj, void *check_admin_arg, int flags)
 {
        struct ast_conf_user *user = obj;
@@ -2225,6 +2746,434 @@ static int user_set_muted_cb(void *obj, void *check_admin_arg, int flags)
        return 0;
 }
 
+enum menu_modes {
+       MENU_DISABLED = 0,
+       MENU_NORMAL,
+       MENU_ADMIN,
+       MENU_ADMIN_EXTENDED,
+};
+
+/*! \internal
+ * \brief Processes menu options for the standard menu (accessible through the 's' option for app_meetme)
+ *
+ * \param menu_mode a pointer to the currently active menu_mode.
+ * \param dtmf a pointer to the dtmf value currently being processed against the menu.
+ * \param conf the active conference for which the user has called the menu from.
+ * \param confflags flags used by conf for various options
+ * \param chan ast_channel belonging to the user who called the menu
+ * \param user which meetme conference user invoked the menu
+ */
+static void meetme_menu_normal(enum menu_modes *menu_mode, int *dtmf, struct ast_conference *conf, struct ast_flags64 *confflags, struct ast_channel *chan, struct ast_conf_user *user)
+{
+       switch (*dtmf) {
+       case '1': /* Un/Mute */
+               *menu_mode = MENU_DISABLED;
+
+               /* user can only toggle the self-muted state */
+               user->adminflags ^= ADMINFLAG_SELFMUTED;
+
+               /* they can't override the admin mute state */
+               if (ast_test_flag64(confflags, CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) {
+                       if (!ast_streamfile(chan, "conf-muted", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               } else {
+                       if (!ast_streamfile(chan, "conf-unmuted", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               }
+               break;
+
+       case '2':
+               *menu_mode = MENU_DISABLED;
+               if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) {
+                       user->adminflags |= ADMINFLAG_T_REQUEST;
+               }
+
+               if (user->adminflags & ADMINFLAG_T_REQUEST) {
+                       if (!ast_streamfile(chan, "beep", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               }
+               break;
+
+       case '4':
+               tweak_listen_volume(user, VOL_DOWN);
+               break;
+       case '5':
+               /* Extend RT conference */
+               if (rt_schedule) {
+                       rt_extend_conf(conf->confno);
+               }
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '6':
+               tweak_listen_volume(user, VOL_UP);
+               break;
+
+       case '7':
+               tweak_talk_volume(user, VOL_DOWN);
+               break;
+
+       case '8':
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '9':
+               tweak_talk_volume(user, VOL_UP);
+               break;
+
+       default:
+               *menu_mode = MENU_DISABLED;
+               if (!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan))) {
+                       ast_waitstream(chan, "");
+               }
+               break;
+       }
+}
+
+/*! \internal
+ * \brief Processes menu options for the adminstrator menu (accessible through the 's' option for app_meetme)
+ *
+ * \param menu_mode a pointer to the currently active menu_mode.
+ * \param dtmf a pointer to the dtmf value currently being processed against the menu.
+ * \param conf the active conference for which the user has called the menu from.
+ * \param confflags flags used by conf for various options
+ * \param chan ast_channel belonging to the user who called the menu
+ * \param user which meetme conference user invoked the menu
+ */
+static void meetme_menu_admin(enum menu_modes *menu_mode, int *dtmf, struct ast_conference *conf, struct ast_flags64 *confflags, struct ast_channel *chan, struct ast_conf_user *user)
+{
+       switch(*dtmf) {
+       case '1': /* Un/Mute */
+               *menu_mode = MENU_DISABLED;
+               /* for admin, change both admin and use flags */
+               if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) {
+                       user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
+               } else {
+                       user->adminflags |= (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
+               }
+
+               if (ast_test_flag64(confflags, CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) {
+                       if (!ast_streamfile(chan, "conf-muted", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               } else {
+                       if (!ast_streamfile(chan, "conf-unmuted", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               }
+               break;
+
+       case '2': /* Un/Lock the Conference */
+               *menu_mode = MENU_DISABLED;
+               if (conf->locked) {
+                       conf->locked = 0;
+                       if (!ast_streamfile(chan, "conf-unlockednow", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               } else {
+                       conf->locked = 1;
+                       if (!ast_streamfile(chan, "conf-lockednow", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               }
+               break;
+
+       case '3': /* Eject last user */
+       {
+               struct ast_conf_user *usr = NULL;
+               int max_no = 0;
+               ao2_callback(conf->usercontainer, OBJ_NODATA, user_max_cmp, &max_no);
+               *menu_mode = MENU_DISABLED;
+               usr = ao2_find(conf->usercontainer, &max_no, 0);
+               if ((ast_channel_name(usr->chan) == ast_channel_name(chan)) || ast_test_flag64(&usr->userflags, CONFFLAG_ADMIN)) {
+                       if (!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               } else {
+                       usr->adminflags |= ADMINFLAG_KICKME;
+               }
+               ao2_ref(usr, -1);
+               ast_stopstream(chan);
+               break;
+       }
+
+       case '4':
+               tweak_listen_volume(user, VOL_DOWN);
+               break;
+
+       case '5':
+               /* Extend RT conference */
+               if (rt_schedule) {
+                       if (!rt_extend_conf(conf->confno)) {
+                               if (!ast_streamfile(chan, "conf-extended", ast_channel_language(chan))) {
+                                       ast_waitstream(chan, "");
+                               }
+                       } else {
+                               if (!ast_streamfile(chan, "conf-nonextended", ast_channel_language(chan))) {
+                                       ast_waitstream(chan, "");
+                               }
+                       }
+                       ast_stopstream(chan);
+               }
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '6':
+               tweak_listen_volume(user, VOL_UP);
+               break;
+
+       case '7':
+               tweak_talk_volume(user, VOL_DOWN);
+               break;
+
+       case '8':
+               if (!ast_streamfile(chan, "conf-adminmenu-menu8", ast_channel_language(chan))) {
+                       /* If the user provides DTMF while playing the sound, we want to drop right into the extended menu function with new DTMF once we get out of here. */
+                       *dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
+                       ast_stopstream(chan);
+               }
+               *menu_mode = MENU_ADMIN_EXTENDED;
+               break;
+
+       case '9':
+               tweak_talk_volume(user, VOL_UP);
+               break;
+       default:
+               *menu_mode = MENU_DISABLED;
+               /* Play an error message! */
+               if (!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan))) {
+                       ast_waitstream(chan, "");
+               }
+               break;
+       }
+
+}
+
+/*! \internal
+ * \brief Processes menu options for the extended administrator menu (accessible through option 8 on the administrator menu)
+ *
+ * \param menu_mode a pointer to the currently active menu_mode.
+ * \param dtmf a pointer to the dtmf value currently being processed against the menu.
+ * \param conf the active conference for which the user has called the menu from.
+ * \param confflags flags used by conf for various options
+ * \param chan ast_channel belonging to the user who called the menu
+ * \param user which meetme conference user invoked the menu
+ * \param recordingtmp character buffer which may hold the name of the conference recording file
+ */
+static void meetme_menu_admin_extended(enum menu_modes *menu_mode, int *dtmf,
+       struct ast_conference *conf, struct ast_flags64 *confflags, struct ast_channel *chan,
+       struct ast_conf_user *user, char *recordingtmp, int recordingtmp_size,
+       struct ast_format_cap *cap_slin)
+{
+       int keepplaying;
+       int playednamerec;
+       int res;
+       struct ao2_iterator user_iter;
+       struct ast_conf_user *usr = NULL;
+
+       switch(*dtmf) {
+       case '1': /* *81 Roll call */
+               keepplaying = 1;
+               playednamerec = 0;
+               if (conf->users == 1) {
+                       if (keepplaying && !ast_streamfile(chan, "conf-onlyperson", ast_channel_language(chan))) {
+                               res = ast_waitstream(chan, AST_DIGIT_ANY);
+                               ast_stopstream(chan);
+                               if (res > 0) {
+                                       keepplaying = 0;
+                               }
+                       }
+               } else if (conf->users == 2) {
+                       if (keepplaying && !ast_streamfile(chan, "conf-onlyone", ast_channel_language(chan))) {
+                               res = ast_waitstream(chan, AST_DIGIT_ANY);
+                               ast_stopstream(chan);
+                               if (res > 0) {
+                                       keepplaying = 0;
+                               }
+                       }
+               } else {
+                       if (keepplaying && !ast_streamfile(chan, "conf-thereare", ast_channel_language(chan))) {
+                               res = ast_waitstream(chan, AST_DIGIT_ANY);
+                               ast_stopstream(chan);
+                               if (res > 0) {
+                                       keepplaying = 0;
+                               }
+                       }
+                       if (keepplaying) {
+                               res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, ast_channel_language(chan), (char *) NULL);
+                               ast_stopstream(chan);
+                               if (res > 0) {
+                                       keepplaying = 0;
+                               }
+                       }
+                       if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", ast_channel_language(chan))) {
+                               res = ast_waitstream(chan, AST_DIGIT_ANY);
+                               ast_stopstream(chan);
+                               if (res > 0) {
+                                       keepplaying = 0;
+                               }
+                       }
+               }
+               user_iter = ao2_iterator_init(conf->usercontainer, 0);
+               while((usr = ao2_iterator_next(&user_iter))) {
+                       if (ast_fileexists(usr->namerecloc, NULL, NULL)) {
+                               if (keepplaying && !ast_streamfile(chan, usr->namerecloc, ast_channel_language(chan))) {
+                                       res = ast_waitstream(chan, AST_DIGIT_ANY);
+                                       ast_stopstream(chan);
+                                       if (res > 0) {
+                                               keepplaying = 0;
+                                       }
+                               }
+                               playednamerec = 1;
+                       }
+                       ao2_ref(usr, -1);
+               }
+               ao2_iterator_destroy(&user_iter);
+               if (keepplaying && playednamerec && !ast_streamfile(chan, "conf-roll-callcomplete", ast_channel_language(chan))) {
+                       res = ast_waitstream(chan, AST_DIGIT_ANY);
+                       ast_stopstream(chan);
+                       if (res > 0) {
+                               keepplaying = 0;
+                       }
+               }
+
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '2': /* *82 Eject all non-admins */
+               if (conf->users == 1) {
+                       if(!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               } else {
+                       ao2_callback(conf->usercontainer, OBJ_NODATA, user_set_kickme_cb, &conf);
+               }
+               ast_stopstream(chan);
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '3': /* *83 (Admin) mute/unmute all non-admins */
+               if(conf->gmuted) {
+                       conf->gmuted = 0;
+                       ao2_callback(conf->usercontainer, OBJ_NODATA, user_set_unmuted_cb, &conf);
+                       if (!ast_streamfile(chan, "conf-now-unmuted", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               } else {
+                       conf->gmuted = 1;
+                       ao2_callback(conf->usercontainer, OBJ_NODATA, user_set_muted_cb, &conf);
+                       if (!ast_streamfile(chan, "conf-now-muted", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               }
+               ast_stopstream(chan);
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '4': /* *84 Record conference */
+               if (conf->recording != MEETME_RECORD_ACTIVE) {
+                       ast_set_flag64(confflags, CONFFLAG_RECORDCONF);
+                       if (!conf->recordingfilename) {
+                               const char *var;
+                               ast_channel_lock(chan);
+                               if ((var = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE"))) {
+                                       conf->recordingfilename = ast_strdup(var);
+                               }
+                               if ((var = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT"))) {
+                                       conf->recordingformat = ast_strdup(var);
+                               }
+                               ast_channel_unlock(chan);
+                               if (!conf->recordingfilename) {
+                                       snprintf(recordingtmp, recordingtmp_size, "meetme-conf-rec-%s-%s", conf->confno, ast_channel_uniqueid(chan));
+                                       conf->recordingfilename = ast_strdup(recordingtmp);
+                               }
+                               if (!conf->recordingformat) {
+                                       conf->recordingformat = ast_strdup("wav");
+                               }
+                               ast_verb(4, "Starting recording of MeetMe Conference %s into file %s.%s.\n",
+                               conf->confno, conf->recordingfilename, conf->recordingformat);
+                       }
+
+                       ast_mutex_lock(&conf->recordthreadlock);
+                       if ((conf->recordthread == AST_PTHREADT_NULL) && ast_test_flag64(confflags, CONFFLAG_RECORDCONF) && ((conf->lchan = ast_request("DAHDI", cap_slin, NULL, chan, "pseudo", NULL)))) {
+                               struct dahdi_confinfo dahdic;
+
+                               ast_set_read_format(conf->lchan, ast_format_slin);
+                               ast_set_write_format(conf->lchan, ast_format_slin);
+                               dahdic.chan = 0;
+                               dahdic.confno = conf->dahdiconf;
+                               dahdic.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
+                               if (ioctl(ast_channel_fd(conf->lchan, 0), DAHDI_SETCONF, &dahdic)) {
+                                       ast_log(LOG_WARNING, "Error starting listen channel\n");
+                                       ast_hangup(conf->lchan);
+                                       conf->lchan = NULL;
+                               } else {
+                                       ast_pthread_create_detached_background(&conf->recordthread, NULL, recordthread, conf);
+                               }
+                       }
+                       ast_mutex_unlock(&conf->recordthreadlock);
+                       if (!ast_streamfile(chan, "conf-now-recording", ast_channel_language(chan))) {
+                               ast_waitstream(chan, "");
+                       }
+               }
+
+               ast_stopstream(chan);
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       case '8': /* *88 Exit the menu and return to the conference... without an error message */
+               ast_stopstream(chan);
+               *menu_mode = MENU_DISABLED;
+               break;
+
+       default:
+               if (!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan))) {
+                       ast_waitstream(chan, "");
+               }
+               ast_stopstream(chan);
+               *menu_mode = MENU_DISABLED;
+               break;
+       }
+}
+
+/*! \internal
+ * \brief Processes menu options for the various menu types (accessible through the 's' option for app_meetme)
+ *
+ * \param menu_mode a pointer to the currently active menu_mode.
+ * \param dtmf a pointer to the dtmf value currently being processed against the menu.
+ * \param conf the active conference for which the user has called the menu from.
+ * \param confflags flags used by conf for various options
+ * \param chan ast_channel belonging to the user who called the menu
+ * \param user which meetme conference user invoked the menu
+ * \param recordingtmp character buffer which may hold the name of the conference recording file
+ */
+static void meetme_menu(enum menu_modes *menu_mode, int *dtmf,
+       struct ast_conference *conf, struct ast_flags64 *confflags, struct ast_channel *chan,
+       struct ast_conf_user *user, char *recordingtmp, int recordingtmp_size,
+       struct ast_format_cap *cap_slin)
+{
+       switch (*menu_mode) {
+       case MENU_DISABLED:
+               break;
+       case MENU_NORMAL:
+               meetme_menu_normal(menu_mode, dtmf, conf, confflags, chan, user);
+               break;
+       case MENU_ADMIN:
+               meetme_menu_admin(menu_mode, dtmf, conf, confflags, chan, user);
+               /* Admin Menu is capable of branching into another menu, in which case it will reset dtmf and change the menu mode. */
+               if (*menu_mode != MENU_ADMIN_EXTENDED || (*dtmf <= 0)) {
+                       break;
+               }
+       case MENU_ADMIN_EXTENDED:
+               meetme_menu_admin_extended(menu_mode, dtmf, conf, confflags, chan, user,
+                       recordingtmp, recordingtmp_size, cap_slin);
+               break;
+       }
+}
+
 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struct ast_flags64 *confflags, char *optargs[])
 {
        struct ast_conf_user *user = NULL;
@@ -2245,8 +3194,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        int currentmarked = 0;
        int ret = -1;
        int x;
-       int menu_active = 0;
-       int menu8_active = 0;
+       enum menu_modes menu_mode = MENU_DISABLED;
        int talkreq_manager = 0;
        int using_pseudo = 0;
        int duration = 20;
@@ -2256,13 +3204,13 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        struct timeval now;
        struct ast_dsp *dsp = NULL;
        struct ast_app *agi_app;
-       char *agifile, *mod_speex;
+       char *agifile;
        const char *agifiledefault = "conf-background.agi", *tmpvar;
        char meetmesecs[30] = "";
        char exitcontext[AST_MAX_CONTEXT] = "";
-       char recordingtmp[AST_MAX_EXTENSION] = "";
+       char recordingtmp[AST_MAX_EXTENSION * 2] = "";
        char members[10] = "";
-       int dtmf, opt_waitmarked_timeout = 0;
+       int dtmf = 0, opt_waitmarked_timeout = 0;
        time_t timeout = 0;
        struct dahdi_bufferinfo bi;
        char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
@@ -2281,13 +3229,12 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        int setusercount = 0;
        int confsilence = 0, totalsilence = 0;
        char *mailbox, *context;
-       struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
-       struct ast_format tmpfmt;
+       struct ast_format_cap *cap_slin = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 
        if (!cap_slin) {
                goto conf_run_cleanup;
        }
-       ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
+       ast_format_cap_append(cap_slin, ast_format_slin, 0);
 
        if (!(user = ao2_alloc(sizeof(*user), NULL))) {
                goto conf_run_cleanup;
@@ -2303,7 +3250,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
        if (ast_test_flag64(confflags, CONFFLAG_DURATION_STOP) && !ast_strlen_zero(optargs[OPT_ARG_DURATION_STOP])) {
                calldurationlimit = atoi(optargs[OPT_ARG_DURATION_STOP]);
-               ast_verb(3, "Setting call duration limit to %d seconds.\n", calldurationlimit);
+               ast_verb(3, "Setting call duration limit to %u seconds.\n", calldurationlimit);
        }
 
        if (ast_test_flag64(confflags, CONFFLAG_DURATION_LIMIT) && !ast_strlen_zero(optargs[OPT_ARG_DURATION_LIMIT])) {
@@ -2382,7 +3329,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                else
                        exitkeys = ast_strdupa("#"); /* Default */
        }
-       
+
        if (ast_test_flag64(confflags, CONFFLAG_RECORDCONF)) {
                if (!conf->recordingfilename) {
                        const char *var;
@@ -2395,7 +3342,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                        }
                        ast_channel_unlock(chan);
                        if (!conf->recordingfilename) {
-                               snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
+                               snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, ast_channel_uniqueid(chan));
                                conf->recordingfilename = ast_strdup(recordingtmp);
                        }
                        if (!conf->recordingformat) {
@@ -2408,13 +3355,13 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
        ast_mutex_lock(&conf->recordthreadlock);
        if ((conf->recordthread == AST_PTHREADT_NULL) && ast_test_flag64(confflags, CONFFLAG_RECORDCONF) &&
-               ((conf->lchan = ast_request("DAHDI", cap_slin, chan, "pseudo", NULL)))) {
-               ast_set_read_format_by_id(conf->lchan, AST_FORMAT_SLINEAR);
-               ast_set_write_format_by_id(conf->lchan, AST_FORMAT_SLINEAR);
+               ((conf->lchan = ast_request("DAHDI", cap_slin, NULL, chan, "pseudo", NULL)))) {
+               ast_set_read_format(conf->lchan, ast_format_slin);
+               ast_set_write_format(conf->lchan, ast_format_slin);
                dahdic.chan = 0;
                dahdic.confno = conf->dahdiconf;
                dahdic.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
-               if (ioctl(conf->lchan->fds[0], DAHDI_SETCONF, &dahdic)) {
+               if (ioctl(ast_channel_fd(conf->lchan, 0), DAHDI_SETCONF, &dahdic)) {
                        ast_log(LOG_WARNING, "Error starting listen channel\n");
                        ast_hangup(conf->lchan);
                        conf->lchan = NULL;
@@ -2434,30 +3381,30 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        ast_mutex_unlock(&conf->announcethreadlock);
 
        time(&user->jointime);
-       
+
        user->timelimit = timelimit;
        user->play_warning = play_warning;
        user->warning_freq = warning_freq;
        user->warning_sound = warning_sound;
-       user->end_sound = end_sound;    
-       
+       user->end_sound = end_sound;
+
        if (calldurationlimit > 0) {
                time(&user->kicktime);
                user->kicktime = user->kicktime + calldurationlimit;
        }
-       
+
        if (ast_tvzero(user->start_time))
                user->start_time = ast_tvnow();
        time_left_ms = user->timelimit;
-       
+
        if (user->timelimit) {
                nexteventts = ast_tvadd(user->start_time, ast_samp2tv(user->timelimit, 1000));
                nexteventts = ast_tvsub(nexteventts, ast_samp2tv(user->play_warning, 1000));
        }
 
        if (conf->locked && (!ast_test_flag64(confflags, CONFFLAG_ADMIN))) {
-               /* Sorry, but this conference is locked! */     
-               if (!ast_streamfile(chan, "conf-locked", chan->language))
+               /* Sorry, but this conference is locked! */
+               if (!ast_streamfile(chan, "conf-locked", ast_channel_language(chan)))
                        ast_waitstream(chan, "");
                goto outrun;
        }
@@ -2466,10 +3413,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
        if (rt_schedule && conf->maxusers) {
                if (conf->users >= conf->maxusers) {
-                       /* Sorry, but this confernce has reached the participant limit! */      
-                       if (!ast_streamfile(chan, "conf-full", chan->language))
-                               ast_waitstream(chan, "");
+                       /* Sorry, but this confernce has reached the participant limit! */
                        ast_mutex_unlock(&conf->playlock);
+                       if (!ast_streamfile(chan, "conf-full", ast_channel_language(chan)))
+                               ast_waitstream(chan, "");
                        goto outrun;
                }
        }
@@ -2530,7 +3477,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
                res = 0;
                if (ast_test_flag64(confflags, CONFFLAG_INTROUSERNOREVIEW) && !ast_fileexists(user->namerecloc, NULL, NULL))
-                       res = ast_play_and_record(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE), 0, NULL);
+                       res = ast_play_and_record(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE), 0, NULL);
                else if (ast_test_flag64(confflags, CONFFLAG_INTROUSER) && !ast_fileexists(user->namerecloc, NULL, NULL))
                        res = ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL);
                if (res == -1)
@@ -2556,7 +3503,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
        /* This device changed state now - if this is the first user */
        if (conf->users == 1)
-               ast_devstate_changed(AST_DEVICE_INUSE, "meetme:%s", conf->confno);
+               ast_devstate_changed(AST_DEVICE_INUSE, (conf->isdynamic ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), "meetme:%s", conf->confno);
 
        ast_mutex_unlock(&conf->playlock);
 
@@ -2567,10 +3514,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                ast_channel_lock(chan);
                if ((tmpvar = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT"))) {
                        ast_copy_string(exitcontext, tmpvar, sizeof(exitcontext));
-               } else if (!ast_strlen_zero(chan->macrocontext)) {
-                       ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
+               } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))) {
+                       ast_copy_string(exitcontext, ast_channel_macrocontext(chan), sizeof(exitcontext));
                } else {
-                       ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
+                       ast_copy_string(exitcontext, ast_channel_context(chan), sizeof(exitcontext));
                }
                ast_channel_unlock(chan);
        }
@@ -2578,26 +3525,25 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        /* Play an arbitrary intro message */
        if (ast_test_flag64(confflags, CONFFLAG_INTROMSG) &&
                        !ast_strlen_zero(optargs[OPT_ARG_INTROMSG])) {
-               if (!ast_streamfile(chan, optargs[OPT_ARG_INTROMSG], chan->language)) {
+               if (!ast_streamfile(chan, optargs[OPT_ARG_INTROMSG], ast_channel_language(chan))) {
                        ast_waitstream(chan, "");
                }
        }
 
        if (!ast_test_flag64(confflags, (CONFFLAG_QUIET | CONFFLAG_NOONLYPERSON))) {
                if (conf->users == 1 && !ast_test_flag64(confflags, CONFFLAG_WAITMARKED))
-                       if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
+                       if (!ast_streamfile(chan, "conf-onlyperson", ast_channel_language(chan)))
                                ast_waitstream(chan, "");
                if (ast_test_flag64(confflags, CONFFLAG_WAITMARKED) && conf->markedusers == 0)
-                       if (!ast_streamfile(chan, "conf-waitforleader", chan->language))
+                       if (!ast_streamfile(chan, "conf-waitforleader", ast_channel_language(chan)))
                                ast_waitstream(chan, "");
        }
 
-       if (!ast_test_flag64(confflags, CONFFLAG_QUIET) && ast_test_flag64(confflags, CONFFLAG_ANNOUNCEUSERCOUNT) &&
-               conf->users > 1) {
+       if (ast_test_flag64(confflags, CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
                int keepplaying = 1;
 
-               if (conf->users == 2) { 
-                       if (!ast_streamfile(chan, "conf-onlyone", chan->language)) {
+               if (conf->users == 2) {
+                       if (!ast_streamfile(chan, "conf-onlyone", ast_channel_language(chan))) {
                                res = ast_waitstream(chan, AST_DIGIT_ANY);
                                ast_stopstream(chan);
                                if (res > 0)
@@ -2605,8 +3551,8 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                else if (res == -1)
                                        goto outrun;
                        }
-               } else { 
-                       if (!ast_streamfile(chan, "conf-thereare", chan->language)) {
+               } else {
+                       if (!ast_streamfile(chan, "conf-thereare", ast_channel_language(chan))) {
                                res = ast_waitstream(chan, AST_DIGIT_ANY);
                                ast_stopstream(chan);
                                if (res > 0)
@@ -2615,18 +3561,18 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                        goto outrun;
                        }
                        if (keepplaying) {
-                               res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
+                               res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, ast_channel_language(chan), (char *) NULL);
                                if (res > 0)
                                        keepplaying = 0;
                                else if (res == -1)
                                        goto outrun;
                        }
-                       if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
+                       if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", ast_channel_language(chan))) {
                                res = ast_waitstream(chan, AST_DIGIT_ANY);
                                ast_stopstream(chan);
                                if (res > 0)
                                        keepplaying = 0;
-                               else if (res == -1) 
+                               else if (res == -1)
                                        goto outrun;
                        }
                }
@@ -2637,27 +3583,26 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                ast_indicate(chan, -1);
        }
 
-       if (ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR) < 0) {
-               ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
+       if (ast_set_write_format(chan, ast_format_slin) < 0) {
+               ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", ast_channel_name(chan));
                goto outrun;
        }
 
-       if (ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR) < 0) {
-               ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
+       if (ast_set_read_format(chan, ast_format_slin) < 0) {
+               ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", ast_channel_name(chan));
                goto outrun;
        }
 
        /* Reduce background noise from each participant */
-       if ((mod_speex = ast_module_helper("", "codec_speex", 0, 0, 0, 0))) {
-               ast_free(mod_speex);
+       if (!ast_test_flag64(confflags, CONFFLAG_DONT_DENOISE)) {
                ast_func_write(chan, "DENOISE(rx)", "on");
        }
 
-       retrydahdi = (strcasecmp(chan->tech->type, "DAHDI") || (chan->audiohooks || chan->monitor) ? 1 : 0);
+       retrydahdi = (strcasecmp(ast_channel_tech(chan)->type, "DAHDI") || (ast_channel_audiohooks(chan) || ast_channel_monitor(chan)) ? 1 : 0);
        user->dahdichannel = !retrydahdi;
 
  dahdiretry:
-       origfd = chan->fds[0];
+       origfd = ast_channel_fd(chan, 0);
        if (retrydahdi) {
                /* open pseudo in non-blocking mode */
                fd = open("/dev/dahdi/pseudo", O_RDWR | O_NONBLOCK);
@@ -2686,7 +3631,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                nfds = 1;
        } else {
                /* XXX Make sure we're not running on a pseudo channel XXX */
-               fd = chan->fds[0];
+               fd = ast_channel_fd(chan, 0);
                nfds = 0;
        }
        memset(&dahdic, 0, sizeof(dahdic));
@@ -2717,7 +3662,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                if (!(item = ao2_alloc(sizeof(*item), NULL)))
                        goto outrun;
                ast_copy_string(item->namerecloc, user->namerecloc, sizeof(item->namerecloc));
-               ast_copy_string(item->language, chan->language, sizeof(item->language));
+               ast_copy_string(item->language, ast_channel_language(chan), sizeof(item->language));
                item->confchan = conf->chan;
                item->confusers = conf->users;
                if (ast_test_flag64(confflags, CONFFLAG_INTROUSER_VMREC)){
@@ -2750,25 +3695,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                close(fd);
                goto outrun;
        }
-       ast_debug(1, "Placed channel %s in DAHDI conf %d\n", chan->name, conf->dahdiconf);
+       ast_debug(1, "Placed channel %s in DAHDI conf %d\n", ast_channel_name(chan), conf->dahdiconf);
 
        if (!sent_event) {
-               ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeJoin",
-                       "Channel: %s\r\n"
-                       "Uniqueid: %s\r\n"
-                       "Meetme: %s\r\n"
-                       "Usernum: %d\r\n"
-                       "CallerIDnum: %s\r\n"
-                       "CallerIDname: %s\r\n"
-                       "ConnectedLineNum: %s\r\n"
-                       "ConnectedLineName: %s\r\n",
-                       chan->name, chan->uniqueid, conf->confno,
-                       user->user_no,
-                       S_COR(user->chan->caller.id.number.valid, user->chan->caller.id.number.str, "<unknown>"),
-                       S_COR(user->chan->caller.id.name.valid, user->chan->caller.id.name.str, "<unknown>"),
-                       S_COR(user->chan->connected.id.number.valid, user->chan->connected.id.number.str, "<unknown>"),
-                       S_COR(user->chan->connected.id.name.valid, user->chan->connected.id.name.str, "<unknown>")
-                       );
+               meetme_stasis_generate_msg(conf, chan, user, meetme_join_type(), NULL);
                sent_event = 1;
        }
 
@@ -2803,7 +3733,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                        agifile = ast_strdupa(agifiledefault);
                }
                ast_channel_unlock(chan);
-               
+
                if (user->dahdichannel) {
                        /*  Set CONFMUTE mode on DAHDI channel to mute DTMF tones */
                        x = 1;
@@ -2823,11 +3753,13 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                        ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
                }
        } else {
+               int lastusers = conf->users;
                if (user->dahdichannel && ast_test_flag64(confflags, CONFFLAG_STARMENU)) {
                        /*  Set CONFMUTE mode on DAHDI channel to mute DTMF tones when the menu is enabled */
                        x = 1;
                        ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
-               }       
+               }
+
                for (;;) {
                        int menu_was_active = 0;
 
@@ -2875,10 +3807,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
                                                if (!announcement_played && conf->endalert) {
                                                        if (now.tv_sec + conf->endalert >= conf->endtime) {
-                                                               if (!ast_streamfile(chan, "conf-will-end-in", chan->language))
+                                                               if (!ast_streamfile(chan, "conf-will-end-in", ast_channel_language(chan)))
                                                                        ast_waitstream(chan, "");
-                                                               ast_say_digits(chan, (conf->endtime - now.tv_sec) / 60, "", chan->language);
-                                                               if (!ast_streamfile(chan, "minutes", chan->language))
+                                                               ast_say_digits(chan, (conf->endtime - now.tv_sec) / 60, "", ast_channel_language(chan));
+                                                               if (!ast_streamfile(chan, "minutes", ast_channel_language(chan)))
                                                                        ast_waitstream(chan, "");
                                                                if (musiconhold) {
                                                                        conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
@@ -2906,11 +3838,11 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                }
                                break;
                        }
-  
+
                        to = -1;
                        if (user->timelimit) {
                                int minutes = 0, seconds = 0, remain = 0;
+
                                to = ast_tvdiff_ms(nexteventts, now);
                                if (to < 0) {
                                        to = 0;
@@ -2919,10 +3851,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                if (time_left_ms < to) {
                                        to = time_left_ms;
                                }
-       
+
                                if (time_left_ms <= 0) {
-                                       if (user->end_sound) {                                          
-                                               res = ast_streamfile(chan, user->end_sound, chan->language);
+                                       if (user->end_sound) {
+                                               res = ast_streamfile(chan, user->end_sound, ast_channel_language(chan));
                                                res = ast_waitstream(chan, "");
                                        }
                                        if (ast_test_flag64(confflags, CONFFLAG_KICK_CONTINUE)) {
@@ -2932,10 +3864,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                        }
                                        break;
                                }
-                               
+
                                if (!to) {
-                                       if (time_left_ms >= 5000) {                                             
-                                               
+                                       if (time_left_ms >= 5000) {
+
                                                remain = (time_left_ms + 500) / 1000;
                                                if (remain / 60 >= 1) {
                                                        minutes = remain / 60;
@@ -2943,25 +3875,25 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                } else {
                                                        seconds = remain;
                                                }
-                                               
+
                                                /* force the time left to round up if appropriate */
                                                if (user->warning_sound && user->play_warning) {
                                                        if (!strcmp(user->warning_sound, "timeleft")) {
-                                                               
-                                                               res = ast_streamfile(chan, "vm-youhave", chan->language);
+
+                                                               res = ast_streamfile(chan, "vm-youhave", ast_channel_language(chan));
                                                                res = ast_waitstream(chan, "");
                                                                if (minutes) {
-                                                                       res = ast_say_number(chan, minutes, AST_DIGIT_ANY, chan->language, (char *) NULL);
-                                                                       res = ast_streamfile(chan, "queue-minutes", chan->language);
+                                                                       res = ast_say_number(chan, minutes, AST_DIGIT_ANY, ast_channel_language(chan), (char *) NULL);
+                                                                       res = ast_streamfile(chan, "queue-minutes", ast_channel_language(chan));
                                                                        res = ast_waitstream(chan, "");
                                                                }
                                                                if (seconds) {
-                                                                       res = ast_say_number(chan, seconds, AST_DIGIT_ANY, chan->language, (char *) NULL);
-                                                                       res = ast_streamfile(chan, "queue-seconds", chan->language);
+                                                                       res = ast_say_number(chan, seconds, AST_DIGIT_ANY, ast_channel_language(chan), (char *) NULL);
+                                                                       res = ast_streamfile(chan, "queue-seconds", ast_channel_language(chan));
                                                                        res = ast_waitstream(chan, "");
                                                                }
                                                        } else {
-                                                               res = ast_streamfile(chan, user->warning_sound, chan->language);
+                                                               res = ast_streamfile(chan, user->warning_sound, ast_channel_language(chan));
                                                                res = ast_waitstream(chan, "");
                                                        }
                                                        if (musiconhold) {
@@ -2990,11 +3922,11 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                        /* if we have just exited from the menu, and the user had a channel-driver
                           volume adjustment, restore it
                        */
-                       if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual) {
+                       if (!menu_mode && menu_was_active && user->listen.desired && !user->listen.actual) {
                                set_talk_volume(user, user->listen.desired);
                        }
 
-                       menu_was_active = menu_active;
+                       menu_was_active = menu_mode;
 
                        currentmarked = conf->markedusers;
                        if (!ast_test_flag64(confflags, CONFFLAG_QUIET) &&
@@ -3002,19 +3934,19 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                            ast_test_flag64(confflags, CONFFLAG_WAITMARKED) &&
                            lastmarked == 0) {
                                if (currentmarked == 1 && conf->users > 1) {
-                                       ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
+                                       ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, ast_channel_language(chan), (char *) NULL);
                                        if (conf->users - 1 == 1) {
-                                               if (!ast_streamfile(chan, "conf-userwilljoin", chan->language)) {
+                                               if (!ast_streamfile(chan, "conf-userwilljoin", ast_channel_language(chan))) {
                                                        ast_waitstream(chan, "");
                                                }
                                        } else {
-                                               if (!ast_streamfile(chan, "conf-userswilljoin", chan->language)) {
+                                               if (!ast_streamfile(chan, "conf-userswilljoin", ast_channel_language(chan))) {
                                                        ast_waitstream(chan, "");
                                                }
                                        }
                                }
                                if (conf->users == 1 && !ast_test_flag64(confflags, CONFFLAG_MARKEDUSER)) {
-                                       if (!ast_streamfile(chan, "conf-onlyperson", chan->language)) {
+                                       if (!ast_streamfile(chan, "conf-onlyperson", ast_channel_language(chan))) {
                                                ast_waitstream(chan, "");
                                        }
                                }
@@ -3027,7 +3959,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                if (currentmarked == 0) {
                                        if (lastmarked != 0) {
                                                if (!ast_test_flag64(confflags, CONFFLAG_QUIET)) {
-                                                       if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language)) {
+                                                       if (!ast_streamfile(chan, "conf-leaderhasleft", ast_channel_language(chan))) {
                                                                ast_waitstream(chan, "");
                                                        }
                                                }
@@ -3068,9 +4000,9 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                ast_moh_stop(chan);
                                                musiconhold = 0;
                                        }
-                                       if (!ast_test_flag64(confflags, CONFFLAG_QUIET) && 
+                                       if (!ast_test_flag64(confflags, CONFFLAG_QUIET) &&
                                                !ast_test_flag64(confflags, CONFFLAG_MARKEDUSER)) {
-                                               if (!ast_streamfile(chan, "conf-placeintoconf", chan->language)) {
+                                               if (!ast_streamfile(chan, "conf-placeintoconf", ast_channel_language(chan))) {
                                                        ast_waitstream(chan, "");
                                                }
                                                conf_play(chan, conf, ENTER);
@@ -3084,7 +4016,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                        if (!musiconhold) {
                                                conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
                                                musiconhold = 1;
-                                       } 
+                                       }
                                } else {
                                        if (musiconhold) {
                                                ast_moh_stop(chan);
@@ -3092,7 +4024,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                        }
                                }
                        }
-                       
+
                        /* Leave if the last marked user left */
                        if (currentmarked == 0 && lastmarked != 0 && ast_test_flag64(confflags, CONFFLAG_MARKEDEXIT)) {
                                if (ast_test_flag64(confflags, CONFFLAG_KICK_CONTINUE)) {
@@ -3102,11 +4034,20 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                }
                                break;
                        }
-       
+
+                       /* Throw a TestEvent if a user exit did not cause this user to leave the conference */
+                       if (conf->users != lastusers) {
+                               if (conf->users < lastusers) {
+                                       ast_test_suite_event_notify("NOEXIT", "Message: CONFFLAG_MARKEDEXIT\r\nLastUsers: %d\r\nUsers: %d", lastusers, conf->users);
+                               }
+                               lastusers = conf->users;
+                       }
+
                        /* Check if my modes have changed */
 
                        /* If I should be muted but am still talker, mute me */
                        if ((user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && (dahdic.confmode & DAHDI_CONF_TALKER)) {
+                               RAII_VAR(struct ast_json *, status_blob, status_to_json(1), ast_json_unref);
                                dahdic.confmode ^= DAHDI_CONF_TALKER;
                                if (ioctl(fd, DAHDI_SETCONF, &dahdic)) {
                                        ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
@@ -3118,84 +4059,71 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                if (ast_test_flag64(confflags,  (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER))) {
                                        set_user_talking(chan, conf, user, -1, ast_test_flag64(confflags, CONFFLAG_MONITORTALKER));
                                }
-
-                               ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeMute",
-                                               "Channel: %s\r\n"
-                                               "Uniqueid: %s\r\n"
-                                               "Meetme: %s\r\n"
-                                               "Usernum: %i\r\n"
-                                               "Status: on\r\n",
-                                               chan->name, chan->uniqueid, conf->confno, user->user_no);
+                               meetme_stasis_generate_msg(conf, chan, user, meetme_mute_type(), status_blob);
                        }
 
                        /* If I should be un-muted but am not talker, un-mute me */
                        if (!(user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && !ast_test_flag64(confflags, CONFFLAG_MONITOR) && !(dahdic.confmode & DAHDI_CONF_TALKER)) {
+                               RAII_VAR(struct ast_json *, status_blob, status_to_json(0), ast_json_unref);
                                dahdic.confmode |= DAHDI_CONF_TALKER;
                                if (ioctl(fd, DAHDI_SETCONF, &dahdic)) {
                                        ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
                                        ret = -1;
                                        break;
                                }
-
-                               ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeMute",
-                                               "Channel: %s\r\n"
-                                               "Uniqueid: %s\r\n"
-                                               "Meetme: %s\r\n"
-                                               "Usernum: %i\r\n"
-                                               "Status: off\r\n",
-                                               chan->name, chan->uniqueid, conf->confno, user->user_no);
+                               meetme_stasis_generate_msg(conf, chan, user, meetme_mute_type(), status_blob);
                        }
-                       
-                       if ((user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && 
+
+                       if ((user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) &&
                                (user->adminflags & ADMINFLAG_T_REQUEST) && !(talkreq_manager)) {
-                               talkreq_manager = 1;
 
-                               ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeTalkRequest",
-                                             "Channel: %s\r\n"
-                                                             "Uniqueid: %s\r\n"
-                                                             "Meetme: %s\r\n"
-                                                             "Usernum: %i\r\n"
-                                                             "Status: on\r\n",
-                                                             chan->name, chan->uniqueid, conf->confno, user->user_no);
+                               RAII_VAR(struct ast_json *, status_blob, status_to_json(1), ast_json_unref);
+                               talkreq_manager = 1;
+                               meetme_stasis_generate_msg(conf, chan, user, meetme_talk_request_type(), status_blob);
                        }
 
-                       
-                       if (!(user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && 
+                       if (!(user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) &&
                                !(user->adminflags & ADMINFLAG_T_REQUEST) && (talkreq_manager)) {
+                               RAII_VAR(struct ast_json *, status_blob, status_to_json(0), ast_json_unref);
                                talkreq_manager = 0;
-                               ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeTalkRequest",
-                                             "Channel: %s\r\n"
-                                                             "Uniqueid: %s\r\n"
-                                                             "Meetme: %s\r\n"
-                                                             "Usernum: %i\r\n"
-                                                             "Status: off\r\n",
-                                                            chan->name, chan->uniqueid, conf->confno, user->user_no);
+                               meetme_stasis_generate_msg(conf, chan, user, meetme_talk_request_type(), status_blob);
                        }
-                       
+
+                       /* If user have been hung up, exit the conference */
+                       if (user->adminflags & ADMINFLAG_HANGUP) {
+                               ret = 0;
+                               break;
+                       }
+
                        /* If I have been kicked, exit the conference */
                        if (user->adminflags & ADMINFLAG_KICKME) {
                                /* You have been kicked. */
-                               if (!ast_test_flag64(confflags, CONFFLAG_QUIET) && 
-                                       !ast_streamfile(chan, "conf-kicked", chan->language)) {
+                               if (!ast_test_flag64(confflags, CONFFLAG_QUIET) &&
+                                       !ast_streamfile(chan, "conf-kicked", ast_channel_language(chan))) {
                                        ast_waitstream(chan, "");
                                }
                                ret = 0;
                                break;
                        }
 
+                       /* Perform a hangup check here since ast_waitfor_nandfds will not always be able to get a channel after a hangup has occurred */
+                       if (ast_check_hangup(chan)) {
+                               break;
+                       }
+
                        c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
 
                        if (c) {
                                char dtmfstr[2] = "";
 
-                               if (c->fds[0] != origfd || (user->dahdichannel && (c->audiohooks || c->monitor))) {
+                               if (ast_channel_fd(c, 0) != origfd || (user->dahdichannel && (ast_channel_audiohooks(c) || ast_channel_monitor(c)))) {
                                        if (using_pseudo) {
                                                /* Kill old pseudo */
                                                close(fd);
                                                using_pseudo = 0;
                                        }
                                        ast_debug(1, "Ooh, something swapped out under us, starting over\n");
-                                       retrydahdi = (strcasecmp(c->tech->type, "DAHDI") || (c->audiohooks || c->monitor) ? 1 : 0);
+                                       retrydahdi = (strcasecmp(ast_channel_tech(c)->type, "DAHDI") || (ast_channel_audiohooks(c) || ast_channel_monitor(c)) ? 1 : 0);
                                        user->dahdichannel = !retrydahdi;
                                        goto dahdiretry;
                                }
@@ -3212,7 +4140,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                        dtmfstr[1] = '\0';
                                }
 
-                               if ((f->frametype == AST_FRAME_VOICE) && (f->subclass.format.id == AST_FORMAT_SLINEAR)) {
+                               if ((f->frametype == AST_FRAME_VOICE) && (ast_format_cmp(f->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL)) {
                                        if (user->talk.actual) {
                                                ast_frame_adjust_volume(f, user->talk.actual);
                                        }
@@ -3248,10 +4176,11 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                        careful_write(fd, f->data.ptr, f->datalen, 0);
                                                }
                                        }
-                               } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass.integer == '*') && ast_test_flag64(confflags, CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
+                               } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass.integer == '*') && ast_test_flag64(confflags, CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_mode)) {
                                        if (ast_test_flag64(confflags, CONFFLAG_PASS_DTMF)) {
                                                conf_queue_dtmf(conf, user, f);
                                        }
+                                       /* Take out of conference */
                                        if (ioctl(fd, DAHDI_SETCONF, &dahdic_empty)) {
                                                ast_log(LOG_WARNING, "Error setting conference\n");
                                                close(fd);
@@ -3261,349 +4190,43 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
 
                                        /* if we are entering the menu, and the user has a channel-driver
                                           volume adjustment, clear it
-                                       */
-                                       if (!menu_active && user->talk.desired && !user->talk.actual) {
-                                               set_talk_volume(user, 0);
-                                       }
-
-                                       if (musiconhold) {
-                                               ast_moh_stop(chan);
-                                       }
-                                       if (menu8_active) {
-                                               /* *8 Submenu */
-                                               dtmf = f->subclass.integer;
-                                               if (dtmf) {
-                                                       int keepplaying;
-                                                       int playednamerec;
-                                                       struct ao2_iterator user_iter;
-                                                       struct ast_conf_user *usr = NULL;
-                                                       switch(dtmf) {
-                                                       case '1': /* *81 Roll call */
-                                                               keepplaying = 1;
-                                                               playednamerec = 0;
-                                                               if (conf->users == 1) {
-                                                                       if (keepplaying && !ast_streamfile(chan, "conf-onlyperson", chan->language)) {
-                                                                               res = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                                               ast_stopstream(chan);
-                                                                               if (res > 0)
-                                                                                       keepplaying = 0;
-                                                                       }
-                                                               } else if (conf->users == 2) {
-                                                                       if (keepplaying && !ast_streamfile(chan, "conf-onlyone", chan->language)) {
-                                                                               res = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                                               ast_stopstream(chan);
-                                                                               if (res > 0)
-                                                                                       keepplaying = 0;
-                                                                       }
-                                                               } else {
-                                                                       if (keepplaying && !ast_streamfile(chan, "conf-thereare", chan->language)) {
-                                                                               res = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                                               ast_stopstream(chan);
-                                                                               if (res > 0)
-                                                                                       keepplaying = 0;
-                                                                       }
-                                                                       if (keepplaying) {
-                                                                               res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
-                                                                               if (res > 0)
-                                                                                       keepplaying = 0;
-                                                                       }
-                                                                       if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
-                                                                               res = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                                               ast_stopstream(chan);
-                                                                               if (res > 0)
-                                                                                       keepplaying = 0;
-                                                                       }
-                                                               }
-                                                               user_iter = ao2_iterator_init(conf->usercontainer, 0);
-                                                               while((usr = ao2_iterator_next(&user_iter))) {
-                                                                       if (ast_fileexists(usr->namerecloc, NULL, NULL)) {
-                                                                               if (keepplaying && !ast_streamfile(chan, usr->namerecloc, chan->language)) {
-                                                                                       res = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                                                       ast_stopstream(chan);
-                                                                                       if (res > 0)
-                                                                                               keepplaying = 0;
-                                                                               }
-                                                                               playednamerec = 1;
-                                                                       }
-                                                                       ao2_ref(usr, -1);
-                                                               }
-                                                               ao2_iterator_destroy(&user_iter);
-                                                               if (keepplaying && playednamerec && !ast_streamfile(chan, "conf-roll-callcomplete", chan->language)) {
-                                                                       res = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                                       ast_stopstream(chan);
-                                                                       if (res > 0)
-                                                                               keepplaying = 0;
-                                                               }
-                                                               break;
-                                                       case '2': /* *82 Eject all non-admins */
-                                                               if (conf->users == 1) {
-                                                                       if(!ast_streamfile(chan, "conf-errormenu", chan->language))
-                                                                               ast_waitstream(chan, "");
-                                                               } else {
-                                                                       ao2_callback(conf->usercontainer, OBJ_NODATA, user_set_kickme_cb, &conf);
-                                                               }
-                                                               ast_stopstream(chan);
-                                                               break;
-                                                       case '3': /* *83 (Admin) mute/unmute all non-admins */
-                                                               if(conf->gmuted) {
-                                                                       conf->gmuted = 0;
-                                                                       ao2_callback(conf->usercontainer, OBJ_NODATA, user_set_unmuted_cb, &conf);
-                                                                       if (!ast_streamfile(chan, "conf-now-unmuted", chan->language))
-                                                                               ast_waitstream(chan, "");
-                                                               } else {
-                                                                       conf->gmuted = 1;
-                                                                       ao2_callback(conf->usercontainer, OBJ_NODATA, user_set_muted_cb, &conf);
-                                                                       if (!ast_streamfile(chan, "conf-now-muted", chan->language))
-                                                                               ast_waitstream(chan, "");
-                                                               }
-                                                               ast_stopstream(chan);
-                                                               break;
-                                                       case '4': /* *84 Record conference */
-                                                               if (conf->recording != MEETME_RECORD_ACTIVE) {
-                                                                       ast_set_flag64(confflags, CONFFLAG_RECORDCONF);
-
-                                                                       if (!conf->recordingfilename) {
-                                                                               const char *var;
-                                                                               ast_channel_lock(chan);
-                                                                               if ((var = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE"))) {
-                                                                                       conf->recordingfilename = ast_strdup(var);
-                                                                               }
-                                                                               if ((var = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT"))) {
-                                                                                       conf->recordingformat = ast_strdup(var);
-                                                                               }
-                                                                               ast_channel_unlock(chan);
-                                                                               if (!conf->recordingfilename) {
-                                                                                       snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
-                                                                                       conf->recordingfilename = ast_strdup(recordingtmp);
-                                                                               }
-                                                                               if (!conf->recordingformat) {
-                                                                                       conf->recordingformat = ast_strdup("wav");
-                                                                               }
-                                                                               ast_verb(4, "Starting recording of MeetMe Conference %s into file %s.%s.\n",
-                                                                                       conf->confno, conf->recordingfilename, conf->recordingformat);
-                                                                       }
-
-                                                                       ast_mutex_lock(&conf->recordthreadlock);
-                                                                       if ((conf->recordthread == AST_PTHREADT_NULL) && ast_test_flag64(confflags, CONFFLAG_RECORDCONF) && ((conf->lchan = ast_request("DAHDI", cap_slin, chan, "pseudo", NULL)))) {
-                                                                               ast_set_read_format_by_id(conf->lchan, AST_FORMAT_SLINEAR);
-                                                                               ast_set_write_format_by_id(conf->lchan, AST_FORMAT_SLINEAR);
-                                                                               dahdic.chan = 0;
-                                                                               dahdic.confno = conf->dahdiconf;
-                                                                               dahdic.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
-                                                                               if (ioctl(conf->lchan->fds[0], DAHDI_SETCONF, &dahdic)) {
-                                                                                       ast_log(LOG_WARNING, "Error starting listen channel\n");
-                                                                                       ast_hangup(conf->lchan);
-                                                                                       conf->lchan = NULL;
-                                                                               } else {
-                                                                                       ast_pthread_create_detached_background(&conf->recordthread, NULL, recordthread, conf);
-                                                                               }
-                                                                       }
-                                                                       ast_mutex_unlock(&conf->recordthreadlock);
-
-                                                                       if (!ast_streamfile(chan, "conf-now-recording", chan->language))
-                                                                               ast_waitstream(chan, "");
-
-                                                               }
-
-                                                               ast_stopstream(chan);
-                                                               break;
-                                                       default:
-                                                               if (!ast_streamfile(chan, "conf-errormenu", chan->language))
-                                                                       ast_waitstream(chan, "");
-                                                               ast_stopstream(chan);
-                                                               break;
-                                                       }
-                                               }
+                                       */
+                                       if (!menu_mode && user->talk.desired && !user->talk.actual) {
+                                               set_talk_volume(user, 0);
+                                       }
 
-                                               menu8_active = 0;
-                                               menu_active = 0;
-                                       } else if (ast_test_flag64(confflags, CONFFLAG_ADMIN)) {
-                                               /* Admin menu */
-                                               if (!menu_active) {
-                                                       menu_active = 1;
-                                                       /* Record this sound! */
-                                                       if (!ast_streamfile(chan, "conf-adminmenu-162", chan->language)) {
-                                                               dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                               ast_stopstream(chan);
-                                                       } else {
-                                                               dtmf = 0;
-                                                       }
+                                       if (musiconhold) {
+                                               ast_moh_stop(chan);
+                                       } else if (!menu_mode) {
+                                               char *menu_to_play;
+                                               if (ast_test_flag64(confflags, CONFFLAG_ADMIN)) {
+                                                       menu_mode = MENU_ADMIN;
+                                                       menu_to_play = "conf-adminmenu-18";
                                                } else {
-                                                       dtmf = f->subclass.integer;
+                                                       menu_mode = MENU_NORMAL;
+                                                       menu_to_play = "conf-usermenu-162";
                                                }
-                                               if (dtmf) {
-                                                       switch(dtmf) {
-                                                       case '1': /* Un/Mute */
-                                                               menu_active = 0;
-
-                                                               /* for admin, change both admin and use flags */
-                                                               if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) {
-                                                                       user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
-                                                               } else {
-                                                                       user->adminflags |= (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
-                                                               }
 
-                                                               if (ast_test_flag64(confflags, CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) {
-                                                                       if (!ast_streamfile(chan, "conf-muted", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               } else {
-                                                                       if (!ast_streamfile(chan, "conf-unmuted", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               }
-                                                               break;
-                                                       case '2': /* Un/Lock the Conference */
-                                                               menu_active = 0;
-                                                               if (conf->locked) {
-                                                                       conf->locked = 0;
-                                                                       if (!ast_streamfile(chan, "conf-unlockednow", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               } else {
-                                                                       conf->locked = 1;
-                                                                       if (!ast_streamfile(chan, "conf-lockednow", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               }
-                                                               break;
-                                                       case '3': /* Eject last user */
-                                                       {
-                                                               struct ast_conf_user *usr = NULL;
-                                                               int max_no = 0;
-                                                               ao2_callback(conf->usercontainer, OBJ_NODATA, user_max_cmp, &max_no);
-                                                               menu_active = 0;
-                                                               usr = ao2_find(conf->usercontainer, &max_no, 0);
-                                                               if ((usr->chan->name == chan->name) || ast_test_flag64(&usr->userflags, CONFFLAG_ADMIN)) {
-                                                                       if (!ast_streamfile(chan, "conf-errormenu", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               } else {
-                                                                       usr->adminflags |= ADMINFLAG_KICKME;
-                                                               }
-                                                               ao2_ref(usr, -1);
-                                                               ast_stopstream(chan);
-                                                               break;  
-                                                       }
-                                                       case '4':
-                                                               tweak_listen_volume(user, VOL_DOWN);
-                                                               break;
-                                                       case '5':
-                                                               /* Extend RT conference */
-                                                               if (rt_schedule) {
-                                                                       if (!rt_extend_conf(conf->confno)) {
-                                                                               if (!ast_streamfile(chan, "conf-extended", chan->language)) {
-                                                                                       ast_waitstream(chan, "");
-                                                                               }
-                                                                       } else {
-                                                                               if (!ast_streamfile(chan, "conf-nonextended", chan->language)) {
-                                                                                       ast_waitstream(chan, "");
-                                                                               }
-                                                                       }
-                                                                       ast_stopstream(chan);
-                                                               }
-                                                               menu_active = 0;
-                                                               break;
-                                                       case '6':
-                                                               tweak_listen_volume(user, VOL_UP);
-                                                               break;
-                                                       case '7':
-                                                               tweak_talk_volume(user, VOL_DOWN);
-                                                               break;
-                                                       case '8':
-                                                               menu8_active = 1;
-                                                               break;
-                                                       case '9':
-                                                               tweak_talk_volume(user, VOL_UP);
-                                                               break;
-                                                       default:
-                                                               menu_active = 0;
-                                                               /* Play an error message! */
-                                                               if (!ast_streamfile(chan, "conf-errormenu", chan->language)) {
-                                                                       ast_waitstream(chan, "");
-                                                               }
-                                                               break;
-                                                       }
-                                               }
-                                       } else {
-                                               /* User menu */
-                                               if (!menu_active) {
-                                                       menu_active = 1;
-                                                       if (!ast_streamfile(chan, "conf-usermenu-162", chan->language)) {
-                                                               dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                               ast_stopstream(chan);
-                                                       } else {
-                                                               dtmf = 0;
-                                                       }
+                                               if (!ast_streamfile(chan, menu_to_play, ast_channel_language(chan))) {
+                                                       dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
+                                                       ast_stopstream(chan);
                                                } else {
-                                                       dtmf = f->subclass.integer;
-                                               }
-                                               if (dtmf) {
-                                                       switch (dtmf) {
-                                                       case '1': /* Un/Mute */
-                                                               menu_active = 0;
-
-                                                               /* user can only toggle the self-muted state */
-                                                               user->adminflags ^= ADMINFLAG_SELFMUTED;
-
-                                                               /* they can't override the admin mute state */
-                                                               if (ast_test_flag64(confflags, CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) {
-                                                                       if (!ast_streamfile(chan, "conf-muted", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               } else {
-                                                                       if (!ast_streamfile(chan, "conf-unmuted", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               }
-                                                               break;
-                                                       case '2':
-                                                               menu_active = 0;
-                                                               if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) {
-                                                                       user->adminflags |= ADMINFLAG_T_REQUEST;
-                                                               }
-                                                                       
-                                                               if (user->adminflags & ADMINFLAG_T_REQUEST) {
-                                                                       if (!ast_streamfile(chan, "beep", chan->language)) {
-                                                                               ast_waitstream(chan, "");
-                                                                       }
-                                                               }
-                                                               break;
-                                                       case '4':
-                                                               tweak_listen_volume(user, VOL_DOWN);
-                                                               break;
-                                                       case '5':
-                                                               /* Extend RT conference */
-                                                               if (rt_schedule) {
-                                                                       rt_extend_conf(conf->confno);
-                                                               }
-                                                               menu_active = 0;
-                                                               break;
-                                                       case '6':
-                                                               tweak_listen_volume(user, VOL_UP);
-                                                               break;
-                                                       case '7':
-                                                               tweak_talk_volume(user, VOL_DOWN);
-                                                               break;
-                                                       case '8':
-                                                               menu_active = 0;
-                                                               break;
-                                                       case '9':
-                                                               tweak_talk_volume(user, VOL_UP);
-                                                               break;
-                                                       default:
-                                                               menu_active = 0;
-                                                               if (!ast_streamfile(chan, "conf-errormenu", chan->language)) {
-                                                                       ast_waitstream(chan, "");
-                                                               }
-                                                               break;
-                                                       }
+                                                       dtmf = 0;
                                                }
+                                       } else {
+                                               dtmf = f->subclass.integer;
+                                       }
+
+                                       if (dtmf > 0) {
+                                               meetme_menu(&menu_mode, &dtmf, conf, confflags,
+                                                       chan, user, recordingtmp, sizeof(recordingtmp), cap_slin);
                                        }
-                                       if (musiconhold && !menu_active) {
+
+                                       if (musiconhold && !menu_mode) {
                                                conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
                                        }
 
+                                       /* Put back into conference */
                                        if (ioctl(fd, DAHDI_SETCONF, &dahdic)) {
                                                ast_log(LOG_WARNING, "Error setting conference\n");
                                                close(fd);
@@ -3612,7 +4235,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                        }
 
                                        conf_flush(fd, chan);
-                               /* Since this option could absorb DTMF meant for the previous (menu), we have to check this one last */
+                               /*
+                                * Since options using DTMF could absorb DTMF meant for the
+                                * conference menu, we have to check them after the menu.
+                                */
                                } else if ((f->frametype == AST_FRAME_DTMF) && ast_test_flag64(confflags, CONFFLAG_EXIT_CONTEXT) && ast_exists_extension(chan, exitcontext, dtmfstr, 1, "")) {
                                        if (ast_test_flag64(confflags, CONFFLAG_PASS_DTMF)) {
                                                conf_queue_dtmf(conf, user, f);
@@ -3658,13 +4284,13 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                break;
                                        default:
                                                ast_debug(1,
-                                                       "Got ignored control frame on channel %s, f->frametype=%d,f->subclass=%d\n",
-                                                       chan->name, f->frametype, f->subclass.integer);
+                                                       "Got ignored control frame on channel %s, f->frametype=%u,f->subclass=%d\n",
+                                                       ast_channel_name(chan), f->frametype, f->subclass.integer);
                                        }
                                } else {
                                        ast_debug(1,
-                                               "Got unrecognized frame on channel %s, f->frametype=%d,f->subclass=%d\n",
-                                               chan->name, f->frametype, f->subclass.integer);
+                                               "Got unrecognized frame on channel %s, f->frametype=%u,f->subclass=%d\n",
+                                               ast_channel_name(chan), f->frametype, f->subclass.integer);
                                }
                                ast_frfree(f);
                        } else if (outfd > -1) {
@@ -3672,7 +4298,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                if (res > 0) {
                                        memset(&fr, 0, sizeof(fr));
                                        fr.frametype = AST_FRAME_VOICE;
-                                       ast_format_set(&fr.subclass.format, AST_FORMAT_SLINEAR, 0);
+                                       fr.subclass.format = ast_format_slin;
                                        fr.datalen = res;
                                        fr.samples = res / 2;
                                        fr.data.ptr = buf;
@@ -3684,7 +4310,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                 )) {
                                                int idx;
                                                for (idx = 0; idx < AST_FRAME_BITS; idx++) {
-                                                       if (ast_format_to_old_bitfield(&chan->rawwriteformat) & (1 << idx)) {
+                                                       if (ast_format_compatibility_format2bitfield(ast_channel_rawwriteformat(chan)) & (1 << idx)) {
                                                                break;
                                                        }
                                                }
@@ -3694,16 +4320,15 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                ast_mutex_lock(&conf->listenlock);
                                                if (!conf->transframe[idx]) {
                                                        if (conf->origframe) {
-                                                               if (musiconhold && !ast_dsp_silence(dsp, conf->origframe, &confsilence) && confsilence < MEETME_DELAYDETECTTALK) {
+                                                               if (musiconhold
+                                                                       && !ast_test_flag64(confflags, CONFFLAG_WAITMARKED)
+                                                                       && !ast_dsp_silence(dsp, conf->origframe, &confsilence)
+                                                                       && confsilence < MEETME_DELAYDETECTTALK) {
                                                                        ast_moh_stop(chan);
                                                                        mohtempstopped = 1;
                                                                }
                                                                if (!conf->transpath[idx]) {
-                                                                       struct ast_format src;
-                                                                       struct ast_format dst;
-                                                                       ast_format_set(&src, AST_FORMAT_SLINEAR, 0);
-                                                                       ast_format_from_old_bitfield(&dst, (1 << idx));
-                                                                       conf->transpath[idx] = ast_translator_build_path(&dst, &src);
+                                                                       conf->transpath[idx] = ast_translator_build_path(ast_channel_rawwriteformat(chan), ast_format_slin);
                                                                }
                                                                if (conf->transpath[idx]) {
                                                                        conf->transframe[idx] = ast_translate(conf->transpath[idx], conf->origframe, 0);
@@ -3722,13 +4347,13 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                                */
                                                                for (cur = conf->transframe[idx]; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
                                                                        if (ast_write(chan, cur)) {
-                                                                               ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name);
+                                                                               ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", ast_channel_name(chan));
                                                                                break;
                                                                        }
                                                                }
                                                                if (musiconhold && mohtempstopped && confsilence > MEETME_DELAYDETECTENDTALK) {
                                                                        mohtempstopped = 0;
-                                                                       ast_moh_start(chan, NULL, NULL);
+                                                                       conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
                                                                }
                                                        }
                                                } else {
@@ -3738,7 +4363,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                ast_mutex_unlock(&conf->listenlock);
                                        } else {
 bailoutandtrynormal:
-                                               if (musiconhold && !ast_dsp_silence(dsp, &fr, &confsilence) && confsilence < MEETME_DELAYDETECTTALK) {
+                                               if (musiconhold
+                                                       && !ast_test_flag64(confflags, CONFFLAG_WAITMARKED)
+                                                       && !ast_dsp_silence(dsp, &fr, &confsilence)
+                                                       && confsilence < MEETME_DELAYDETECTTALK) {
                                                        ast_moh_stop(chan);
                                                        mohtempstopped = 1;
                                                }
@@ -3746,11 +4374,11 @@ bailoutandtrynormal:
                                                        ast_frame_adjust_volume(&fr, user->listen.actual);
                                                }
                                                if (can_write(chan, confflags) && ast_write(chan, &fr) < 0) {
-                                                       ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name);
+                                                       ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", ast_channel_name(chan));
                                                }
                                                if (musiconhold && mohtempstopped && confsilence > MEETME_DELAYDETECTENDTALK) {
                                                        mohtempstopped = 0;
-                                                       ast_moh_start(chan, NULL, NULL);
+                                                       conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
                                                }
                                        }
                                } else {
@@ -3764,12 +4392,12 @@ bailoutandtrynormal:
        if (musiconhold) {
                ast_moh_stop(chan);
        }
-       
+
        if (using_pseudo) {
                close(fd);
        } else {
                /* Take out of conference */
-               dahdic.chan = 0;        
+               dahdic.chan = 0;
                dahdic.confno = 0;
                dahdic.confmode = 0;
                if (ioctl(fd, DAHDI_SETCONF, &dahdic)) {
@@ -3789,7 +4417,7 @@ bailoutandtrynormal:
                if (!(item = ao2_alloc(sizeof(*item), NULL)))
                        goto outrun;
                ast_copy_string(item->namerecloc, user->namerecloc, sizeof(item->namerecloc));
-               ast_copy_string(item->language, chan->language, sizeof(item->language));
+               ast_copy_string(item->language, ast_channel_language(chan), sizeof(item->language));
                item->confchan = conf->chan;
                item->confusers = conf->users;
                item->announcetype = CONF_HASLEFT;
@@ -3811,29 +4439,13 @@ bailoutandtrynormal:
        if (dsp) {
                ast_dsp_free(dsp);
        }
-       
+
        if (user->user_no) {
                /* Only cleanup users who really joined! */
                now = ast_tvnow();
 
                if (sent_event) {
-                       ast_manager_event(chan, EVENT_FLAG_CALL, "MeetmeLeave",
-                               "Channel: %s\r\n"
-                               "Uniqueid: %s\r\n"
-                               "Meetme: %s\r\n"
-                               "Usernum: %d\r\n"
-                               "CallerIDNum: %s\r\n"
-                               "CallerIDName: %s\r\n"
-                               "ConnectedLineNum: %s\r\n"
-                               "ConnectedLineName: %s\r\n"
-                               "Duration: %ld\r\n",
-                               chan->name, chan->uniqueid, conf->confno,
-                               user->user_no,
-                               S_COR(user->chan->caller.id.number.valid, user->chan->caller.id.number.str, "<unknown>"),
-                               S_COR(user->chan->caller.id.name.valid, user->chan->caller.id.name.str, "<unknown>"),
-                               S_COR(user->chan->connected.id.number.valid, user->chan->connected.id.number.str, "<unknown>"),
-                               S_COR(user->chan->connected.id.name.valid, user->chan->connected.id.name.str, "<unknown>"),
-                               (long)(now.tv_sec - user->jointime));
+                       meetme_stasis_generate_msg(conf, chan, user, meetme_leave_type(), NULL);
                }
 
                if (setusercount) {
@@ -3852,13 +4464,18 @@ bailoutandtrynormal:
                        }
                }
                /* Remove ourselves from the container */
-               ao2_unlink(conf->usercontainer, user); 
+               ao2_unlink(conf->usercontainer, user);
 
                /* Change any states */
                if (!conf->users) {
-                       ast_devstate_changed(AST_DEVICE_NOT_INUSE, "meetme:%s", conf->confno);
+                       ast_devstate_changed(AST_DEVICE_NOT_INUSE, (conf->isdynamic ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), "meetme:%s", conf->confno);
                }
 
+               /* This flag is meant to kill a conference with only one participant remaining.  */
+               if (conf->users == 1 && ast_test_flag64(confflags, CONFFLAG_KILL_LAST_MAN_STANDING)) {
+                       ao2_callback(conf->usercontainer, 0, user_set_hangup_cb, NULL);
+               }
+
                /* Return the number of seconds the user was in the conf */
                snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
                pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
@@ -3873,7 +4490,7 @@ bailoutandtrynormal:
 
 
 conf_run_cleanup:
-       cap_slin = ast_format_cap_destroy(cap_slin);
+       ao2_cleanup(cap_slin);
 
        return ret;
 }
@@ -3907,7 +4524,7 @@ static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char
                char currenttime[32] = "";
                char eatime[32] = "";
                char bookid[51] = "";
-               char recordingtmp[AST_MAX_EXTENSION] = "";
+               char recordingtmp[AST_MAX_EXTENSION * 2] = "";
                char useropts[OPTIONS_LEN + 1] = ""; /* Used for RealTime conferences */
                char adminopts[OPTIONS_LEN + 1] = "";
                struct ast_tm tm, etm;
@@ -3960,7 +4577,7 @@ static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char
 
                if (rt_schedule && *too_early) {
                        /* Announce that the caller is early and exit */
-                       if (!ast_streamfile(chan, "conf-has-not-started", chan->language)) {
+                       if (!ast_streamfile(chan, "conf-has-not-started", ast_channel_language(chan))) {
                                ast_waitstream(chan, "");
                        }
                        ast_variables_destroy(var);
@@ -4004,8 +4621,12 @@ static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char
                        cnf->useropts = ast_strdup(useropts);
                        cnf->adminopts = ast_strdup(adminopts);
                        cnf->bookid = ast_strdup(bookid);
-                       cnf->recordingfilename = ast_strdup(recordingfilename);
-                       cnf->recordingformat = ast_strdup(recordingformat);
+                       if (!ast_strlen_zero(recordingfilename)) {
+                               cnf->recordingfilename = ast_strdup(recordingfilename);
+                       }
+                       if (!ast_strlen_zero(recordingformat)) {
+                               cnf->recordingformat = ast_strdup(recordingformat);
+                       }
 
                        /* Parse the other options into confflags -- need to do this in two
                         * steps, because the parse_options routine zeroes the buffer. */
@@ -4021,7 +4642,7 @@ static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char
                                        }
                                        ast_channel_unlock(chan);
                                        if (ast_strlen_zero(cnf->recordingfilename)) {
-                                               snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", cnf->confno, chan->uniqueid);
+                                               snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", cnf->confno, ast_channel_uniqueid(chan));
                                                ast_free(cnf->recordingfilename);
                                                cnf->recordingfilename = ast_strdup(recordingtmp);
                                        }
@@ -4046,7 +4667,7 @@ static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char
        if (cnf) {
                if (confflags->flags && !cnf->chan &&
                    !ast_test_flag64(confflags, CONFFLAG_QUIET) &&
-                   ast_test_flag64(confflags, CONFFLAG_INTROUSER | CONFFLAG_INTROUSERNOREVIEW) | CONFFLAG_INTROUSER_VMREC) {
+                   ast_test_flag64(confflags, CONFFLAG_INTROUSER | CONFFLAG_INTROUSERNOREVIEW | CONFFLAG_INTROUSER_VMREC)) {
                        ast_log(LOG_WARNING, "No DAHDI channel available for conference, user introduction disabled (is chan_dahdi loaded?)\n");
                        ast_clear_flag64(confflags, CONFFLAG_INTROUSER | CONFFLAG_INTROUSERNOREVIEW | CONFFLAG_INTROUSER_VMREC);
                }
@@ -4154,7 +4775,7 @@ static struct ast_conference *find_conf(struct ast_channel *chan, char *confno,
                        ast_log(LOG_WARNING, "No DAHDI channel available for conference, user introduction disabled (is chan_dahdi loaded?)\n");
                        ast_clear_flag64(confflags, CONFFLAG_INTROUSER | CONFFLAG_INTROUSERNOREVIEW | CONFFLAG_INTROUSER_VMREC);
                }
-               
+
                if (confflags && !cnf->chan &&
                    ast_test_flag64(confflags, CONFFLAG_RECORDCONF)) {
                        ast_log(LOG_WARNING, "No DAHDI channel available for conference, conference recording disabled (is chan_dahdi loaded?)\n");
@@ -4172,7 +4793,7 @@ static int count_exec(struct ast_channel *chan, const char *data)
        struct ast_conference *conf;
        int count;
        char *localdata;
-       char val[80] = "0"; 
+       char val[80] = "0";
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(confno);
                AST_APP_ARG(varname);
@@ -4182,12 +4803,11 @@ static int count_exec(struct ast_channel *chan, const char *data)
                ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
                return -1;
        }
-       
-       if (!(localdata = ast_strdupa(data)))
-               return -1;
+
+       localdata = ast_strdupa(data);
 
        AST_STANDARD_APP_ARGS(args, localdata);
-       
+
        conf = find_conf(chan, args.confno, 0, 0, NULL, 0, 1, NULL);
 
        if (conf) {
@@ -4202,10 +4822,10 @@ static int count_exec(struct ast_channel *chan, const char *data)
                snprintf(val, sizeof(val), "%d", count);
                pbx_builtin_setvar_helper(chan, args.varname, val);
        } else {
-               if (chan->_state != AST_STATE_UP) {
+               if (ast_channel_state(chan) != AST_STATE_UP) {
                        ast_answer(chan);
                }
-               res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
+               res = ast_say_number(chan, count, "", ast_channel_language(chan), (char *) NULL); /* Needs gender */
        }
 
        return res;
@@ -4239,13 +4859,13 @@ static int conf_exec(struct ast_channel *chan, const char *data)
        } else {
                notdata = data;
        }
-       
-       if (chan->_state != AST_STATE_UP)
+
+       if (ast_channel_state(chan) != AST_STATE_UP)
                ast_answer(chan);
 
        info = ast_strdupa(notdata);
 
-       AST_STANDARD_APP_ARGS(args, info);      
+       AST_STANDARD_APP_ARGS(args, info);
 
        if (args.confno) {
                ast_copy_string(confno, args.confno, sizeof(confno));
@@ -4253,7 +4873,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                        allowretry = 1;
                }
        }
-       
+
        if (args.pin)
                ast_copy_string(the_pin, args.pin, sizeof(the_pin));
 
@@ -4299,6 +4919,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                                                                        }
                                                                }
                                                                AST_LIST_UNLOCK(&confs);
+                                                               cnf = NULL;
                                                                if (!found) {
                                                                        /* At this point, we have a confno_tmp (static conference) that is empty */
                                                                        if ((empty_no_pin && ast_strlen_zero(stringp)) || (!empty_no_pin)) {
@@ -4369,16 +4990,17 @@ static int conf_exec(struct ast_channel *chan, const char *data)
 
                        /* Not found? */
                        if (ast_strlen_zero(confno)) {
-                               res = ast_streamfile(chan, "conf-noempty", chan->language);
+                               res = ast_streamfile(chan, "conf-noempty", ast_channel_language(chan));
+                               ast_test_suite_event_notify("PLAYBACK", "Message: conf-noempty");
                                if (!res)
                                        ast_waitstream(chan, "");
                        } else {
                                if (sscanf(confno, "%30d", &confno_int) == 1) {
                                        if (!ast_test_flag64(&confflags, CONFFLAG_QUIET)) {
-                                               res = ast_streamfile(chan, "conf-enteringno", chan->language);
+                                               res = ast_streamfile(chan, "conf-enteringno", ast_channel_language(chan));
                                                if (!res) {
                                                        ast_waitstream(chan, "");
-                                                       res = ast_say_digits(chan, confno_int, "", chan->language);
+                                                       res = ast_say_digits(chan, confno_int, "", ast_channel_language(chan));
                                                }
                                        }
                                } else {
@@ -4399,12 +5021,12 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                }
                if (!ast_strlen_zero(confno)) {
                        /* Check the validity of the conference */
-                       cnf = find_conf(chan, confno, 1, dynamic, the_pin, 
+                       cnf = find_conf(chan, confno, 1, dynamic, the_pin,
                                sizeof(the_pin), 1, &confflags);
                        if (!cnf) {
                                int too_early = 0;
 
-                               cnf = find_conf_realtime(chan, confno, 1, dynamic, 
+                               cnf = find_conf_realtime(chan, confno, 1, dynamic,
                                        the_pin, sizeof(the_pin), 1, &confflags, &too_early, optargs);
                                if (rt_schedule && too_early)
                                        allowretry = 0;
@@ -4413,19 +5035,33 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                        if (!cnf) {
                                if (allowretry) {
                                        confno[0] = '\0';
-                                       res = ast_streamfile(chan, "conf-invalid", chan->language);
+                                       res = ast_streamfile(chan, "conf-invalid", ast_channel_language(chan));
                                        if (!res)
                                                ast_waitstream(chan, "");
                                        res = -1;
                                }
                        } else {
-                               /* Check to see if the conference requires a pin
-                                * and we ALWAYS prompt or no pin was provided */
-                               if ((!ast_strlen_zero(cnf->pin) ||
+                               /* Conference requires a pin for specified access level */
+                               int req_pin = !ast_strlen_zero(cnf->pin) ||
                                        (!ast_strlen_zero(cnf->pinadmin) &&
-                                               ast_test_flag64(&confflags, CONFFLAG_ADMIN))) &&
-                                   (ast_test_flag64(&confflags, CONFFLAG_ALWAYSPROMPT) ||
-                                               ast_strlen_zero(args.pin))) {
+                                               ast_test_flag64(&confflags, CONFFLAG_ADMIN));
+                               /* The following logic was derived from a
+                                * 4 variable truth table and defines which
+                                * circumstances are not exempt from pin
+                                * checking.
+                                * If this needs to be modified, write the
+                                * truth table back out from the boolean
+                                * expression AB+A'D+C', change the erroneous
+                                * result, and rederive the expression.
+                                * Variables:
+                                *  A: pin provided?
+                                *  B: always prompt?
+                                *  C: dynamic?
+                                *  D: has users? */
+                               int not_exempt = !cnf->isdynamic;
+                               not_exempt = not_exempt || (!ast_strlen_zero(args.pin) && ast_test_flag64(&confflags, CONFFLAG_ALWAYSPROMPT));
+                               not_exempt = not_exempt || (ast_strlen_zero(args.pin) && cnf->users);
+                               if (req_pin && not_exempt) {
                                        char pin[MAX_PIN] = "";
                                        int j;
 
@@ -4436,6 +5072,9 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                                                        res = 0;
                                                } else {
                                                        /* Prompt user for pin if pin is required */
+                                                       ast_test_suite_event_notify("PLAYBACK", "Message: conf-getpin\r\n"
+                                                               "Channel: %s",
+                                                               ast_channel_name(chan));
                                                        res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
                                                }
                                                if (res >= 0) {
@@ -4463,7 +5102,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                                                                break;
                                                        } else {
                                                                /* Pin invalid */
-                                                               if (!ast_streamfile(chan, "conf-invalidpin", chan->language)) {
+                                                               if (!ast_streamfile(chan, "conf-invalidpin", ast_channel_language(chan))) {
                                                                        res = ast_waitstream(chan, AST_DIGIT_ANY);
                                                                        ast_stopstream(chan);
                                                                } else {
@@ -4495,7 +5134,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                                        /* No pin required */
                                        allowretry = 0;
 
-                                       /* For RealTime conferences without a pin 
+                                       /* For RealTime conferences without a pin
                                         * should still support loading options
                                         */
                                        if (!ast_strlen_zero(cnf->useropts)) {
@@ -4514,7 +5153,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
 
        if (cnf)
                dispose_conf(cnf);
-       
+
        return res;
 }
 
@@ -4522,9 +5161,8 @@ static struct ast_conf_user *find_user(struct ast_conference *conf, const char *
 {
        struct ast_conf_user *user = NULL;
        int cid;
-       
-       sscanf(callerident, "%30i", &cid);
-       if (conf && callerident) {
+
+       if (conf && callerident && sscanf(callerident, "%30d", &cid) == 1) {
                user = ao2_find(conf->usercontainer, &cid, 0);
                /* reference decremented later in admin_exec */
                return user;
@@ -4572,14 +5210,14 @@ static int user_chan_cb(void *obj, void *args, int flags)
        struct ast_conf_user *user = obj;
        const char *channel = args;
 
-       if (!strcmp(user->chan->name, channel)) {
+       if (!strcmp(ast_channel_name(user->chan), channel)) {
                return (CMP_MATCH | CMP_STOP);
        }
 
        return 0;
 }
 
-/*! \brief The MeetMeadmin application 
+/*! \brief The MeetMeadmin application
 
   MeetMeAdmin(confno, command, caller) */
 static int admin_exec(struct ast_channel *chan, const char *data) {
@@ -4630,13 +5268,30 @@ static int admin_exec(struct ast_channel *chan, const char *data) {
                        res = -2;
                        goto usernotfound;
                }
+       } else {
+               /* fail for commands that require a user */
+               switch (*args.command) {
+               case 'm': /* Unmute */
+               case 'M': /* Mute */
+               case 't': /* Lower user's talk volume */
+               case 'T': /* Raise user's talk volume */
+               case 'u': /* Lower user's listen volume */
+               case 'U': /* Raise user's listen volume */
+               case 'r': /* Reset user's volume level */
+               case 'k': /* Kick user */
+                       res = -2;
+                       ast_log(LOG_NOTICE, "No user specified!\n");
+                       goto usernotfound;
+               default:
+                       break;
+               }
        }
 
        switch (*args.command) {
-       case 76: /* L: Lock */ 
+       case 76: /* L: Lock */
                cnf->locked = 1;
                break;
-       case 108: /* l: Unlock */ 
+       case 108: /* l: Unlock */
                cnf->locked = 0;
                break;
        case 75: /* K: kick all users */
@@ -4645,30 +5300,37 @@ static int admin_exec(struct ast_channel *chan, const char *data) {
        case 101: /* e: Eject last user*/
        {
                int max_no = 0;
+               RAII_VAR(struct ast_conf_user *, eject_user, NULL, ao2_cleanup);
+
                ao2_callback(cnf->usercontainer, OBJ_NODATA, user_max_cmp, &max_no);
-               user = ao2_find(cnf->usercontainer, &max_no, 0);
-               if (!ast_test_flag64(&user->userflags, CONFFLAG_ADMIN))
-                       user->adminflags |= ADMINFLAG_KICKME;
-               else {
+               eject_user = ao2_find(cnf->usercontainer, &max_no, 0);
+               if (!eject_user) {
+                       res = -1;
+                       ast_log(LOG_NOTICE, "No last user to kick!\n");
+                       break;
+               }
+
+               if (!ast_test_flag64(&eject_user->userflags, CONFFLAG_ADMIN)) {
+                       eject_user->adminflags |= ADMINFLAG_KICKME;
+               } else {
                        res = -1;
                        ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
                }
-               ao2_ref(user, -1);
                break;
        }
-       case 77: /* M: Mute */ 
+       case 77: /* M: Mute */
                user->adminflags |= ADMINFLAG_MUTED;
                break;
        case 78: /* N: Mute all (non-admin) users */
-               ao2_callback(cnf->usercontainer, OBJ_NODATA, user_set_muted_cb, NULL);
-               break;                                  
-       case 109: /* m: Unmute */ 
+               ao2_callback(cnf->usercontainer, OBJ_NODATA, user_set_muted_cb, &cnf);
+               break;
+       case 109: /* m: Unmute */
                user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED | ADMINFLAG_T_REQUEST);
                break;
        case 110: /* n: Unmute all users */
                ao2_callback(cnf->usercontainer, OBJ_NODATA, user_set_unmuted_cb, NULL);
                break;
-       case 107: /* k: Kick user */ 
+       case 107: /* k: Kick user */
                user->adminflags |= ADMINFLAG_KICKME;
                break;
        case 118: /* v: Lower all users listen volume */
@@ -4721,7 +5383,7 @@ usernotfound:
        return 0;
 }
 
-/*! \brief The MeetMeChannelAdmin application 
+/*! \brief The MeetMeChannelAdmin application
        MeetMeChannelAdmin(channel, command) */
 static int channel_admin_exec(struct ast_channel *chan, const char *data) {
        char *params;
@@ -4736,7 +5398,7 @@ static int channel_admin_exec(struct ast_channel *chan, const char *data) {
                ast_log(LOG_WARNING, "MeetMeChannelAdmin requires two arguments!\n");
                return -1;
        }
-       
+
        params = ast_strdupa(data);
        AST_STANDARD_APP_ARGS(args, params);
 
@@ -4756,22 +5418,22 @@ static int channel_admin_exec(struct ast_channel *chan, const char *data) {
                        break;
                }
        }
-       
+
        if (!user) {
                ast_log(LOG_NOTICE, "Specified user (%s) not found\n", args.channel);
                AST_LIST_UNLOCK(&confs);
                return 0;
        }
-       
+
        /* perform the specified action */
        switch (*args.command) {
-               case 77: /* M: Mute */ 
+               case 77: /* M: Mute */
                        user->adminflags |= ADMINFLAG_MUTED;
                        break;
-               case 109: /* m: Unmute */ 
+               case 109: /* m: Unmute */
                        user->adminflags &= ~ADMINFLAG_MUTED;
                        break;
-               case 107: /* k: Kick user */ 
+               case 107: /* k: Kick user */
                        user->adminflags |= ADMINFLAG_KICKME;
                        break;
                default: /* unknown command */
@@ -4780,7 +5442,7 @@ static int channel_admin_exec(struct ast_channel *chan, const char *data) {
        }
        ao2_ref(user, -1);
        AST_LIST_UNLOCK(&confs);
-       
+
        return 0;
 }
 
@@ -4837,7 +5499,7 @@ static int meetmemute(struct mansession *s, const struct message *m, int mute)
 
        AST_LIST_UNLOCK(&confs);
 
-       ast_log(LOG_NOTICE, "Requested to %smute conf %s user %d userchan %s uniqueid %s\n", mute ? "" : "un", conf->confno, user->user_no, user->chan->name, user->chan->uniqueid);
+       ast_log(LOG_NOTICE, "Requested to %smute conf %s user %d userchan %s uniqueid %s\n", mute ? "" : "un", conf->confno, user->user_no, ast_channel_name(user->chan), ast_channel_uniqueid(user->chan));
 
        ao2_ref(user, -1);
        astman_send_ack(s, m, mute ? "User muted" : "User unmuted");
@@ -4904,11 +5566,11 @@ static int action_meetmelist(struct mansession *s, const struct message *m)
                                idText,
                                cnf->confno,
                                user->user_no,
-                               S_COR(user->chan->caller.id.number.valid, user->chan->caller.id.number.str, "<unknown>"),
-                               S_COR(user->chan->caller.id.name.valid, user->chan->caller.id.name.str, "<no name>"),
-                               S_COR(user->chan->connected.id.number.valid, user->chan->connected.id.number.str, "<unknown>"),
-                               S_COR(user->chan->connected.id.name.valid, user->chan->connected.id.name.str, "<no name>"),
-                               user->chan->name,
+                               S_COR(ast_channel_caller(user->chan)->id.number.valid, ast_channel_caller(user->chan)->id.number.str, "<unknown>"),
+                               S_COR(ast_channel_caller(user->chan)->id.name.valid, ast_channel_caller(user->chan)->id.name.str, "<no name>"),
+                               S_COR(ast_channel_connected(user->chan)->id.number.valid, ast_channel_connected(user->chan)->id.number.str, "<unknown>"),
+                               S_COR(ast_channel_connected(user->chan)->id.name.valid, ast_channel_connected(user->chan)->id.name.str, "<no name>"),
+                               ast_channel_name(user->chan),
                                ast_test_flag64(&user->userflags, CONFFLAG_ADMIN) ? "Yes" : "No",
                                ast_test_flag64(&user->userflags, CONFFLAG_MONITOR) ? "Listen only" : ast_test_flag64(&user->userflags, CONFFLAG_TALKER) ? "Talk only" : "Talk and listen",
                                ast_test_flag64(&user->userflags, CONFFLAG_MARKEDUSER) ? "Yes" : "No",
@@ -4919,13 +5581,10 @@ static int action_meetmelist(struct mansession *s, const struct message *m)
                ao2_iterator_destroy(&user_iter);
        }
        AST_LIST_UNLOCK(&confs);
+
        /* Send final confirmation */
-       astman_append(s,
-       "Event: MeetmeListComplete\r\n"
-       "EventList: Complete\r\n"
-       "ListItems: %d\r\n"
-       "%s"
-       "\r\n", total, idText);
+       astman_send_list_complete_start(s, m, "MeetmeListComplete", total);
+       astman_send_list_complete_end(s);
        return 0;
 }
 
@@ -4982,20 +5641,41 @@ static int action_meetmelistrooms(struct mansession *s, const struct message *m)
                markedusers,
                hr,  min, sec,
                cnf->isdynamic ? "Dynamic" : "Static",
-               cnf->locked ? "Yes" : "No"); 
+               cnf->locked ? "Yes" : "No");
        }
        AST_LIST_UNLOCK(&confs);
 
        /* Send final confirmation */
-       astman_append(s,
-       "Event: MeetmeListRoomsComplete\r\n"
-       "EventList: Complete\r\n"
-       "ListItems: %d\r\n"
-       "%s"
-       "\r\n", totalitems, idText);
+       astman_send_list_complete_start(s, m, "MeetmeListRoomsComplete", totalitems);
+       astman_send_list_complete_end(s);
        return 0;
 }
 
+/*! \internal
+ * \brief creates directory structure and assigns absolute path from relative paths for filenames
+ *
+ * \param filename contains the absolute or relative path to the desired file
+ * \param buffer stores completed filename, absolutely must be a buffer of PATH_MAX length
+ */
+static void filename_parse(char *filename, char *buffer)
+{
+       char *slash;
+       if (ast_strlen_zero(filename)) {
+               ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
+       } else if (filename[0] != '/') {
+               snprintf(buffer, PATH_MAX, "%s/meetme/%s", ast_config_AST_SPOOL_DIR, filename);
+       } else {
+               ast_copy_string(buffer, filename, PATH_MAX);
+       }
+
+       slash = buffer;
+       if ((slash = strrchr(slash, '/'))) {
+               *slash = '\0';
+               ast_mkdir(buffer, 0777);
+               *slash = '/';
+       }
+}
+
 static void *recordthread(void *args)
 {
        struct ast_conference *cnf = args;
@@ -5005,11 +5685,15 @@ static void *recordthread(void *args)
        int res = 0;
        int x;
        const char *oldrecordingfilename = NULL;
+       char filename_buffer[PATH_MAX];
 
        if (!cnf || !cnf->lchan) {
                pthread_exit(0);
        }
 
+       filename_buffer[0] = '\0';
+       filename_parse(cnf->recordingfilename, filename_buffer);
+
        ast_stopstream(cnf->lchan);
        flags = O_CREAT | O_TRUNC | O_WRONLY;
 
@@ -5021,11 +5705,11 @@ static void *recordthread(void *args)
                        AST_LIST_UNLOCK(&confs);
                        break;
                }
-               if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) {
-                       s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, AST_FILE_MODE);
-                       oldrecordingfilename = cnf->recordingfilename;
+               if (!s && !(ast_strlen_zero(filename_buffer)) && (filename_buffer != oldrecordingfilename)) {
+                       s = ast_writefile(filename_buffer, cnf->recordingformat, NULL, flags, 0, AST_FILE_MODE);
+                       oldrecordingfilename = filename_buffer;
                }
-               
+
                f = ast_read(cnf->lchan);
                if (!f) {
                        res = -1;
@@ -5056,7 +5740,7 @@ static void *recordthread(void *args)
        cnf->recording = MEETME_RECORD_OFF;
        if (s)
                ast_closestream(s);
-       
+
        pthread_exit(0);
 }
 
@@ -5083,12 +5767,32 @@ static enum ast_device_state meetmestate(const char *data)
        return AST_DEVICE_INUSE;
 }
 
-static void load_config_meetme(void)
+static void meetme_set_defaults(void)
+{
+       /*  Scheduling support is off by default */
+       rt_schedule = 0;
+       fuzzystart = 0;
+       earlyalert = 0;
+       endalert = 0;
+       extendby = 0;
+
+       /*  Logging of participants defaults to ON for compatibility reasons */
+       rt_log_members = 1;
+
+       /* Set default number of buffers to be allocated. */
+       audio_buffers = DEFAULT_AUDIO_BUFFERS;
+}
+
+static void load_config_meetme(int reload)
 {
        struct ast_config *cfg;
        struct ast_flags config_flags = { 0 };
        const char *val;
 
+       if (!reload) {
+               meetme_set_defaults();
+       }
+
        if (!(cfg = ast_config_load(CONFIG_FILE_NAME, config_flags))) {
                return;
        } else if (cfg == CONFIG_STATUS_FILEINVALID) {
@@ -5096,17 +5800,9 @@ static void load_config_meetme(void)
                return;
        }
 
-       audio_buffers = DEFAULT_AUDIO_BUFFERS;
-
-       /*  Scheduling support is off by default */
-       rt_schedule = 0;
-       fuzzystart = 0;
-       earlyalert = 0;
-       endalert = 0;
-       extendby = 0;
-
-       /*  Logging of participants defaults to ON for compatibility reasons */
-       rt_log_members = 1;  
+       if (reload) {
+               meetme_set_defaults();
+       }
 
        if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) {
                if ((sscanf(val, "%30d", &audio_buffers) != 1)) {
@@ -5129,58 +5825,54 @@ static void load_config_meetme(void)
                if ((sscanf(val, "%30d", &fuzzystart) != 1)) {
                        ast_log(LOG_WARNING, "fuzzystart must be a number, not '%s'\n", val);
                        fuzzystart = 0;
-               } 
+               }
        }
        if ((val = ast_variable_retrieve(cfg, "general", "earlyalert"))) {
                if ((sscanf(val, "%30d", &earlyalert) != 1)) {
                        ast_log(LOG_WARNING, "earlyalert must be a number, not '%s'\n", val);
                        earlyalert = 0;
-               } 
+               }
        }
        if ((val = ast_variable_retrieve(cfg, "general", "endalert"))) {
                if ((sscanf(val, "%30d", &endalert) != 1)) {
                        ast_log(LOG_WARNING, "endalert must be a number, not '%s'\n", val);
                        endalert = 0;
-               } 
+               }
        }
        if ((val = ast_variable_retrieve(cfg, "general", "extendby"))) {
                if ((sscanf(val, "%30d", &extendby) != 1)) {
                        ast_log(LOG_WARNING, "extendby must be a number, not '%s'\n", val);
                        extendby = 0;
-               } 
+               }
        }
 
        ast_config_destroy(cfg);
 }
 
-/*! \brief Find an SLA trunk by name
- * \note This must be called with the sla_trunks container locked
+/*!
+ * \internal
+ * \brief Find an SLA trunk by name
  */
 static struct sla_trunk *sla_find_trunk(const char *name)
 {
-       struct sla_trunk *trunk = NULL;
-
-       AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
-               if (!strcasecmp(trunk->name, name))
-                       break;
-       }
+       struct sla_trunk tmp_trunk = {
+               .name = name,
+       };
 
-       return trunk;
+       return ao2_find(sla_trunks, &tmp_trunk, OBJ_POINTER);
 }
 
-/*! \brief Find an SLA station by name
- * \note This must be called with the sla_stations container locked
+/*!
+ * \internal
+ * \brief Find an SLA station by name
  */
 static struct sla_station *sla_find_station(const char *name)
 {
-       struct sla_station *station = NULL;
-
-       AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
-               if (!strcasecmp(station->name, name))
-                       break;
-       }
+       struct sla_station tmp_station = {
+               .name = name,
+       };
 
-       return station;
+       return ao2_find(sla_stations, &tmp_station, OBJ_POINTER);
 }
 
 static int sla_check_station_hold_access(const struct sla_trunk *trunk,
@@ -5204,9 +5896,11 @@ static int sla_check_station_hold_access(const struct sla_trunk *trunk,
        return 0;
 }
 
-/*! \brief Find a trunk reference on a station by name
+/*!
+ * \brief Find a trunk reference on a station by name
  * \param station the station
  * \param name the trunk's name
+ * \pre sla_station is locked
  * \return a pointer to the station's trunk reference.  If the trunk
  *         is not found, it is not idle and barge is disabled, or if
  *         it is on hold and private hold is set, then NULL will be returned.
@@ -5220,12 +5914,12 @@ static struct sla_trunk_ref *sla_find_trunk_ref_byname(const struct sla_station
                if (strcasecmp(trunk_ref->trunk->name, name))
                        continue;
 
-               if ( (trunk_ref->trunk->barge_disabled 
+               if ( (trunk_ref->trunk->barge_disabled
                        && trunk_ref->state == SLA_TRUNK_STATE_UP) ||
-                       (trunk_ref->trunk->hold_stations 
+                       (trunk_ref->trunk->hold_stations
                        && trunk_ref->trunk->hold_access == SLA_HOLD_PRIVATE
                        && trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) ||
-                       sla_check_station_hold_access(trunk_ref->trunk, station) ) 
+                       sla_check_station_hold_access(trunk_ref->trunk, station) )
                {
                        trunk_ref = NULL;
                }
@@ -5233,16 +5927,32 @@ static struct sla_trunk_ref *sla_find_trunk_ref_byname(const struct sla_station
                break;
        }
 
+       if (trunk_ref) {
+               ao2_ref(trunk_ref, 1);
+       }
+
        return trunk_ref;
 }
 
+static void sla_station_ref_destructor(void *obj)
+{
+       struct sla_station_ref *station_ref = obj;
+
+       if (station_ref->station) {
+               ao2_ref(station_ref->station, -1);
+               station_ref->station = NULL;
+       }
+}
+
 static struct sla_station_ref *sla_create_station_ref(struct sla_station *station)
 {
        struct sla_station_ref *station_ref;
 
-       if (!(station_ref = ast_calloc(1, sizeof(*station_ref))))
+       if (!(station_ref = ao2_alloc(sizeof(*station_ref), sla_station_ref_destructor))) {
                return NULL;
+       }
 
+       ao2_ref(station, 1);
        station_ref->station = station;
 
        return station_ref;
@@ -5255,12 +5965,48 @@ static struct sla_ringing_station *sla_create_ringing_station(struct sla_station
        if (!(ringing_station = ast_calloc(1, sizeof(*ringing_station))))
                return NULL;
 
+       ao2_ref(station, 1);
        ringing_station->station = station;
        ringing_station->ring_begin = ast_tvnow();
 
        return ringing_station;
 }
 
+static void sla_ringing_station_destroy(struct sla_ringing_station *ringing_station)
+{
+       if (ringing_station->station) {
+               ao2_ref(ringing_station->station, -1);
+               ringing_station->station = NULL;
+       }
+
+       ast_free(ringing_station);
+}
+
+static struct sla_failed_station *sla_create_failed_station(struct sla_station *station)
+{
+       struct sla_failed_station *failed_station;
+
+       if (!(failed_station = ast_calloc(1, sizeof(*failed_station)))) {
+               return NULL;
+       }
+
+       ao2_ref(station, 1);
+       failed_station->station = station;
+       failed_station->last_try = ast_tvnow();
+
+       return failed_station;
+}
+
+static void sla_failed_station_destroy(struct sla_failed_station *failed_station)
+{
+       if (failed_station->station) {
+               ao2_ref(failed_station->station, -1);
+               failed_station->station = NULL;
+       }
+
+       ast_free(failed_station);
+}
+
 static enum ast_device_state sla_state_to_devstate(enum sla_trunk_state state)
 {
        switch (state) {
@@ -5278,23 +6024,30 @@ static enum ast_device_state sla_state_to_devstate(enum sla_trunk_state state)
        return AST_DEVICE_UNKNOWN;
 }
 
-static void sla_change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk_state state, 
+static void sla_change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk_state state,
        enum sla_which_trunk_refs inactive_only, const struct sla_trunk_ref *exclude)
 {
        struct sla_station *station;
        struct sla_trunk_ref *trunk_ref;
+       struct ao2_iterator i;
 
-       AST_LIST_TRAVERSE(&sla_stations, station, entry) {
+       i = ao2_iterator_init(sla_stations, 0);
+       while ((station = ao2_iterator_next(&i))) {
+               ao2_lock(station);
                AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
                        if (trunk_ref->trunk != trunk || (inactive_only ? trunk_ref->chan : 0)
-                               || trunk_ref == exclude)
+                                       || trunk_ref == exclude) {
                                continue;
+                       }
                        trunk_ref->state = state;
-                       ast_devstate_changed(sla_state_to_devstate(state), 
-                               "SLA:%s_%s", station->name, trunk->name);
+                       ast_devstate_changed(sla_state_to_devstate(state), AST_DEVSTATE_CACHABLE,
+                                            "SLA:%s_%s", station->name, trunk->name);
                        break;
                }
+               ao2_unlock(station);
+               ao2_ref(station, -1);
        }
+       ao2_iterator_destroy(&i);
 }
 
 struct run_station_args {
@@ -5312,8 +6065,8 @@ static void answer_trunk_chan(struct ast_channel *chan)
 
 static void *run_station(void *data)
 {
-       struct sla_station *station;
-       struct sla_trunk_ref *trunk_ref;
+       RAII_VAR(struct sla_station *, station, NULL, ao2_cleanup);
+       RAII_VAR(struct sla_trunk_ref *, trunk_ref, NULL, ao2_cleanup);
        struct ast_str *conf_name = ast_str_create(16);
        struct ast_flags64 conf_flags = { 0 };
        struct ast_conference *conf;
@@ -5330,7 +6083,7 @@ static void *run_station(void *data)
 
        ast_atomic_fetchadd_int((int *) &trunk_ref->trunk->active_stations, 1);
        ast_str_set(&conf_name, 0, "SLA_%s", trunk_ref->trunk->name);
-       ast_set_flag64(&conf_flags, 
+       ast_set_flag64(&conf_flags,
                CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF | CONFFLAG_SLA_STATION);
        answer_trunk_chan(trunk_ref->chan);
        conf = build_conf(ast_str_buffer(conf_name), "", "", 0, 0, 1, trunk_ref->chan, NULL);
@@ -5356,6 +6109,8 @@ static void *run_station(void *data)
        return NULL;
 }
 
+static void sla_ringing_trunk_destroy(struct sla_ringing_trunk *ringing_trunk);
+
 static void sla_stop_ringing_trunk(struct sla_ringing_trunk *ringing_trunk)
 {
        char buf[80];
@@ -5365,10 +6120,11 @@ static void sla_stop_ringing_trunk(struct sla_ringing_trunk *ringing_trunk)
        admin_exec(NULL, buf);
        sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
 
-       while ((station_ref = AST_LIST_REMOVE_HEAD(&ringing_trunk->timed_out_stations, entry)))
-               ast_free(station_ref);
+       while ((station_ref = AST_LIST_REMOVE_HEAD(&ringing_trunk->timed_out_stations, entry))) {
+               ao2_ref(station_ref, -1);
+       }
 
-       ast_free(ringing_trunk);
+       sla_ringing_trunk_destroy(ringing_trunk);
 }
 
 static void sla_stop_ringing_station(struct sla_ringing_station *ringing_station,
@@ -5403,7 +6159,7 @@ static void sla_stop_ringing_station(struct sla_ringing_station *ringing_station
        }
 
 done:
-       ast_free(ringing_station);
+       sla_ringing_station_destroy(ringing_station);
 }
 
 static void sla_dial_state_callback(struct ast_dial *dial)
@@ -5435,7 +6191,7 @@ static int sla_check_timed_out_station(const struct sla_ringing_trunk *ringing_t
  * \return a pointer to the selected ringing trunk, or NULL if none found
  * \note Assumes that sla.lock is locked
  */
-static struct sla_ringing_trunk *sla_choose_ringing_trunk(struct sla_station *station, 
+static struct sla_ringing_trunk *sla_choose_ringing_trunk(struct sla_station *station,
        struct sla_trunk_ref **trunk_ref, int rm)
 {
        struct sla_trunk_ref *s_trunk_ref;
@@ -5455,13 +6211,15 @@ static struct sla_ringing_trunk *sla_choose_ringing_trunk(struct sla_station *st
                        if (rm)
                                AST_LIST_REMOVE_CURRENT(entry);
 
-                       if (trunk_ref)
+                       if (trunk_ref) {
+                               ao2_ref(s_trunk_ref, 1);
                                *trunk_ref = s_trunk_ref;
+                       }
 
                        break;
                }
                AST_LIST_TRAVERSE_SAFE_END;
-       
+
                if (ringing_trunk)
                        break;
        }
@@ -5474,7 +6232,7 @@ static void sla_handle_dial_state_event(void)
        struct sla_ringing_station *ringing_station;
 
        AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) {
-               struct sla_trunk_ref *s_trunk_ref = NULL;
+               RAII_VAR(struct sla_trunk_ref *, s_trunk_ref, NULL, ao2_cleanup);
                struct sla_ringing_trunk *ringing_trunk = NULL;
                struct run_station_args args;
                enum ast_dial_result dial_res;
@@ -5498,7 +6256,16 @@ static void sla_handle_dial_state_event(void)
                        ringing_trunk = sla_choose_ringing_trunk(ringing_station->station, &s_trunk_ref, 1);
                        ast_mutex_unlock(&sla.lock);
                        if (!ringing_trunk) {
+                               /* This case happens in a bit of a race condition.  If two stations answer
+                                * the outbound call at the same time, the first one will get connected to
+                                * the trunk.  When the second one gets here, it will not see any trunks
+                                * ringing so we have no idea what to conect it to.  So, we just hang up
+                                * on it. */
                                ast_debug(1, "Found no ringing trunk for station '%s' to answer!\n", ringing_station->station->name);
+                               ast_dial_join(ringing_station->station->dial);
+                               ast_dial_destroy(ringing_station->station->dial);
+                               ringing_station->station->dial = NULL;
+                               sla_ringing_station_destroy(ringing_station);
                                break;
                        }
                        /* Track the channel that answered this trunk */
@@ -5509,12 +6276,14 @@ static void sla_handle_dial_state_event(void)
                        /* Now, start a thread that will connect this station to the trunk.  The rest of
                         * the code here sets up the thread and ensures that it is able to save the arguments
                         * before they are no longer valid since they are allocated on the stack. */
+                       ao2_ref(s_trunk_ref, 1);
                        args.trunk_ref = s_trunk_ref;
+                       ao2_ref(ringing_station->station, 1);
                        args.station = ringing_station->station;
                        args.cond = &cond;
                        args.cond_lock = &cond_lock;
-                       ast_free(ringing_trunk);
-                       ast_free(ringing_station);
+                       sla_ringing_trunk_destroy(ringing_trunk);
+                       sla_ringing_station_destroy(ringing_station);
                        ast_mutex_init(&cond_lock);
                        ast_cond_init(&cond, NULL);
                        ast_mutex_lock(&cond_lock);
@@ -5540,8 +6309,8 @@ static void sla_handle_dial_state_event(void)
        AST_LIST_TRAVERSE_SAFE_END;
 }
 
-/*! \brief Check to see if this station is already ringing 
- * \note Assumes sla.lock is locked 
+/*! \brief Check to see if this station is already ringing
+ * \note Assumes sla.lock is locked
  */
 static int sla_check_ringing_station(const struct sla_station *station)
 {
@@ -5568,7 +6337,7 @@ static int sla_check_failed_station(const struct sla_station *station)
                        continue;
                if (ast_tvdiff_ms(ast_tvnow(), failed_station->last_try) > 1000) {
                        AST_LIST_REMOVE_CURRENT(entry);
-                       ast_free(failed_station);
+                       sla_failed_station_destroy(failed_station);
                        break;
                }
                res = 1;
@@ -5597,7 +6366,7 @@ static int sla_ring_station(struct sla_ringing_trunk *ringing_trunk, struct sla_
        tech_data = ast_strdupa(station->device);
        tech = strsep(&tech_data, "/");
 
-       if (ast_dial_append(dial, tech, tech_data) == -1) {
+       if (ast_dial_append(dial, tech, tech_data, NULL) == -1) {
                ast_dial_destroy(dial);
                return -1;
        }
@@ -5606,26 +6375,24 @@ static int sla_ring_station(struct sla_ringing_trunk *ringing_trunk, struct sla_
        caller_is_saved = 0;
        if (!sla.attempt_callerid) {
                caller_is_saved = 1;
-               caller = ringing_trunk->trunk->chan->caller;
-               ast_party_caller_init(&ringing_trunk->trunk->chan->caller);
+               caller = *ast_channel_caller(ringing_trunk->trunk->chan);
+               ast_party_caller_init(ast_channel_caller(ringing_trunk->trunk->chan));
        }
 
        res = ast_dial_run(dial, ringing_trunk->trunk->chan, 1);
-       
+
        /* Restore saved caller ID */
        if (caller_is_saved) {
-               ast_party_caller_free(&ringing_trunk->trunk->chan->caller);
-               ringing_trunk->trunk->chan->caller = caller;
+               ast_party_caller_free(ast_channel_caller(ringing_trunk->trunk->chan));
+               ast_channel_caller_set(ringing_trunk->trunk->chan, &caller);
        }
-       
+
        if (res != AST_DIAL_RESULT_TRYING) {
                struct sla_failed_station *failed_station;
                ast_dial_destroy(dial);
-               if (!(failed_station = ast_calloc(1, sizeof(*failed_station))))
-                       return -1;
-               failed_station->station = station;
-               failed_station->last_try = ast_tvnow();
-               AST_LIST_INSERT_HEAD(&sla.failed_stations, failed_station, entry);
+               if ((failed_station = sla_create_failed_station(station))) {
+                       AST_LIST_INSERT_HEAD(&sla.failed_stations, failed_station, entry);
+               }
                return -1;
        }
        if (!(ringing_station = sla_create_ringing_station(station))) {
@@ -5665,6 +6432,8 @@ static struct sla_trunk_ref *sla_find_trunk_ref(const struct sla_station *statio
                        break;
        }
 
+       ao2_ref(trunk_ref, 1);
+
        return trunk_ref;
 }
 
@@ -5673,10 +6442,10 @@ static struct sla_trunk_ref *sla_find_trunk_ref(const struct sla_station *statio
  * \param ringing_trunk the trunk.  If NULL, the highest priority ringing trunk will be used
  * \return the number of ms left before the delay is complete, or INT_MAX if there is no delay
  */
-static int sla_check_station_delay(struct sla_station *station, 
+static int sla_check_station_delay(struct sla_station *station,
        struct sla_ringing_trunk *ringing_trunk)
 {
-       struct sla_trunk_ref *trunk_ref;
+       RAII_VAR(struct sla_trunk_ref *, trunk_ref, NULL, ao2_cleanup);
        unsigned int delay = UINT_MAX;
        int time_left, time_elapsed;
 
@@ -5769,7 +6538,7 @@ static void sla_hangup_stations(void)
                        ast_dial_join(ringing_station->station->dial);
                        ast_dial_destroy(ringing_station->station->dial);
                        ringing_station->station->dial = NULL;
-                       ast_free(ringing_station);
+                       sla_ringing_station_destroy(ringing_station);
                }
        }
        AST_LIST_TRAVERSE_SAFE_END
@@ -5789,9 +6558,9 @@ static void sla_handle_hold_event(struct sla_event *event)
 {
        ast_atomic_fetchadd_int((int *) &event->trunk_ref->trunk->hold_stations, 1);
        event->trunk_ref->state = SLA_TRUNK_STATE_ONHOLD_BYME;
-       ast_devstate_changed(AST_DEVICE_ONHOLD, "SLA:%s_%s", 
-               event->station->name, event->trunk_ref->trunk->name);
-       sla_change_trunk_state(event->trunk_ref->trunk, SLA_TRUNK_STATE_ONHOLD, 
+       ast_devstate_changed(AST_DEVICE_ONHOLD, AST_DEVSTATE_CACHABLE, "SLA:%s_%s",
+                            event->station->name, event->trunk_ref->trunk->name);
+       sla_change_trunk_state(event->trunk_ref->trunk, SLA_TRUNK_STATE_ONHOLD,
                INACTIVE_TRUNK_REFS, event->trunk_ref);
 
        if (event->trunk_ref->trunk->active_stations == 1) {
@@ -5926,8 +6695,10 @@ static int sla_calc_station_delays(unsigned int *timeout)
 {
        struct sla_station *station;
        int res = 0;
+       struct ao2_iterator i;
 
-       AST_LIST_TRAVERSE(&sla_stations, station, entry) {
+       i = ao2_iterator_init(sla_stations, 0);
+       for (; (station = ao2_iterator_next(&i)); ao2_ref(station, -1)) {
                struct sla_ringing_trunk *ringing_trunk;
                int time_left;
 
@@ -5947,7 +6718,7 @@ static int sla_calc_station_delays(unsigned int *timeout)
                        continue;
 
                /* If there is no time left, then the station needs to start ringing.
-                * Return non-zero so that an event will be queued up an event to 
+                * Return non-zero so that an event will be queued up an event to
                 * make that happen. */
                if (time_left <= 0) {
                        res = 1;
@@ -5957,6 +6728,7 @@ static int sla_calc_station_delays(unsigned int *timeout)
                if (time_left < *timeout)
                        *timeout = time_left;
        }
+       ao2_iterator_destroy(&i);
 
        return res;
 }
@@ -5998,62 +6770,19 @@ static int sla_process_timers(struct timespec *ts)
        return 1;
 }
 
-static int sla_load_config(int reload);
-
-/*! \brief Check if we can do a reload of SLA, and do it if we can */
-static void sla_check_reload(void)
+static void sla_event_destroy(struct sla_event *event)
 {
-       struct sla_station *station;
-       struct sla_trunk *trunk;
-
-       ast_mutex_lock(&sla.lock);
-
-       if (!AST_LIST_EMPTY(&sla.event_q) || !AST_LIST_EMPTY(&sla.ringing_trunks) 
-               || !AST_LIST_EMPTY(&sla.ringing_stations)) {
-               ast_mutex_unlock(&sla.lock);
-               return;
-       }
-
-       AST_RWLIST_RDLOCK(&sla_stations);
-       AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
-               if (station->ref_count)
-                       break;
-       }
-       AST_RWLIST_UNLOCK(&sla_stations);
-       if (station) {
-               ast_mutex_unlock(&sla.lock);
-               return;
+       if (event->trunk_ref) {
+               ao2_ref(event->trunk_ref, -1);
+               event->trunk_ref = NULL;
        }
 
-       AST_RWLIST_RDLOCK(&sla_trunks);
-       AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
-               if (trunk->ref_count)
-                       break;
-       }
-       AST_RWLIST_UNLOCK(&sla_trunks);
-       if (trunk) {
-               ast_mutex_unlock(&sla.lock);
-               return;
-       }
-
-       /* We need to actually delete the previous versions of trunks and stations now */
-       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&sla_stations, station, entry) {
-               AST_RWLIST_REMOVE_CURRENT(entry);
-               ast_free(station);
-       }
-       AST_RWLIST_TRAVERSE_SAFE_END;
-
-       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&sla_trunks, trunk, entry) {
-               AST_RWLIST_REMOVE_CURRENT(entry);
-               ast_free(trunk);
+       if (event->station) {
+               ao2_ref(event->station, -1);
+               event->station = NULL;
        }
-       AST_RWLIST_TRAVERSE_SAFE_END;
 
-       /* yay */
-       sla_load_config(1);
-       sla.reload = 0;
-
-       ast_mutex_unlock(&sla.lock);
+       ast_free(event);
 }
 
 static void *sla_thread(void *data)
@@ -6092,27 +6821,21 @@ static void *sla_thread(void *data)
                        case SLA_EVENT_RINGING_TRUNK:
                                sla_handle_ringing_trunk_event();
                                break;
-                       case SLA_EVENT_RELOAD:
-                               sla.reload = 1;
-                       case SLA_EVENT_CHECK_RELOAD:
-                               break;
                        }
-                       ast_free(event);
+                       sla_event_destroy(event);
                        ast_mutex_lock(&sla.lock);
                }
-
-               if (sla.reload) {
-                       sla_check_reload();
-               }
        }
 
        ast_mutex_unlock(&sla.lock);
 
-       while ((ringing_station = AST_LIST_REMOVE_HEAD(&sla.ringing_stations, entry)))
-               ast_free(ringing_station);
+       while ((ringing_station = AST_LIST_REMOVE_HEAD(&sla.ringing_stations, entry))) {
+               sla_ringing_station_destroy(ringing_station);
+       }
 
-       while ((failed_station = AST_LIST_REMOVE_HEAD(&sla.failed_stations, entry)))
-               ast_free(failed_station);
+       while ((failed_station = AST_LIST_REMOVE_HEAD(&sla.failed_stations, entry))) {
+               sla_failed_station_destroy(failed_station);
+       }
 
        return NULL;
 }
@@ -6133,9 +6856,12 @@ static void *dial_trunk(void *data)
        char conf_name[MAX_CONFNUM];
        struct ast_conference *conf;
        struct ast_flags64 conf_flags = { 0 };
-       struct sla_trunk_ref *trunk_ref = args->trunk_ref;
+       RAII_VAR(struct sla_trunk_ref *, trunk_ref, args->trunk_ref, ao2_cleanup);
+       RAII_VAR(struct sla_station *, station, args->station, ao2_cleanup);
        int caller_is_saved;
        struct ast_party_caller caller;
+       int last_state = 0;
+       int current_state = 0;
 
        if (!(dial = ast_dial_create())) {
                ast_mutex_lock(args->cond_lock);
@@ -6146,7 +6872,7 @@ static void *dial_trunk(void *data)
 
        tech_data = ast_strdupa(trunk_ref->trunk->device);
        tech = strsep(&tech_data, "/");
-       if (ast_dial_append(dial, tech, tech_data) == -1) {
+       if (ast_dial_append(dial, tech, tech_data, NULL) == -1) {
                ast_mutex_lock(args->cond_lock);
                ast_cond_signal(args->cond);
                ast_mutex_unlock(args->cond_lock);
@@ -6158,16 +6884,16 @@ static void *dial_trunk(void *data)
        caller_is_saved = 0;
        if (!sla.attempt_callerid) {
                caller_is_saved = 1;
-               caller = trunk_ref->chan->caller;
-               ast_party_caller_init(&trunk_ref->chan->caller);
+               caller = *ast_channel_caller(trunk_ref->chan);
+               ast_party_caller_init(ast_channel_caller(trunk_ref->chan));
        }
 
        dial_res = ast_dial_run(dial, trunk_ref->chan, 1);
 
        /* Restore saved caller ID */
        if (caller_is_saved) {
-               ast_party_caller_free(&trunk_ref->chan->caller);
-               trunk_ref->chan->caller = caller;
+               ast_party_caller_free(ast_channel_caller(trunk_ref->chan));
+               ast_channel_caller_set(trunk_ref->chan, &caller);
        }
 
        if (dial_res != AST_DIAL_RESULT_TRYING) {
@@ -6189,14 +6915,35 @@ static void *dial_trunk(void *data)
                case AST_DIAL_RESULT_TIMEOUT:
                case AST_DIAL_RESULT_UNANSWERED:
                        done = 1;
+                       break;
                case AST_DIAL_RESULT_TRYING:
+                       current_state = AST_CONTROL_PROGRESS;
+                       break;
                case AST_DIAL_RESULT_RINGING:
                case AST_DIAL_RESULT_PROGRESS:
                case AST_DIAL_RESULT_PROCEEDING:
+                       current_state = AST_CONTROL_RINGING;
                        break;
                }
                if (done)
                        break;
+
+               /* check that SLA station that originated trunk call is still alive */
+               if (station && ast_device_state(station->device) == AST_DEVICE_NOT_INUSE) {
+                       ast_debug(3, "Originating station device %s no longer active\n", station->device);
+                       trunk_ref->trunk->chan = NULL;
+                       break;
+               }
+
+               /* If trunk line state changed, send indication back to originating SLA Station channel */
+               if (current_state != last_state) {
+                       ast_debug(3, "Indicating State Change %d to channel %s\n", current_state, ast_channel_name(trunk_ref->chan));
+                       ast_indicate(trunk_ref->chan, current_state);
+                       last_state = current_state;
+               }
+
+               /* avoid tight loop... sleep for 1/10th second */
+               ast_safe_sleep(trunk_ref->chan, 100);
        }
 
        if (!trunk_ref->trunk->chan) {
@@ -6209,8 +6956,8 @@ static void *dial_trunk(void *data)
        }
 
        snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name);
-       ast_set_flag64(&conf_flags, 
-               CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | 
+       ast_set_flag64(&conf_flags,
+               CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER |
                CONFFLAG_PASS_DTMF | CONFFLAG_SLA_TRUNK);
        conf = build_conf(conf_name, "", "", 1, 1, 1, trunk_ref->trunk->chan, NULL);
 
@@ -6236,15 +6983,19 @@ static void *dial_trunk(void *data)
        return NULL;
 }
 
-/*! \brief For a given station, choose the highest priority idle trunk
+/*!
+ * \brief For a given station, choose the highest priority idle trunk
+ * \pre sla_station is locked
  */
 static struct sla_trunk_ref *sla_choose_idle_trunk(const struct sla_station *station)
 {
        struct sla_trunk_ref *trunk_ref = NULL;
 
        AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
-               if (trunk_ref->state == SLA_TRUNK_STATE_IDLE)
+               if (trunk_ref->state == SLA_TRUNK_STATE_IDLE) {
+                       ao2_ref(trunk_ref, 1);
                        break;
+               }
        }
 
        return trunk_ref;
@@ -6253,8 +7004,8 @@ static struct sla_trunk_ref *sla_choose_idle_trunk(const struct sla_station *sta
 static int sla_station_exec(struct ast_channel *chan, const char *data)
 {
        char *station_name, *trunk_name;
-       struct sla_station *station;
-       struct sla_trunk_ref *trunk_ref = NULL;
+       RAII_VAR(struct sla_station *, station, NULL, ao2_cleanup);
+       RAII_VAR(struct sla_trunk_ref *, trunk_ref, NULL, ao2_cleanup);
        char conf_name[MAX_CONFNUM];
        struct ast_flags64 conf_flags = { 0 };
        struct ast_conference *conf;
@@ -6274,25 +7025,21 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                return 0;
        }
 
-       AST_RWLIST_RDLOCK(&sla_stations);
        station = sla_find_station(station_name);
-       if (station)
-               ast_atomic_fetchadd_int((int *) &station->ref_count, 1);
-       AST_RWLIST_UNLOCK(&sla_stations);
 
        if (!station) {
                ast_log(LOG_WARNING, "Station '%s' not found!\n", station_name);
                pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE");
-               sla_queue_event(SLA_EVENT_CHECK_RELOAD);
                return 0;
        }
 
-       AST_RWLIST_RDLOCK(&sla_trunks);
+       ao2_lock(station);
        if (!ast_strlen_zero(trunk_name)) {
                trunk_ref = sla_find_trunk_ref_byname(station, trunk_name);
-       } else
+       } else {
                trunk_ref = sla_choose_idle_trunk(station);
-       AST_RWLIST_UNLOCK(&sla_trunks);
+       }
+       ao2_unlock(station);
 
        if (!trunk_ref) {
                if (ast_strlen_zero(trunk_name))
@@ -6302,8 +7049,6 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                                "'%s' due to access controls.\n", trunk_name);
                }
                pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION");
-               ast_atomic_fetchadd_int((int *) &station->ref_count, -1);
-               sla_queue_event(SLA_EVENT_CHECK_RELOAD);
                return 0;
        }
 
@@ -6312,8 +7057,8 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                        sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
                else {
                        trunk_ref->state = SLA_TRUNK_STATE_UP;
-                       ast_devstate_changed(AST_DEVICE_INUSE, 
-                               "SLA:%s_%s", station->name, trunk_ref->trunk->name);
+                       ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE,
+                                            "SLA:%s_%s", station->name, trunk_ref->trunk->name);
                }
        } else if (trunk_ref->state == SLA_TRUNK_STATE_RINGING) {
                struct sla_ringing_trunk *ringing_trunk;
@@ -6332,7 +7077,7 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                        answer_trunk_chan(ringing_trunk->trunk->chan);
                        sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
 
-                       free(ringing_trunk);
+                       sla_ringing_trunk_destroy(ringing_trunk);
 
                        /* Queue up reprocessing ringing trunks, and then ringing stations again */
                        sla_queue_event(SLA_EVENT_RINGING_TRUNK);
@@ -6352,6 +7097,8 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                        .cond_lock = &cond_lock,
                        .cond = &cond,
                };
+               ao2_ref(trunk_ref, 1);
+               ao2_ref(station, 1);
                sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
                /* Create a thread to dial the trunk and dump it into the conference.
                 * However, we want to wait until the trunk has been dialed and the
@@ -6367,12 +7114,10 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                ast_cond_destroy(&cond);
                ast_autoservice_stop(chan);
                if (!trunk_ref->trunk->chan) {
-                       ast_debug(1, "Trunk didn't get created. chan: %lx\n", (long) trunk_ref->trunk->chan);
+                       ast_debug(1, "Trunk didn't get created. chan: %lx\n", (unsigned long) trunk_ref->trunk->chan);
                        pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION");
                        sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
                        trunk_ref->chan = NULL;
-                       ast_atomic_fetchadd_int((int *) &station->ref_count, -1);
-                       sla_queue_event(SLA_EVENT_CHECK_RELOAD);
                        return 0;
                }
        }
@@ -6402,22 +7147,31 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
                trunk_ref->trunk->hold_stations = 0;
                sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
        }
-       
-       pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "SUCCESS");
 
-       ast_atomic_fetchadd_int((int *) &station->ref_count, -1);
-       sla_queue_event(SLA_EVENT_CHECK_RELOAD);
+       pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "SUCCESS");
 
        return 0;
 }
 
+static void sla_trunk_ref_destructor(void *obj)
+{
+       struct sla_trunk_ref *trunk_ref = obj;
+
+       if (trunk_ref->trunk) {
+               ao2_ref(trunk_ref->trunk, -1);
+               trunk_ref->trunk = NULL;
+       }
+}
+
 static struct sla_trunk_ref *create_trunk_ref(struct sla_trunk *trunk)
 {
        struct sla_trunk_ref *trunk_ref;
 
-       if (!(trunk_ref = ast_calloc(1, sizeof(*trunk_ref))))
+       if (!(trunk_ref = ao2_alloc(sizeof(*trunk_ref), sla_trunk_ref_destructor))) {
                return NULL;
+       }
 
+       ao2_ref(trunk, 1);
        trunk_ref->trunk = trunk;
 
        return trunk_ref;
@@ -6427,9 +7181,11 @@ static struct sla_ringing_trunk *queue_ringing_trunk(struct sla_trunk *trunk)
 {
        struct sla_ringing_trunk *ringing_trunk;
 
-       if (!(ringing_trunk = ast_calloc(1, sizeof(*ringing_trunk))))
+       if (!(ringing_trunk = ast_calloc(1, sizeof(*ringing_trunk)))) {
                return NULL;
-       
+       }
+
+       ao2_ref(trunk, 1);
        ringing_trunk->trunk = trunk;
        ringing_trunk->ring_begin = ast_tvnow();
 
@@ -6444,6 +7200,16 @@ static struct sla_ringing_trunk *queue_ringing_trunk(struct sla_trunk *trunk)
        return ringing_trunk;
 }
 
+static void sla_ringing_trunk_destroy(struct sla_ringing_trunk *ringing_trunk)
+{
+       if (ringing_trunk->trunk) {
+               ao2_ref(ringing_trunk->trunk, -1);
+               ringing_trunk->trunk = NULL;
+       }
+
+       ast_free(ringing_trunk);
+}
+
 enum {
        SLA_TRUNK_OPT_MOH = (1 << 0),
 };
@@ -6462,7 +7228,7 @@ static int sla_trunk_exec(struct ast_channel *chan, const char *data)
        char conf_name[MAX_CONFNUM];
        struct ast_conference *conf;
        struct ast_flags64 conf_flags = { 0 };
-       struct sla_trunk *trunk;
+       RAII_VAR(struct sla_trunk *, trunk, NULL, ao2_cleanup);
        struct sla_ringing_trunk *ringing_trunk;
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(trunk_name);
@@ -6486,16 +7252,11 @@ static int sla_trunk_exec(struct ast_channel *chan, const char *data)
                }
        }
 
-       AST_RWLIST_RDLOCK(&sla_trunks);
        trunk = sla_find_trunk(args.trunk_name);
-       if (trunk)
-               ast_atomic_fetchadd_int((int *) &trunk->ref_count, 1);
-       AST_RWLIST_UNLOCK(&sla_trunks);
 
        if (!trunk) {
                ast_log(LOG_ERROR, "SLA Trunk '%s' not found!\n", args.trunk_name);
                pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
-               sla_queue_event(SLA_EVENT_CHECK_RELOAD);        
                return 0;
        }
 
@@ -6503,8 +7264,6 @@ static int sla_trunk_exec(struct ast_channel *chan, const char *data)
                ast_log(LOG_ERROR, "Call came in on %s, but the trunk is already in use!\n",
                        args.trunk_name);
                pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
-               ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1);
-               sla_queue_event(SLA_EVENT_CHECK_RELOAD);        
                return 0;
        }
 
@@ -6512,8 +7271,6 @@ static int sla_trunk_exec(struct ast_channel *chan, const char *data)
 
        if (!(ringing_trunk = queue_ringing_trunk(trunk))) {
                pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
-               ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1);
-               sla_queue_event(SLA_EVENT_CHECK_RELOAD);        
                return 0;
        }
 
@@ -6521,11 +7278,9 @@ static int sla_trunk_exec(struct ast_channel *chan, const char *data)
        conf = build_conf(conf_name, "", "", 1, 1, 1, chan, NULL);
        if (!conf) {
                pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
-               ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1);
-               sla_queue_event(SLA_EVENT_CHECK_RELOAD);        
                return 0;
        }
-       ast_set_flag64(&conf_flags, 
+       ast_set_flag64(&conf_flags,
                CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | CONFFLAG_PASS_DTMF | CONFFLAG_NO_AUDIO_UNTIL_UP);
 
        if (ast_test_flag(&opt_flags, SLA_TRUNK_OPT_MOH)) {
@@ -6556,46 +7311,37 @@ static int sla_trunk_exec(struct ast_channel *chan, const char *data)
        AST_LIST_TRAVERSE_SAFE_END;
        ast_mutex_unlock(&sla.lock);
        if (ringing_trunk) {
-               ast_free(ringing_trunk);
+               sla_ringing_trunk_destroy(ringing_trunk);
                pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "UNANSWERED");
                /* Queue reprocessing of ringing trunks to make stations stop ringing
                 * that shouldn't be ringing after this trunk stopped. */
                sla_queue_event(SLA_EVENT_RINGING_TRUNK);
        }
 
-       ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1);
-       sla_queue_event(SLA_EVENT_CHECK_RELOAD);        
-
        return 0;
 }
 
 static enum ast_device_state sla_state(const char *data)
 {
        char *buf, *station_name, *trunk_name;
-       struct sla_station *station;
+       RAII_VAR(struct sla_station *, station, NULL, ao2_cleanup);
        struct sla_trunk_ref *trunk_ref;
        enum ast_device_state res = AST_DEVICE_INVALID;
 
        trunk_name = buf = ast_strdupa(data);
        station_name = strsep(&trunk_name, "_");
 
-       AST_RWLIST_RDLOCK(&sla_stations);
-       AST_LIST_TRAVERSE(&sla_stations, station, entry) {
-               if (strcasecmp(station_name, station->name))
-                       continue;
-               AST_RWLIST_RDLOCK(&sla_trunks);
+       station = sla_find_station(station_name);
+       if (station) {
+               ao2_lock(station);
                AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
-                       if (!strcasecmp(trunk_name, trunk_ref->trunk->name))
+                       if (!strcasecmp(trunk_name, trunk_ref->trunk->name)) {
+                               res = sla_state_to_devstate(trunk_ref->state);
                                break;
+                       }
                }
-               if (!trunk_ref) {
-                       AST_RWLIST_UNLOCK(&sla_trunks);
-                       break;
-               }
-               res = sla_state_to_devstate(trunk_ref->state);
-               AST_RWLIST_UNLOCK(&sla_trunks);
+               ao2_unlock(station);
        }
-       AST_RWLIST_UNLOCK(&sla_stations);
 
        if (res == AST_DEVICE_INVALID) {
                ast_log(LOG_ERROR, "Could not determine state for trunk %s on station %s!\n",
@@ -6605,61 +7351,72 @@ static enum ast_device_state sla_state(const char *data)
        return res;
 }
 
-static void destroy_trunk(struct sla_trunk *trunk)
+static int sla_trunk_release_refs(void *obj, void *arg, int flags)
 {
+       struct sla_trunk *trunk = obj;
        struct sla_station_ref *station_ref;
 
-       if (!ast_strlen_zero(trunk->autocontext))
-               ast_context_remove_extension(trunk->autocontext, "s", 1, sla_registrar);
-
-       while ((station_ref = AST_LIST_REMOVE_HEAD(&trunk->stations, entry)))
-               ast_free(station_ref);
+       while ((station_ref = AST_LIST_REMOVE_HEAD(&trunk->stations, entry))) {
+               ao2_ref(station_ref, -1);
+       }
 
-       ast_string_field_free_memory(trunk);
-       ast_free(trunk);
+       return 0;
 }
 
-static void destroy_station(struct sla_station *station)
+static int sla_station_release_refs(void *obj, void *arg, int flags)
 {
+       struct sla_station *station = obj;
        struct sla_trunk_ref *trunk_ref;
 
+       while ((trunk_ref = AST_LIST_REMOVE_HEAD(&station->trunks, entry))) {
+               ao2_ref(trunk_ref, -1);
+       }
+
+       return 0;
+}
+
+static void sla_station_destructor(void *obj)
+{
+       struct sla_station *station = obj;
+
+       ast_debug(1, "sla_station destructor for '%s'\n", station->name);
+
        if (!ast_strlen_zero(station->autocontext)) {
-               AST_RWLIST_RDLOCK(&sla_trunks);
+               struct sla_trunk_ref *trunk_ref;
+
                AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
                        char exten[AST_MAX_EXTENSION];
                        char hint[AST_MAX_APP];
                        snprintf(exten, sizeof(exten), "%s_%s", station->name, trunk_ref->trunk->name);
                        snprintf(hint, sizeof(hint), "SLA:%s", exten);
-                       ast_context_remove_extension(station->autocontext, exten, 
+                       ast_context_remove_extension(station->autocontext, exten,
                                1, sla_registrar);
-                       ast_context_remove_extension(station->autocontext, hint, 
+                       ast_context_remove_extension(station->autocontext, hint,
                                PRIORITY_HINT, sla_registrar);
                }
-               AST_RWLIST_UNLOCK(&sla_trunks);
        }
 
-       while ((trunk_ref = AST_LIST_REMOVE_HEAD(&station->trunks, entry)))
-               ast_free(trunk_ref);
+       sla_station_release_refs(station, NULL, 0);
 
        ast_string_field_free_memory(station);
-       ast_free(station);
 }
 
-static void sla_destroy(void)
+static int sla_trunk_cmp(void *obj, void *arg, int flags)
 {
-       struct sla_trunk *trunk;
-       struct sla_station *station;
+       struct sla_trunk *trunk = obj, *trunk2 = arg;
 
-       AST_RWLIST_WRLOCK(&sla_trunks);
-       while ((trunk = AST_RWLIST_REMOVE_HEAD(&sla_trunks, entry)))
-               destroy_trunk(trunk);
-       AST_RWLIST_UNLOCK(&sla_trunks);
+       return !strcasecmp(trunk->name, trunk2->name) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int sla_station_cmp(void *obj, void *arg, int flags)
+{
+       struct sla_station *station = obj, *station2 = arg;
 
-       AST_RWLIST_WRLOCK(&sla_stations);
-       while ((station = AST_RWLIST_REMOVE_HEAD(&sla_stations, entry)))
-               destroy_station(station);
-       AST_RWLIST_UNLOCK(&sla_stations);
+       return !strcasecmp(station->name, station2->name) ? CMP_MATCH | CMP_STOP : 0;
+}
 
+static void sla_destroy(void)
+{
        if (sla.thread != AST_PTHREADT_NULL) {
                ast_mutex_lock(&sla.lock);
                sla.stop = 1;
@@ -6673,6 +7430,15 @@ static void sla_destroy(void)
 
        ast_mutex_destroy(&sla.lock);
        ast_cond_destroy(&sla.cond);
+
+       ao2_callback(sla_trunks, 0, sla_trunk_release_refs, NULL);
+       ao2_callback(sla_stations, 0, sla_station_release_refs, NULL);
+
+       ao2_ref(sla_trunks, -1);
+       sla_trunks = NULL;
+
+       ao2_ref(sla_stations, -1);
+       sla_stations = NULL;
 }
 
 static int sla_check_device(const char *device)
@@ -6688,11 +7454,27 @@ static int sla_check_device(const char *device)
        return 0;
 }
 
+static void sla_trunk_destructor(void *obj)
+{
+       struct sla_trunk *trunk = obj;
+
+       ast_debug(1, "sla_trunk destructor for '%s'\n", trunk->name);
+
+       if (!ast_strlen_zero(trunk->autocontext)) {
+               ast_context_remove_extension(trunk->autocontext, "s", 1, sla_registrar);
+       }
+
+       sla_trunk_release_refs(trunk, NULL, 0);
+
+       ast_string_field_free_memory(trunk);
+}
+
 static int sla_build_trunk(struct ast_config *cfg, const char *cat)
 {
-       struct sla_trunk *trunk;
+       RAII_VAR(struct sla_trunk *, trunk, NULL, ao2_cleanup);
        struct ast_variable *var;
        const char *dev;
+       int existing_trunk = 0;
 
        if (!(dev = ast_variable_retrieve(cfg, cat, "device"))) {
                ast_log(LOG_ERROR, "SLA Trunk '%s' defined with no device!\n", cat);
@@ -6700,16 +7482,25 @@ static int sla_build_trunk(struct ast_config *cfg, const char *cat)
        }
 
        if (sla_check_device(dev)) {
-               ast_log(LOG_ERROR, "SLA Trunk '%s' define with invalid device '%s'!\n",
+               ast_log(LOG_ERROR, "SLA Trunk '%s' defined with invalid device '%s'!\n",
                        cat, dev);
                return -1;
        }
 
-       if (!(trunk = ast_calloc_with_stringfields(1, struct sla_trunk, 32))) {
+       if ((trunk = sla_find_trunk(cat))) {
+               trunk->mark = 0;
+               existing_trunk = 1;
+       } else if ((trunk = ao2_alloc(sizeof(*trunk), sla_trunk_destructor))) {
+               if (ast_string_field_init(trunk, 32)) {
+                       return -1;
+               }