app_queue: Cleanup queue_ref / queue_unref routines.
[asterisk/asterisk.git] / apps / app_meetme.c
index 46a42a5..40c0bd2 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"
@@ -139,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)
@@ -147,7 +166,8 @@ 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.  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>
@@ -189,7 +209,7 @@ 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
@@ -535,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
@@ -592,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),
@@ -620,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 */
@@ -638,14 +741,16 @@ enum {
        CONFFLAG_DURATION_LIMIT = (1 << 30),
 };
 
-/* These flags are defined separately because we ran out of bits that an enum can be used to represent. 
+/* 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 ((uint64_t)1 << 34)
+#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,
@@ -676,6 +781,7 @@ AST_APP_OPTIONS(meetme_opts, BEGIN_OPTIONS
        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 ),
@@ -836,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;
@@ -853,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 */
@@ -887,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;
@@ -904,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";
 
@@ -919,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 {
@@ -932,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;
@@ -978,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,
 };
@@ -988,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
@@ -1009,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);
@@ -1019,7 +1401,7 @@ static const char *istalking(int x)
                return "(talking)";
        else if (x < 0)
                return "(unmonitored)";
-       else 
+       else
                return "(not talking)";
 }
 
@@ -1078,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:
@@ -1147,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);
 
@@ -1171,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);
 }
 
@@ -1209,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.
@@ -1220,20 +1610,19 @@ 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))) {
@@ -1279,10 +1668,10 @@ 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;
@@ -1316,9 +1705,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);
 
@@ -1327,71 +1716,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;
 }
 
@@ -1401,37 +1850,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)) {
@@ -1439,23 +1882,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,
@@ -1472,18 +1917,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);
@@ -1496,7 +1942,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 */
@@ -1516,7 +1961,7 @@ static char *meetme_show_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                                        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,
@@ -1536,93 +1981,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 {
@@ -1630,6 +2031,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;
        }
@@ -1642,49 +2047,115 @@ 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);
        }
 
-       ast_cli(a->fd, "\n"
-                   "=============================================================\n"
-                   "=== Configured SLA Trunks ===================================\n"
-                   "=============================================================\n"
-                   "===\n");
-       AST_RWLIST_RDLOCK(&sla_trunks);
-       AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
-               struct sla_station_ref *station_ref;
+       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"
+                   "=============================================================\n"
+                   "=== Configured SLA Trunks ===================================\n"
+                   "=============================================================\n"
+                   "===\n");
+       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"
@@ -1693,18 +2164,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;
@@ -1726,7 +2200,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:
@@ -1739,22 +2214,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"
@@ -1766,34 +2244,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");
 
@@ -1801,8 +2282,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"),
 };
@@ -1820,7 +2303,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);
@@ -1843,9 +2326,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;
@@ -1872,7 +2356,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){
@@ -1885,10 +2369,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) {
@@ -1928,17 +2410,22 @@ static void conf_queue_dtmf(const struct ast_conference *conf,
        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;
@@ -1972,6 +2459,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, "_");
@@ -1980,16 +2468,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");
@@ -2179,13 +2674,8 @@ static int can_write(struct ast_channel *chan, struct ast_flags64 *confflags)
 
 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",
-             ast_channel_name(chan), ast_channel_uniqueid(chan), 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)
@@ -2250,6 +2740,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;
@@ -2270,8 +3188,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;
@@ -2281,13 +3198,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];
@@ -2306,13 +3223,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;
@@ -2328,7 +3244,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])) {
@@ -2407,7 +3323,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;
@@ -2433,9 +3349,9 @@ 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;
@@ -2459,29 +3375,29 @@ 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! */     
+               /* Sorry, but this conference is locked! */
                if (!ast_streamfile(chan, "conf-locked", ast_channel_language(chan)))
                        ast_waitstream(chan, "");
                goto outrun;
@@ -2491,10 +3407,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! */      
+                       /* 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, "");
-                       ast_mutex_unlock(&conf->playlock);
                        goto outrun;
                }
        }
@@ -2581,7 +3497,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);
 
@@ -2620,7 +3536,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        if (ast_test_flag64(confflags, CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
                int keepplaying = 1;
 
-               if (conf->users == 2) { 
+               if (conf->users == 2) {
                        if (!ast_streamfile(chan, "conf-onlyone", ast_channel_language(chan))) {
                                res = ast_waitstream(chan, AST_DIGIT_ANY);
                                ast_stopstream(chan);
@@ -2629,7 +3545,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                else if (res == -1)
                                        goto outrun;
                        }
-               } else { 
+               } else {
                        if (!ast_streamfile(chan, "conf-thereare", ast_channel_language(chan))) {
                                res = ast_waitstream(chan, AST_DIGIT_ANY);
                                ast_stopstream(chan);
@@ -2650,7 +3566,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                ast_stopstream(chan);
                                if (res > 0)
                                        keepplaying = 0;
-                               else if (res == -1) 
+                               else if (res == -1)
                                        goto outrun;
                        }
                }
@@ -2661,19 +3577,18 @@ 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) {
+       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) {
+       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");
        }
 
@@ -2777,22 +3692,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
        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",
-                       ast_channel_name(chan), ast_channel_uniqueid(chan), conf->confno,
-                       user->user_no,
-                       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, "<unknown>"),
-                       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, "<unknown>")
-                       );
+               meetme_stasis_generate_msg(conf, chan, user, meetme_join_type(), NULL);
                sent_event = 1;
        }
 
@@ -2827,7 +3727,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;
@@ -2847,11 +3747,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;
 
@@ -2930,11 +3832,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;
@@ -2943,9 +3845,9 @@ 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) {                                          
+                                       if (user->end_sound) {
                                                res = ast_streamfile(chan, user->end_sound, ast_channel_language(chan));
                                                res = ast_waitstream(chan, "");
                                        }
@@ -2956,10 +3858,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;
@@ -2967,11 +3869,11 @@ 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", ast_channel_language(chan));
                                                                res = ast_waitstream(chan, "");
                                                                if (minutes) {
@@ -3014,11 +3916,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) &&
@@ -3092,7 +3994,7 @@ 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", ast_channel_language(chan))) {
                                                        ast_waitstream(chan, "");
@@ -3108,7 +4010,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);
@@ -3116,7 +4018,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)) {
@@ -3126,11 +4028,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");
@@ -3142,58 +4053,34 @@ 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",
-                                               ast_channel_name(chan), ast_channel_uniqueid(chan), 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",
-                                               ast_channel_name(chan), ast_channel_uniqueid(chan), 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",
-                                                             ast_channel_name(chan), ast_channel_uniqueid(chan), 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",
-                                                            ast_channel_name(chan), ast_channel_uniqueid(chan), 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 */
@@ -3205,7 +4092,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                        /* If I have been kicked, exit the conference */
                        if (user->adminflags & ADMINFLAG_KICKME) {
                                /* You have been kicked. */
-                               if (!ast_test_flag64(confflags, CONFFLAG_QUIET) && 
+                               if (!ast_test_flag64(confflags, CONFFLAG_QUIET) &&
                                        !ast_streamfile(chan, "conf-kicked", ast_channel_language(chan))) {
                                        ast_waitstream(chan, "");
                                }
@@ -3213,6 +4100,11 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                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) {
@@ -3242,7 +4134,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);
                                        }
@@ -3268,372 +4160,67 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                   The buffering in the pseudo channel will take care of any
                                                   timing differences, unless they are so drastic as to lose
                                                   audio frames (in which case carefully writing would only
-                                                  have delayed the audio even further).
-                                               */
-                                               /* As it turns out, we do want to use careful write.  We just
-                                                  don't want to block, but we do want to at least *try*
-                                                  to write out all the samples.
-                                                */
-                                               if (user->talking || !ast_test_flag64(confflags, CONFFLAG_OPTIMIZETALKER)) {
-                                                       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)) {
-                                       if (ast_test_flag64(confflags, CONFFLAG_PASS_DTMF)) {
-                                               conf_queue_dtmf(conf, user, f);
-                                       }
-                                       if (ioctl(fd, DAHDI_SETCONF, &dahdic_empty)) {
-                                               ast_log(LOG_WARNING, "Error setting conference\n");
-                                               close(fd);
-                                               ast_frfree(f);
-                                               goto outrun;
-                                       }
-
-                                       /* 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", 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);
-                                                                               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;
-                                                               }
-                                                               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);
-                                                               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);
-                                                               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, 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, 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(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);
-                                                               break;
-                                                       default:
-                                                               if (!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan)))
-                                                                       ast_waitstream(chan, "");
-                                                               ast_stopstream(chan);
-                                                               break;
-                                                       }
-                                               }
-
-                                               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", ast_channel_language(chan))) {
-                                                               dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                               ast_stopstream(chan);
-                                                       } else {
-                                                               dtmf = 0;
-                                                       }
-                                               } else {
-                                                       dtmf = f->subclass.integer;
-                                               }
-                                               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", 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_active = 0;
-                                                               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_active = 0;
-                                                               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_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", ast_channel_language(chan))) {
-                                                                       ast_waitstream(chan, "");
-                                                               }
-                                                               break;
-                                                       }
+                                                  have delayed the audio even further).
+                                               */
+                                               /* As it turns out, we do want to use careful write.  We just
+                                                  don't want to block, but we do want to at least *try*
+                                                  to write out all the samples.
+                                                */
+                                               if (user->talking || !ast_test_flag64(confflags, CONFFLAG_OPTIMIZETALKER)) {
+                                                       careful_write(fd, f->data.ptr, f->datalen, 0);
                                                }
-                                       } else {
-                                               /* User menu */
-                                               if (!menu_active) {
-                                                       menu_active = 1;
-                                                       if (!ast_streamfile(chan, "conf-usermenu-162", ast_channel_language(chan))) {
-                                                               dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
-                                                               ast_stopstream(chan);
-                                                       } else {
-                                                               dtmf = 0;
-                                                       }
+                                       }
+                               } 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);
+                                               ast_frfree(f);
+                                               goto outrun;
+                                       }
+
+                                       /* if we are entering the menu, and the user has a channel-driver
+                                          volume adjustment, clear it
+                                       */
+                                       if (!menu_mode && user->talk.desired && !user->talk.actual) {
+                                               set_talk_volume(user, 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;
-
-                                                               /* 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_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", 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_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", ast_channel_language(chan))) {
-                                                                       ast_waitstream(chan, "");
-                                                               }
-                                                               break;
-                                                       }
+
+                                               if (!ast_streamfile(chan, menu_to_play, ast_channel_language(chan))) {
+                                                       dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
+                                                       ast_stopstream(chan);
+                                               } else {
+                                                       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);
@@ -3691,12 +4278,12 @@ 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",
+                                                       "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",
+                                               "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);
@@ -3705,7 +4292,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;
@@ -3717,7 +4304,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(ast_channel_rawwriteformat(chan)) & (1 << idx)) {
+                                                       if (ast_format_compatibility_format2bitfield(ast_channel_rawwriteformat(chan)) & (1 << idx)) {
                                                                break;
                                                        }
                                                }
@@ -3727,16 +4314,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);
@@ -3761,7 +4347,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, struc
                                                                }
                                                                if (musiconhold && mohtempstopped && confsilence > MEETME_DELAYDETECTENDTALK) {
                                                                        mohtempstopped = 0;
-                                                                       ast_moh_start(chan, NULL, NULL);
+                                                                       conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
                                                                }
                                                        }
                                                } else {
@@ -3771,7 +4357,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;
                                                }
@@ -3783,7 +4372,7 @@ bailoutandtrynormal:
                                                }
                                                if (musiconhold && mohtempstopped && confsilence > MEETME_DELAYDETECTENDTALK) {
                                                        mohtempstopped = 0;
-                                                       ast_moh_start(chan, NULL, NULL);
+                                                       conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]);
                                                }
                                        }
                                } else {
@@ -3797,12 +4386,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)) {
@@ -3844,29 +4433,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",
-                               ast_channel_name(chan), ast_channel_uniqueid(chan), conf->confno,
-                               user->user_no,
-                               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, "<unknown>"),
-                               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, "<unknown>"),
-                               (long)(now.tv_sec - user->jointime));
+                       meetme_stasis_generate_msg(conf, chan, user, meetme_leave_type(), NULL);
                }
 
                if (setusercount) {
@@ -3885,11 +4458,11 @@ 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.  */
@@ -3911,7 +4484,7 @@ bailoutandtrynormal:
 
 
 conf_run_cleanup:
-       cap_slin = ast_format_cap_destroy(cap_slin);
+       ao2_cleanup(cap_slin);
 
        return ret;
 }
@@ -3945,7 +4518,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;
@@ -4088,7 +4661,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);
                }
@@ -4196,7 +4769,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");
@@ -4214,7 +4787,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);
@@ -4224,12 +4797,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) {
@@ -4281,13 +4853,13 @@ static int conf_exec(struct ast_channel *chan, const char *data)
        } else {
                notdata = data;
        }
-       
+
        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));
@@ -4295,7 +4867,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));
 
@@ -4341,6 +4913,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)) {
@@ -4412,6 +4985,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
                        /* Not found? */
                        if (ast_strlen_zero(confno)) {
                                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 {
@@ -4441,12 +5015,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;
@@ -4492,6 +5066,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) {
@@ -4551,7 +5128,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)) {
@@ -4570,7 +5147,7 @@ static int conf_exec(struct ast_channel *chan, const char *data)
 
        if (cnf)
                dispose_conf(cnf);
-       
+
        return res;
 }
 
@@ -4578,9 +5155,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;
@@ -4635,7 +5211,7 @@ static int user_chan_cb(void *obj, void *args, int flags)
        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) {
@@ -4686,13 +5262,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 */
@@ -4701,30 +5294,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, &cnf);
-               break;                                  
-       case 109: /* m: Unmute */ 
+               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 */
@@ -4777,7 +5377,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;
@@ -4792,7 +5392,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);
 
@@ -4812,22 +5412,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 */
@@ -4836,7 +5436,7 @@ static int channel_admin_exec(struct ast_channel *chan, const char *data) {
        }
        ao2_ref(user, -1);
        AST_LIST_UNLOCK(&confs);
-       
+
        return 0;
 }
 
@@ -4975,13 +5575,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;
 }
 
@@ -5038,20 +5635,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;
@@ -5061,11 +5679,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;
 
@@ -5077,11 +5699,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;
@@ -5112,7 +5734,7 @@ static void *recordthread(void *args)
        cnf->recording = MEETME_RECORD_OFF;
        if (s)
                ast_closestream(s);
-       
+
        pthread_exit(0);
 }
 
@@ -5139,12 +5761,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) {
@@ -5152,17 +5794,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)) {
@@ -5185,58 +5819,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,
@@ -5260,9 +5890,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.
@@ -5276,12 +5908,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;
                }
@@ -5289,16 +5921,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;
@@ -5311,12 +5959,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) {
@@ -5334,23 +6018,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 {
@@ -5368,8 +6059,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;
@@ -5386,7 +6077,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);
@@ -5412,6 +6103,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];
@@ -5421,10 +6114,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,
@@ -5459,7 +6153,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)
@@ -5491,7 +6185,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;
@@ -5511,13 +6205,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;
        }
@@ -5530,7 +6226,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;
@@ -5554,7 +6250,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 */
@@ -5565,12 +6270,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);
@@ -5596,8 +6303,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)
 {
@@ -5624,7 +6331,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;
@@ -5653,7 +6360,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;
        }
@@ -5667,21 +6374,19 @@ static int sla_ring_station(struct sla_ringing_trunk *ringing_trunk, struct sla_
        }
 
        res = ast_dial_run(dial, ringing_trunk->trunk->chan, 1);
-       
+
        /* Restore saved caller ID */
        if (caller_is_saved) {
                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))) {
@@ -5721,6 +6426,8 @@ static struct sla_trunk_ref *sla_find_trunk_ref(const struct sla_station *statio
                        break;
        }
 
+       ao2_ref(trunk_ref, 1);
+
        return trunk_ref;
 }
 
@@ -5729,10 +6436,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;
 
@@ -5825,7 +6532,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
@@ -5845,9 +6552,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) {
@@ -5982,8 +6689,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;
 
@@ -6003,7 +6712,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;
@@ -6013,6 +6722,7 @@ static int sla_calc_station_delays(unsigned int *timeout)
                if (time_left < *timeout)
                        *timeout = time_left;
        }
+       ao2_iterator_destroy(&i);
 
        return res;
 }
@@ -6054,49 +6764,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;
+       if (event->trunk_ref) {
+               ao2_ref(event->trunk_ref, -1);
+               event->trunk_ref = NULL;
        }
 
-       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;
-       }
-
-       AST_RWLIST_RDLOCK(&sla_trunks);
-       AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
-               if (trunk->ref_count)
-                       break;
+       if (event->station) {
+               ao2_ref(event->station, -1);
+               event->station = NULL;
        }
-       AST_RWLIST_UNLOCK(&sla_trunks);
-       if (trunk) {
-               ast_mutex_unlock(&sla.lock);
-               return;
-       }
-
-       /* yay */
-       sla_load_config(1);
-       sla.reload = 0;
 
-       ast_mutex_unlock(&sla.lock);
+       ast_free(event);
 }
 
 static void *sla_thread(void *data)
@@ -6135,27 +6815,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;
 }
@@ -6176,9 +6850,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);
@@ -6189,7 +6866,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);
@@ -6232,14 +6909,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) {
@@ -6252,8 +6950,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);
 
@@ -6279,15 +6977,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;
@@ -6296,8 +6998,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;
@@ -6317,25 +7019,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))
@@ -6345,8 +7043,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;
        }
 
@@ -6355,8 +7051,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;
@@ -6375,7 +7071,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);
@@ -6395,6 +7091,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
@@ -6410,12 +7108,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;
                }
        }
@@ -6445,22 +7141,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;
@@ -6470,9 +7175,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();
 
@@ -6484,7 +7191,17 @@ static struct sla_ringing_trunk *queue_ringing_trunk(struct sla_trunk *trunk)
 
        sla_queue_event(SLA_EVENT_RINGING_TRUNK);
 
-       return ringing_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 {
@@ -6505,7 +7222,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);
@@ -6529,16 +7246,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;
        }
 
@@ -6546,8 +7258,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;
        }
 
@@ -6555,8 +7265,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;
        }
 
@@ -6564,11 +7272,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)) {
@@ -6599,46 +7305,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",
@@ -6648,61 +7345,86 @@ 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_hash(const void *obj, const int flags)
 {
-       struct sla_trunk *trunk;
-       struct sla_station *station;
+       const struct sla_trunk *trunk = obj;
+
+       return ast_str_case_hash(trunk->name);
+}
+
+static int sla_trunk_cmp(void *obj, void *arg, int flags)
+{
+       struct sla_trunk *trunk = obj, *trunk2 = arg;
+
+       return !strcasecmp(trunk->name, trunk2->name) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int sla_station_hash(const void *obj, const int flags)
+{
+       const struct sla_station *station = obj;
+
+       return ast_str_case_hash(station->name);
+}
 
-       AST_RWLIST_WRLOCK(&sla_trunks);
-       while ((trunk = AST_RWLIST_REMOVE_HEAD(&sla_trunks, entry)))
-               destroy_trunk(trunk);
-       AST_RWLIST_UNLOCK(&sla_trunks);
+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;
@@ -6716,6 +7438,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)
@@ -6731,11 +7462,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);
@@ -6743,16 +7490,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;
+               }
+               ast_string_field_set(trunk, name, cat);
+       } else {
                return -1;
        }
 
-       ast_string_field_set(trunk, name, cat);
+       ao2_lock(trunk);
+
        ast_string_field_set(trunk, device, dev);
 
        for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
@@ -6781,54 +7537,64 @@ static int sla_build_trunk(struct ast_config *cfg, const char *cat)
                }
        }
 
+       ao2_unlock(trunk);
+
        if (!ast_strlen_zero(trunk->autocontext)) {
-               struct ast_context *context;
-               context = ast_context_find_or_create(NULL, NULL, trunk->autocontext, sla_registrar);
-               if (!context) {
+               if (!ast_context_find_or_create(NULL, NULL, trunk->autocontext, sla_registrar)) {
                        ast_log(LOG_ERROR, "Failed to automatically find or create "
                                "context '%s' for SLA!\n", trunk->autocontext);
-                       destroy_trunk(trunk);
                        return -1;
                }
-               if (ast_add_extension2(context, 0 /* don't replace */, "s", 1,
+
+               if (ast_add_extension(trunk->autocontext, 0 /* don't replace */, "s", 1,
                        NULL, NULL, slatrunk_app, ast_strdup(trunk->name), ast_free_ptr, sla_registrar)) {
                        ast_log(LOG_ERROR, "Failed to automatically create extension "
                                "for trunk '%s'!\n", trunk->name);
-                       destroy_trunk(trunk);
                        return -1;
                }
        }
 
-       AST_RWLIST_WRLOCK(&sla_trunks);
-       AST_RWLIST_INSERT_TAIL(&sla_trunks, trunk, entry);
-       AST_RWLIST_UNLOCK(&sla_trunks);
+       if (!existing_trunk) {
+               ao2_link(sla_trunks, trunk);
+       }
 
        return 0;
 }
 
+/*!
+ * \internal
+ * \pre station is not locked
+ */
 static void sla_add_trunk_to_station(struct sla_station *station, struct ast_variable *var)
 {
-       struct sla_trunk *trunk;
-       struct sla_trunk_ref *trunk_ref;
+       RAII_VAR(struct sla_trunk *, trunk, NULL, ao2_cleanup);
+       struct sla_trunk_ref *trunk_ref = NULL;
        struct sla_station_ref *station_ref;
        char *trunk_name, *options, *cur;
+       int existing_trunk_ref = 0;
+       int existing_station_ref = 0;
 
        options = ast_strdupa(var->value);
        trunk_name = strsep(&options, ",");
-       
-       AST_RWLIST_RDLOCK(&sla_trunks);
-       AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
-               if (!strcasecmp(trunk->name, trunk_name))
-                       break;
-       }
 
-       AST_RWLIST_UNLOCK(&sla_trunks);
+       trunk = sla_find_trunk(trunk_name);
        if (!trunk) {
                ast_log(LOG_ERROR, "Trunk '%s' not found!\n", var->value);
                return;
        }
-       if (!(trunk_ref = create_trunk_ref(trunk)))
+
+       AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+               if (trunk_ref->trunk == trunk) {
+                       trunk_ref->mark = 0;
+                       existing_trunk_ref = 1;
+                       break;
+               }
+       }
+
+       if (!trunk_ref && !(trunk_ref = create_trunk_ref(trunk))) {
                return;
+       }
+
        trunk_ref->state = SLA_TRUNK_STATE_IDLE;
 
        while ((cur = strsep(&options, ","))) {
@@ -6852,41 +7618,73 @@ static void sla_add_trunk_to_station(struct sla_station *station, struct ast_var
                }
        }
 
-       if (!(station_ref = sla_create_station_ref(station))) {
-               ast_free(trunk_ref);
+       AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) {
+               if (station_ref->station == station) {
+                       station_ref->mark = 0;
+                       existing_station_ref = 1;
+                       break;
+               }
+       }
+
+       if (!station_ref && !(station_ref = sla_create_station_ref(station))) {
+               if (!existing_trunk_ref) {
+                       ao2_ref(trunk_ref, -1);
+               } else {
+                       trunk_ref->mark = 1;
+               }
                return;
        }
-       ast_atomic_fetchadd_int((int *) &trunk->num_stations, 1);
-       AST_RWLIST_WRLOCK(&sla_trunks);
-       AST_LIST_INSERT_TAIL(&trunk->stations, station_ref, entry);
-       AST_RWLIST_UNLOCK(&sla_trunks);
-       AST_LIST_INSERT_TAIL(&station->trunks, trunk_ref, entry);
+
+       if (!existing_station_ref) {
+               ao2_lock(trunk);
+               AST_LIST_INSERT_TAIL(&trunk->stations, station_ref, entry);
+               ast_atomic_fetchadd_int((int *) &trunk->num_stations, 1);
+               ao2_unlock(trunk);
+       }
+
+       if (!existing_trunk_ref) {
+               ao2_lock(station);
+               AST_LIST_INSERT_TAIL(&station->trunks, trunk_ref, entry);
+               ao2_unlock(station);
+       }
 }
 
 static int sla_build_station(struct ast_config *cfg, const char *cat)
 {
-       struct sla_station *station;
+       RAII_VAR(struct sla_station *, station, NULL, ao2_cleanup);
        struct ast_variable *var;
        const char *dev;
+       int existing_station = 0;
 
        if (!(dev = ast_variable_retrieve(cfg, cat, "device"))) {
                ast_log(LOG_ERROR, "SLA Station '%s' defined with no device!\n", cat);
                return -1;
        }
 
-       if (!(station = ast_calloc_with_stringfields(1, struct sla_station, 32))) {
+       if ((station = sla_find_station(cat))) {
+               station->mark = 0;
+               existing_station = 1;
+       } else if ((station = ao2_alloc(sizeof(*station), sla_station_destructor))) {
+               if (ast_string_field_init(station, 32)) {
+                       return -1;
+               }
+               ast_string_field_set(station, name, cat);
+       } else {
                return -1;
        }
 
-       ast_string_field_set(station, name, cat);
+       ao2_lock(station);
+
        ast_string_field_set(station, device, dev);
 
        for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
-               if (!strcasecmp(var->name, "trunk"))
+               if (!strcasecmp(var->name, "trunk")) {
+                       ao2_unlock(station);
                        sla_add_trunk_to_station(station, var);
-               else if (!strcasecmp(var->name, "autocontext"))
+                       ao2_lock(station);
+               } else if (!strcasecmp(var->name, "autocontext")) {
                        ast_string_field_set(station, autocontext, var->value);
-               else if (!strcasecmp(var->name, "ringtimeout")) {
+               } else if (!strcasecmp(var->name, "ringtimeout")) {
                        if (sscanf(var->value, "%30u", &station->ring_timeout) != 1) {
                                ast_log(LOG_WARNING, "Invalid ringtimeout '%s' specified for station '%s'\n",
                                        var->value, station->name);
@@ -6914,60 +7712,152 @@ static int sla_build_station(struct ast_config *cfg, const char *cat)
                }
        }
 
+       ao2_unlock(station);
+
        if (!ast_strlen_zero(station->autocontext)) {
-               struct ast_context *context;
                struct sla_trunk_ref *trunk_ref;
-               context = ast_context_find_or_create(NULL, NULL, station->autocontext, sla_registrar);
-               if (!context) {
+
+               if (!ast_context_find_or_create(NULL, NULL, station->autocontext, sla_registrar)) {
                        ast_log(LOG_ERROR, "Failed to automatically find or create "
                                "context '%s' for SLA!\n", station->autocontext);
-                       destroy_station(station);
                        return -1;
                }
                /* The extension for when the handset goes off-hook.
                 * exten => station1,1,SLAStation(station1) */
-               if (ast_add_extension2(context, 0 /* don't replace */, station->name, 1,
+               if (ast_add_extension(station->autocontext, 0 /* don't replace */, station->name, 1,
                        NULL, NULL, slastation_app, ast_strdup(station->name), ast_free_ptr, sla_registrar)) {
                        ast_log(LOG_ERROR, "Failed to automatically create extension "
                                "for trunk '%s'!\n", station->name);
-                       destroy_station(station);
                        return -1;
                }
-               AST_RWLIST_RDLOCK(&sla_trunks);
                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);
-                       /* Extension for this line button 
+                       /* Extension for this line button
                         * exten => station1_line1,1,SLAStation(station1_line1) */
-                       if (ast_add_extension2(context, 0 /* don't replace */, exten, 1,
+                       if (ast_add_extension(station->autocontext, 0 /* don't replace */, exten, 1,
                                NULL, NULL, slastation_app, ast_strdup(exten), ast_free_ptr, sla_registrar)) {
                                ast_log(LOG_ERROR, "Failed to automatically create extension "
                                        "for trunk '%s'!\n", station->name);
-                               destroy_station(station);
                                return -1;
                        }
-                       /* Hint for this line button 
+                       /* Hint for this line button
                         * exten => station1_line1,hint,SLA:station1_line1 */
-                       if (ast_add_extension2(context, 0 /* don't replace */, exten, PRIORITY_HINT,
+                       if (ast_add_extension(station->autocontext, 0 /* don't replace */, exten, PRIORITY_HINT,
                                NULL, NULL, hint, NULL, NULL, sla_registrar)) {
                                ast_log(LOG_ERROR, "Failed to automatically create hint "
                                        "for trunk '%s'!\n", station->name);
-                               destroy_station(station);
                                return -1;
                        }
                }
-               AST_RWLIST_UNLOCK(&sla_trunks);
        }
 
-       AST_RWLIST_WRLOCK(&sla_stations);
-       AST_RWLIST_INSERT_TAIL(&sla_stations, station, entry);
-       AST_RWLIST_UNLOCK(&sla_stations);
+       if (!existing_station) {
+               ao2_link(sla_stations, station);
+       }
+
+       return 0;
+}
+
+static int sla_trunk_mark(void *obj, void *arg, int flags)
+{
+       struct sla_trunk *trunk = obj;
+       struct sla_station_ref *station_ref;
+
+       ao2_lock(trunk);
+
+       trunk->mark = 1;
+
+       AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) {
+               station_ref->mark = 1;
+       }
+
+       ao2_unlock(trunk);
+
+       return 0;
+}
+
+static int sla_station_mark(void *obj, void *arg, int flags)
+{
+       struct sla_station *station = obj;
+       struct sla_trunk_ref *trunk_ref;
+
+       ao2_lock(station);
+
+       station->mark = 1;
+
+       AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+               trunk_ref->mark = 1;
+       }
+
+       ao2_unlock(station);
 
        return 0;
 }
 
+static int sla_trunk_is_marked(void *obj, void *arg, int flags)
+{
+       struct sla_trunk *trunk = obj;
+
+       ao2_lock(trunk);
+
+       if (trunk->mark) {
+               /* Only remove all of the station references if the trunk itself is going away */
+               sla_trunk_release_refs(trunk, NULL, 0);
+       } else {
+               struct sla_station_ref *station_ref;
+
+               /* Otherwise only remove references to stations no longer in the config */
+               AST_LIST_TRAVERSE_SAFE_BEGIN(&trunk->stations, station_ref, entry) {
+                       if (!station_ref->mark) {
+                               continue;
+                       }
+                       AST_LIST_REMOVE_CURRENT(entry);
+                       ao2_ref(station_ref, -1);
+               }
+               AST_LIST_TRAVERSE_SAFE_END
+       }
+
+       ao2_unlock(trunk);
+
+       return trunk->mark ? CMP_MATCH : 0;
+}
+
+static int sla_station_is_marked(void *obj, void *arg, int flags)
+{
+       struct sla_station *station = obj;
+
+       ao2_lock(station);
+
+       if (station->mark) {
+               /* Only remove all of the trunk references if the station itself is going away */
+               sla_station_release_refs(station, NULL, 0);
+       } else {
+               struct sla_trunk_ref *trunk_ref;
+
+               /* Otherwise only remove references to trunks no longer in the config */
+               AST_LIST_TRAVERSE_SAFE_BEGIN(&station->trunks, trunk_ref, entry) {
+                       if (!trunk_ref->mark) {
+                               continue;
+                       }
+                       AST_LIST_REMOVE_CURRENT(entry);
+                       ao2_ref(trunk_ref, -1);
+               }
+               AST_LIST_TRAVERSE_SAFE_END
+       }
+
+       ao2_unlock(station);
+
+       return station->mark ? CMP_MATCH : 0;
+}
+
+static int sla_in_use(void)
+{
+       return ao2_container_count(sla_trunks) || ao2_container_count(sla_stations);
+}
+
 static int sla_load_config(int reload)
 {
        struct ast_config *cfg;
@@ -6979,6 +7869,8 @@ static int sla_load_config(int reload)
        if (!reload) {
                ast_mutex_init(&sla.lock);
                ast_cond_init(&sla.cond, NULL);
+               sla_trunks = ao2_container_alloc(1, sla_trunk_hash, sla_trunk_cmp);
+               sla_stations = ao2_container_alloc(1, sla_station_hash, sla_station_cmp);
        }
 
        if (!(cfg = ast_config_load(SLA_CONFIG_FILE, config_flags))) {
@@ -6991,21 +7883,8 @@ static int sla_load_config(int reload)
        }
 
        if (reload) {
-               struct sla_station *station;
-               struct sla_trunk *trunk;
-
-               /* 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);
-               }
-               AST_RWLIST_TRAVERSE_SAFE_END;
+               ao2_callback(sla_trunks, 0, sla_trunk_mark, NULL);
+               ao2_callback(sla_stations, 0, sla_station_mark, NULL);
        }
 
        if ((val = ast_variable_retrieve(cfg, "general", "attemptcallerid")))
@@ -7032,9 +7911,13 @@ static int sla_load_config(int reload)
 
        ast_config_destroy(cfg);
 
-       /* Even if we don't have any stations, we may after a reload and we need to
-        * be able to process the SLA_EVENT_RELOAD event in that case */
-       if (sla.thread == AST_PTHREADT_NULL && (!AST_LIST_EMPTY(&sla_stations) || !AST_LIST_EMPTY(&sla_trunks))) {
+       if (reload) {
+               ao2_callback(sla_trunks, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sla_trunk_is_marked, NULL);
+               ao2_callback(sla_stations, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sla_station_is_marked, NULL);
+       }
+
+       /* Start SLA event processing thread once SLA has been configured. */
+       if (sla.thread == AST_PTHREADT_NULL && sla_in_use()) {
                ast_pthread_create(&sla.thread, NULL, sla_thread, NULL);
        }
 
@@ -7102,7 +7985,7 @@ static int acf_meetme_info(struct ast_channel *chan, const char *cmd, char *data
                ast_log(LOG_NOTICE, "Error: invalid keyword: '%s'\n", args.keyword);
                snprintf(buf, len, "0");
        } else if (result == -2) {
-               ast_log(LOG_NOTICE, "Error: conference (%s) not found\n", args.confno); 
+               ast_log(LOG_NOTICE, "Error: conference (%s) not found\n", args.confno);
                snprintf(buf, len, "0");
        }
 
@@ -7118,200 +8001,14 @@ static struct ast_custom_function meetme_info_acf = {
 
 static int load_config(int reload)
 {
-       load_config_meetme();
-
-       if (reload && sla.thread != AST_PTHREADT_NULL) {
-               sla_queue_event(SLA_EVENT_RELOAD);
-               ast_log(LOG_NOTICE, "A reload of the SLA configuration has been requested "
-                       "and will be completed when the system is idle.\n");
-               return 0;
-       }
-       
-       return sla_load_config(0);
-}
-
-#define MEETME_DATA_EXPORT(MEMBER)                                     \
-       MEMBER(ast_conference, confno, AST_DATA_STRING)                 \
-       MEMBER(ast_conference, dahdiconf, AST_DATA_INTEGER)             \
-       MEMBER(ast_conference, users, AST_DATA_INTEGER)                 \
-       MEMBER(ast_conference, markedusers, AST_DATA_INTEGER)           \
-       MEMBER(ast_conference, maxusers, AST_DATA_INTEGER)              \
-       MEMBER(ast_conference, isdynamic, AST_DATA_BOOLEAN)             \
-       MEMBER(ast_conference, locked, AST_DATA_BOOLEAN)                \
-       MEMBER(ast_conference, recordingfilename, AST_DATA_STRING)      \
-       MEMBER(ast_conference, recordingformat, AST_DATA_STRING)        \
-       MEMBER(ast_conference, pin, AST_DATA_PASSWORD)                  \
-       MEMBER(ast_conference, pinadmin, AST_DATA_PASSWORD)             \
-       MEMBER(ast_conference, start, AST_DATA_TIMESTAMP)               \
-       MEMBER(ast_conference, endtime, AST_DATA_TIMESTAMP)
-
-AST_DATA_STRUCTURE(ast_conference, MEETME_DATA_EXPORT);
-
-#define MEETME_USER_DATA_EXPORT(MEMBER)                                        \
-       MEMBER(ast_conf_user, user_no, AST_DATA_INTEGER)                \
-       MEMBER(ast_conf_user, talking, AST_DATA_BOOLEAN)                \
-       MEMBER(ast_conf_user, dahdichannel, AST_DATA_BOOLEAN)           \
-       MEMBER(ast_conf_user, jointime, AST_DATA_TIMESTAMP)             \
-       MEMBER(ast_conf_user, kicktime, AST_DATA_TIMESTAMP)             \
-       MEMBER(ast_conf_user, timelimit, AST_DATA_MILLISECONDS)         \
-       MEMBER(ast_conf_user, play_warning, AST_DATA_MILLISECONDS)      \
-       MEMBER(ast_conf_user, warning_freq, AST_DATA_MILLISECONDS)
-
-AST_DATA_STRUCTURE(ast_conf_user, MEETME_USER_DATA_EXPORT);
-
-static int user_add_provider_cb(void *obj, void *arg, int flags)
-{
-       struct ast_data *data_meetme_user;
-       struct ast_data *data_meetme_user_channel;
-       struct ast_data *data_meetme_user_volume;
-
-       struct ast_conf_user *user = obj;
-       struct ast_data *data_meetme_users = arg;
-
-       data_meetme_user = ast_data_add_node(data_meetme_users, "user");
-       if (!data_meetme_user) {
-               return 0;
-       }
-       /* user structure */
-       ast_data_add_structure(ast_conf_user, data_meetme_user, user);
-
-       /* user's channel */
-       data_meetme_user_channel = ast_data_add_node(data_meetme_user, "channel");
-       if (!data_meetme_user_channel) {
-               return 0;
-       }
-
-       ast_channel_data_add_structure(data_meetme_user_channel, user->chan, 1);
-
-       /* volume structure */
-       data_meetme_user_volume = ast_data_add_node(data_meetme_user, "listen-volume");
-       if (!data_meetme_user_volume) {
-               return 0;
-       }
-       ast_data_add_int(data_meetme_user_volume, "desired", user->listen.desired);
-       ast_data_add_int(data_meetme_user_volume, "actual", user->listen.actual);
-
-       data_meetme_user_volume = ast_data_add_node(data_meetme_user, "talk-volume");
-       if (!data_meetme_user_volume) {
-               return 0;
-       }
-       ast_data_add_int(data_meetme_user_volume, "desired", user->talk.desired);
-       ast_data_add_int(data_meetme_user_volume, "actual", user->talk.actual);
-
-       return 0;
-}
-
-/*!
- * \internal
- * \brief Implements the meetme data provider.
- */
-static int meetme_data_provider_get(const struct ast_data_search *search,
-       struct ast_data *data_root)
-{
-       struct ast_conference *cnf;
-       struct ast_data *data_meetme, *data_meetme_users;
-
-       AST_LIST_LOCK(&confs);
-       AST_LIST_TRAVERSE(&confs, cnf, list) {
-               data_meetme = ast_data_add_node(data_root, "meetme");
-               if (!data_meetme) {
-                       continue;
-               }
-
-               ast_data_add_structure(ast_conference, data_meetme, cnf);
-
-               if (ao2_container_count(cnf->usercontainer)) {
-                       data_meetme_users = ast_data_add_node(data_meetme, "users");
-                       if (!data_meetme_users) {
-                               ast_data_remove_node(data_root, data_meetme);
-                               continue;
-                       }
-
-                       ao2_callback(cnf->usercontainer, OBJ_NODATA, user_add_provider_cb, data_meetme_users); 
-               }
-
-               if (!ast_data_search_match(search, data_meetme)) {
-                       ast_data_remove_node(data_root, data_meetme);
-               }
-       }
-       AST_LIST_UNLOCK(&confs);
-
-       return 0;
-}
-
-static const struct ast_data_handler meetme_data_provider = {
-       .version = AST_DATA_HANDLER_VERSION,
-       .get = meetme_data_provider_get
-};
-
-static const struct ast_data_entry meetme_data_providers[] = {
-       AST_DATA_ENTRY("asterisk/application/meetme/list", &meetme_data_provider),
-};
-
-#ifdef TEST_FRAMEWORK
-AST_TEST_DEFINE(test_meetme_data_provider)
-{
-       struct ast_channel *chan;
-       struct ast_conference *cnf;
-       struct ast_data *node;
-       struct ast_data_query query = {
-               .path = "/asterisk/application/meetme/list",
-               .search = "list/meetme/confno=9898"
-       };
-
-       switch (cmd) {
-       case TEST_INIT:
-               info->name = "meetme_get_data_test";
-               info->category = "/main/data/app_meetme/list/";
-               info->summary = "Meetme data provider unit test";
-               info->description =
-                       "Tests whether the Meetme data provider implementation works as expected.";
-               return AST_TEST_NOT_RUN;
-       case TEST_EXECUTE:
-               break;
-       }
-
-       chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, 0, 0, "MeetMeTest");
-       if (!chan) {
-               ast_test_status_update(test, "Channel allocation failed\n");
-               return AST_TEST_FAIL;
-       }
-
-       cnf = build_conf("9898", "", "1234", 1, 1, 1, chan, test);
-       if (!cnf) {
-               ast_test_status_update(test, "Build of test conference 9898 failed\n");
-               ast_hangup(chan);
-               return AST_TEST_FAIL;
-       }
-
-       node = ast_data_get(&query);
-       if (!node) {
-               ast_test_status_update(test, "Data query for test conference 9898 failed\n");
-               dispose_conf(cnf);
-               ast_hangup(chan);
-               return AST_TEST_FAIL;
-       }
-
-       if (strcmp(ast_data_retrieve_string(node, "meetme/confno"), "9898")) {
-               ast_test_status_update(test, "Query returned the wrong conference\n");
-               dispose_conf(cnf);
-               ast_hangup(chan);
-               ast_data_free(node);
-               return AST_TEST_FAIL;
-       }
-
-       ast_data_free(node);
-       dispose_conf(cnf);
-       ast_hangup(chan);
-
-       return AST_TEST_PASS;
+       load_config_meetme(reload);
+       return sla_load_config(reload);
 }
-#endif
 
 static int unload_module(void)
 {
        int res = 0;
-       
+
        ast_cli_unregister_multiple(cli_meetme, ARRAY_LEN(cli_meetme));
        res = ast_manager_unregister("MeetmeMute");
        res |= ast_manager_unregister("MeetmeUnmute");
@@ -7324,30 +8021,37 @@ static int unload_module(void)
        res |= ast_unregister_application(slastation_app);
        res |= ast_unregister_application(slatrunk_app);
 
-#ifdef TEST_FRAMEWORK
-       AST_TEST_UNREGISTER(test_meetme_data_provider);
-#endif
-       ast_data_unregister(NULL);
-
        ast_devstate_prov_del("Meetme");
        ast_devstate_prov_del("SLA");
-       
+
        sla_destroy();
-       
+
        res |= ast_custom_function_unregister(&meetme_info_acf);
        ast_unload_realtime("meetme");
 
+       meetme_stasis_cleanup();
+
        return res;
 }
 
-
-
+/*!
+ * \brief Load the module
+ *
+ * Module loading including tests for configuration or dependencies.
+ * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
+ * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
+ * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
+ * configuration file or other non-critical problem return
+ * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
+ */
 static int load_module(void)
 {
        int res = 0;
 
        res |= load_config(0);
 
+       res |= meetme_stasis_init();
+
        ast_cli_register_multiple(cli_meetme, ARRAY_LEN(cli_meetme));
        res |= ast_manager_register_xml("MeetmeMute", EVENT_FLAG_CALL, action_meetmemute);
        res |= ast_manager_register_xml("MeetmeUnmute", EVENT_FLAG_CALL, action_meetmeunmute);
@@ -7360,11 +8064,6 @@ static int load_module(void)
        res |= ast_register_application_xml(slastation_app, sla_station_exec);
        res |= ast_register_application_xml(slatrunk_app, sla_trunk_exec);
 
-#ifdef TEST_FRAMEWORK
-       AST_TEST_REGISTER(test_meetme_data_provider);
-#endif
-       ast_data_register_multiple(meetme_data_providers, ARRAY_LEN(meetme_data_providers));
-
        res |= ast_devstate_prov_add("Meetme", meetmestate);
        res |= ast_devstate_prov_add("SLA", sla_state);
 
@@ -7381,9 +8080,10 @@ static int reload(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "MeetMe conference bridge",
-               .load = load_module,
-               .unload = unload_module,
-               .reload = reload,
-               .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
-              );
-
+       .support_level = AST_MODULE_SUPPORT_EXTENDED,
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload,
+       .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
+       .optional_modules = "func_speex",
+);