CI: Various updates to buildAsterisk.sh
[asterisk/asterisk.git] / main / manager.c
index 5fcd347..3e41198 100644 (file)
@@ -54,9 +54,6 @@
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/_private.h"
 #include "asterisk/paths.h"    /* use various ast_config_AST_* */
 #include <ctype.h>
 #include <sys/time.h>
@@ -99,7 +96,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/bridge.h"
 #include "asterisk/features_config.h"
 #include "asterisk/rtp_engine.h"
+#include "asterisk/format_cache.h"
 #include "asterisk/translate.h"
+#include "asterisk/taskprocessor.h"
 
 /*** DOCUMENTATION
        <manager name="Ping" language="en_US">
@@ -148,6 +147,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Logoff the current manager session.</para>
                </description>
+               <see-also>
+                       <ref type="manager">Login</ref>
+               </see-also>
        </manager>
        <manager name="Login" language="en_US">
                <synopsis>
@@ -167,6 +169,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Login Manager.</para>
                </description>
+               <see-also>
+                       <ref type="manager">Logoff</ref>
+               </see-also>
        </manager>
        <manager name="Challenge" language="en_US">
                <synopsis>
@@ -210,17 +215,31 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </synopsis>
                <syntax>
                        <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-                       <parameter name="Channel" required="true">
+                       <parameter name="Channel" required="false">
                                <para>The name of the channel to query for status.</para>
                        </parameter>
                        <parameter name="Variables">
                                <para>Comma <literal>,</literal> separated list of variable to include.</para>
                        </parameter>
+                       <parameter name="AllVariables">
+                               <para>If set to "true", the Status event will include all channel variables for
+                               the requested channel(s).</para>
+                               <enumlist>
+                                       <enum name="true"/>
+                                       <enum name="false"/>
+                               </enumlist>
+                       </parameter>
                </syntax>
                <description>
                        <para>Will return the status information of each channel along with the
                        value for the specified channel variables.</para>
                </description>
+               <responses>
+                       <list-elements>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Status'])" />
+                       </list-elements>
+                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='StatusComplete'])" />
+               </responses>
        </manager>
        <managerEvent language="en_US" name="Status">
                <managerEventInstance class="EVENT_FLAG_CALL">
@@ -234,14 +253,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                <parameter name="DNID">
                                        <para>Dialed number identifier</para>
                                </parameter>
+                               <parameter name="EffectiveConnectedLineNum">
+                               </parameter>
+                               <parameter name="EffectiveConnectedLineName">
+                               </parameter>
                                <parameter name="TimeToHangup">
                                        <para>Absolute lifetime of the channel</para>
                                </parameter>
                                <parameter name="BridgeID">
                                        <para>Identifier of the bridge the channel is in, may be empty if not in one</para>
                                </parameter>
-                               <parameter name="Linkedid">
-                               </parameter>
                                <parameter name="Application">
                                        <para>Application currently executing on the channel</para>
                                </parameter>
@@ -278,9 +299,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        </see-also>
                </managerEventInstance>
        </managerEvent>
+       <managerEvent language="en_US" name="StatusComplete">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised in response to a Status command.</synopsis>
+                       <syntax>
+                               <parameter name="Items">
+                                       <para>Number of Status events returned</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="manager">Status</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
        <manager name="Setvar" language="en_US">
                <synopsis>
-                       Set a channel variable.
+                       Sets a channel variable or function value.
                </synopsis>
                <syntax>
                        <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
@@ -288,22 +322,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                <para>Channel to set variable for.</para>
                        </parameter>
                        <parameter name="Variable" required="true">
-                               <para>Variable name.</para>
+                               <para>Variable name, function or expression.</para>
                        </parameter>
                        <parameter name="Value" required="true">
-                               <para>Variable value.</para>
+                               <para>Variable or function value.</para>
                        </parameter>
                </syntax>
                <description>
-                       <para>Set a global or local channel variable.</para>
+                       <para>This command can be used to set the value of channel variables or dialplan
+                       functions.</para>
                        <note>
-                               <para>If a channel name is not provided then the variable is global.</para>
+                               <para>If a channel name is not provided then the variable is considered global.</para>
                        </note>
                </description>
+               <see-also>
+                       <ref type="manager">Getvar</ref>
+               </see-also>
        </manager>
        <manager name="Getvar" language="en_US">
                <synopsis>
-                       Gets a channel variable.
+                       Gets a channel variable or function value.
                </synopsis>
                <syntax>
                        <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
@@ -311,15 +349,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                <para>Channel to read variable from.</para>
                        </parameter>
                        <parameter name="Variable" required="true">
-                               <para>Variable name.</para>
+                               <para>Variable name, function or expression.</para>
                        </parameter>
                </syntax>
                <description>
-                       <para>Get the value of a global or local channel variable.</para>
+                       <para>Get the value of a channel variable or function return.</para>
                        <note>
-                               <para>If a channel name is not provided then the variable is global.</para>
+                               <para>If a channel name is not provided then the variable is considered global.</para>
                        </note>
                </description>
+               <see-also>
+                       <ref type="manager">Setvar</ref>
+               </see-also>
        </manager>
        <manager name="GetConfig" language="en_US">
                <synopsis>
@@ -333,11 +374,31 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="Category">
                                <para>Category in configuration file.</para>
                        </parameter>
+                       <parameter name="Filter">
+                               <para>A comma separated list of
+                               <replaceable>name_regex</replaceable>=<replaceable>value_regex</replaceable>
+                               expressions which will cause only categories whose variables match all expressions
+                               to be considered.  The special variable name <literal>TEMPLATES</literal>
+                               can be used to control whether templates are included.  Passing
+                               <literal>include</literal> as the value will include templates
+                               along with normal categories. Passing
+                               <literal>restrict</literal> as the value will restrict the operation to
+                               ONLY templates.  Not specifying a <literal>TEMPLATES</literal> expression
+                               results in the default behavior which is to not include templates.</para>
+                       </parameter>
                </syntax>
                <description>
                        <para>This action will dump the contents of a configuration
-                       file by category and contents or optionally by specified category only.</para>
+                       file by category and contents or optionally by specified category only.
+                       In the case where a category name is non-unique, a filter may be specified
+                       to match only categories with matching variable values.</para>
                </description>
+               <see-also>
+                       <ref type="manager">GetConfigJSON</ref>
+                       <ref type="manager">UpdateConfig</ref>
+                       <ref type="manager">CreateConfig</ref>
+                       <ref type="manager">ListCategories</ref>
+               </see-also>
        </manager>
        <manager name="GetConfigJSON" language="en_US">
                <synopsis>
@@ -348,12 +409,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="Filename" required="true">
                                <para>Configuration filename (e.g. <filename>foo.conf</filename>).</para>
                        </parameter>
+                       <parameter name="Category">
+                               <para>Category in configuration file.</para>
+                       </parameter>
+                       <parameter name="Filter">
+                               <xi:include xpointer="xpointer(/docs/manager[@name='GetConfig']/syntax/parameter[@name='Filter']/para[1])" />
+                       </parameter>
                </syntax>
                <description>
                        <para>This action will dump the contents of a configuration file by category
-                       and contents in JSON format. This only makes sense to be used using rawman over
-                       the HTTP interface.</para>
+                       and contents in JSON format or optionally by specified category only.
+                       This only makes sense to be used using rawman over the HTTP interface.
+                       In the case where a category name is non-unique, a filter may be specified
+                       to match only categories with matching variable values.</para>
                </description>
+               <see-also>
+                       <ref type="manager">GetConfig</ref>
+                       <ref type="manager">UpdateConfig</ref>
+                       <ref type="manager">CreateConfig</ref>
+                       <ref type="manager">ListCategories</ref>
+               </see-also>
        </manager>
        <manager name="UpdateConfig" language="en_US">
                <synopsis>
@@ -370,9 +445,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="Reload">
                                <para>Whether or not a reload should take place (or name of specific module).</para>
                        </parameter>
-                       <parameter name="Action-XXXXXX">
+                       <parameter name="PreserveEffectiveContext">
+                               <para>Whether the effective category contents should be preserved on template change. Default is true (pre 13.2 behavior).</para>
+                       </parameter>
+                       <parameter name="Action-000000">
                                <para>Action to take.</para>
-                               <para>X's represent 6 digit number beginning with 000000.</para>
+                               <para>0's represent 6 digit number beginning with 000000.</para>
                                <enumlist>
                                        <enum name="NewCat" />
                                        <enum name="RenameCat" />
@@ -384,31 +462,70 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <enum name="Insert" />
                                </enumlist>
                        </parameter>
-                       <parameter name="Cat-XXXXXX">
+                       <parameter name="Cat-000000">
                                <para>Category to operate on.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Var-XXXXXX">
+                       <parameter name="Var-000000">
                                <para>Variable to work on.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Value-XXXXXX">
+                       <parameter name="Value-000000">
                                <para>Value to work on.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Match-XXXXXX">
+                       <parameter name="Match-000000">
                                <para>Extra match required to match line.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Line-XXXXXX">
+                       <parameter name="Line-000000">
                                <para>Line in category to operate on (used with delete and insert actions).</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
+                       </parameter>
+                       <parameter name="Options-000000">
+                               <para>A comma separated list of action-specific options.</para>
+                                       <enumlist>
+                                               <enum name="NewCat"><para>One or more of the following... </para>
+                                                       <enumlist>
+                                                               <enum name="allowdups"><para>Allow duplicate category names.</para></enum>
+                                                               <enum name="template"><para>This category is a template.</para></enum>
+                                                               <enum name="inherit=&quot;template[,...]&quot;"><para>Templates from which to inherit.</para></enum>
+                                                       </enumlist>
+                                               </enum>
+                                       </enumlist>
+                                       <para> </para>
+                                               <para>The following actions share the same options...</para>
+                                       <enumlist>
+                                               <enum name="RenameCat"/>
+                                               <enum name="DelCat"/>
+                                               <enum name="EmptyCat"/>
+                                               <enum name="Update"/>
+                                               <enum name="Delete"/>
+                                               <enum name="Append"/>
+                                               <enum name="Insert"><para> </para>
+                                                       <enumlist>
+                                                               <enum name="catfilter=&quot;&lt;expression&gt;[,...]&quot;"><para> </para>
+                                                                       <xi:include xpointer="xpointer(/docs/manager[@name='GetConfig']/syntax/parameter[@name='Filter']/para[1])" />
+                                                                       <para><literal>catfilter</literal> is most useful when a file
+                                                                       contains multiple categories with the same name and you wish to
+                                                                       operate on specific ones instead of all of them.</para>
+                                                               </enum>
+                                                       </enumlist>
+                                               </enum>
+                                       </enumlist>
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
                </syntax>
                <description>
                        <para>This action will modify, create, or delete configuration elements
                        in Asterisk configuration files.</para>
                </description>
+               <see-also>
+                       <ref type="manager">GetConfig</ref>
+                       <ref type="manager">GetConfigJSON</ref>
+                       <ref type="manager">CreateConfig</ref>
+                       <ref type="manager">ListCategories</ref>
+               </see-also>
        </manager>
        <manager name="CreateConfig" language="en_US">
                <synopsis>
@@ -425,6 +542,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        directory. This action is intended to be used before an UpdateConfig
                        action.</para>
                </description>
+               <see-also>
+                       <ref type="manager">GetConfig</ref>
+                       <ref type="manager">GetConfigJSON</ref>
+                       <ref type="manager">UpdateConfig</ref>
+                       <ref type="manager">ListCategories</ref>
+               </see-also>
        </manager>
        <manager name="ListCategories" language="en_US">
                <synopsis>
@@ -439,6 +562,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>This action will dump the categories in a given file.</para>
                </description>
+               <see-also>
+                       <ref type="manager">GetConfig</ref>
+                       <ref type="manager">GetConfigJSON</ref>
+                       <ref type="manager">UpdateConfig</ref>
+                       <ref type="manager">CreateConfig</ref>
+               </see-also>
        </manager>
        <manager name="Redirect" language="en_US">
                <synopsis>
@@ -474,6 +603,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Redirect (transfer) a call.</para>
                </description>
+               <see-also>
+                       <ref type="manager">BlindTransfer</ref>
+               </see-also>
        </manager>
        <manager name="Atxfer" language="en_US">
                <synopsis>
@@ -494,6 +626,28 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Attended transfer.</para>
                </description>
+               <see-also>
+                       <ref type="managerEvent">AttendedTransfer</ref>
+               </see-also>
+       </manager>
+       <manager name="CancelAtxfer" language="en_US">
+               <synopsis>
+                       Cancel an attended transfer.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="Channel" required="true">
+                               <para>The transferer channel.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Cancel an attended transfer. Note, this uses the configured cancel attended transfer
+                       feature option (atxferabort) to cancel the transfer. If not available this action will fail.
+                       </para>
+               </description>
+               <see-also>
+                       <ref type="managerEvent">AttendedTransfer</ref>
+               </see-also>
        </manager>
        <manager name="Originate" language="en_US">
                <synopsis>
@@ -564,7 +718,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <synopsis>Raised in response to an Originate command.</synopsis>
                        <syntax>
                                <parameter name="ActionID" required="false"/>
-                               <parameter name="Resonse">
+                               <parameter name="Response">
                                        <enumlist>
                                                <enum name="Failure"/>
                                                <enum name="Success"/>
@@ -573,6 +727,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                <parameter name="Channel"/>
                                <parameter name="Context"/>
                                <parameter name="Exten"/>
+                               <parameter name="Application"/>
+                               <parameter name="Data"/>
                                <parameter name="Reason"/>
                                <parameter name="Uniqueid"/>
                                <parameter name="CallerIDNum"/>
@@ -616,6 +772,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Will return an <literal>Extension Status</literal> message. The response will include
                        the hint for the extension and the status.</para>
                </description>
+               <see-also>
+                       <ref type="managerEvent">ExtensionStatus</ref>
+               </see-also>
        </manager>
        <manager name="PresenceState" language="en_US">
                <synopsis>
@@ -632,6 +791,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Will return a <literal>Presence State</literal> message. The response will include the
                        presence state and, if set, a presence subtype and custom message.</para>
                </description>
+               <see-also>
+                       <ref type="managerEvent">PresenceStatus</ref>
+               </see-also>
        </manager>
        <manager name="AbsoluteTimeout" language="en_US">
                <synopsis>
@@ -669,6 +831,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Waiting: <literal>0</literal> if messages waiting, <literal>1</literal>
                        if no messages waiting.</para>
                </description>
+               <see-also>
+                       <ref type="manager">MailboxCount</ref>
+               </see-also>
        </manager>
        <manager name="MailboxCount" language="en_US">
                <synopsis>
@@ -689,6 +854,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>NewMessages: <replaceable>count</replaceable></para>
                        <para>OldMessages: <replaceable>count</replaceable></para>
                </description>
+               <see-also>
+                       <ref type="manager">MailboxStatus</ref>
+               </see-also>
        </manager>
        <manager name="ListCommands" language="en_US">
                <synopsis>
@@ -738,6 +906,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Send an event to manager sessions.</para>
                </description>
+               <see-also>
+                       <ref type="managerEvent">UserEvent</ref>
+                       <ref type="application">UserEvent</ref>
+               </see-also>
        </manager>
        <manager name="WaitEvent" language="en_US">
                <synopsis>
@@ -790,7 +962,53 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Send a reload event.</para>
                </description>
+               <see-also>
+                       <ref type="manager">ModuleLoad</ref>
+               </see-also>
        </manager>
+       <managerEvent language="en_US" name="CoreShowChannel">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised in response to a CoreShowChannels command.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                               <channel_snapshot/>
+                               <parameter name="BridgeId">
+                                       <para>Identifier of the bridge the channel is in, may be empty if not in one</para>
+                               </parameter>
+                               <parameter name="Application">
+                                       <para>Application currently executing on the channel</para>
+                               </parameter>
+                               <parameter name="ApplicationData">
+                                       <para>Data given to the currently executing application</para>
+                               </parameter>
+                               <parameter name="Duration">
+                                       <para>The amount of time the channel has existed</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="manager">CoreShowChannels</ref>
+                               <ref type="managerEvent">CoreShowChannelsComplete</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="CoreShowChannelsComplete">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised at the end of the CoreShowChannel list produced by the CoreShowChannels command.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                               <parameter name="EventList">
+                                       <para>Conveys the status of the command reponse list</para>
+                               </parameter>
+                               <parameter name="ListItems">
+                                       <para>The total number of list items produced</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="manager">CoreShowChannels</ref>
+                               <ref type="managerEvent">CoreShowChannel</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
        <manager name="CoreShowChannels" language="en_US">
                <synopsis>
                        List currently active channels.
@@ -801,6 +1019,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>List currently defined channels and some information about them.</para>
                </description>
+               <responses>
+                       <list-elements>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='CoreShowChannel'])" />
+                       </list-elements>
+                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='CoreShowChannelsComplete'])" />
+               </responses>
        </manager>
        <manager name="LoggerRotate" language="en_US">
                <synopsis>
@@ -853,6 +1077,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Loads, unloads or reloads an Asterisk module in a running system.</para>
                </description>
+               <see-also>
+                       <ref type="manager">Reload</ref>
+                       <ref type="manager">ModuleCheck</ref>
+               </see-also>
        </manager>
        <manager name="ModuleCheck" language="en_US">
                <synopsis>
@@ -868,6 +1096,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Checks if Asterisk module is loaded. Will return Success/Failure.
                        For success returns, the module revision number is included.</para>
                </description>
+               <see-also>
+                       <ref type="manager">ModuleLoad</ref>
+               </see-also>
        </manager>
        <manager name="AOCMessage" language="en_US">
                <synopsis>
@@ -974,6 +1205,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Generates an AOC-D or AOC-E message on a channel.</para>
                </description>
+               <see-also>
+                       <ref type="managerEvent">AOC-D</ref>
+                       <ref type="managerEvent">AOC-E</ref>
+               </see-also>
        </manager>
        <function name="AMI_CLIENT" language="en_US">
                <synopsis>
@@ -1033,6 +1268,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        this command can be used to create filters that may bypass
                        filters defined in manager.conf</para>
                </description>
+               <see-also>
+                       <ref type="manager">FilterList</ref>
+               </see-also>
        </manager>
        <manager name="FilterList" language="en_US">
                <synopsis>
@@ -1042,6 +1280,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>The filters displayed are for the current session.  Only those filters defined in
                         manager.conf will be present upon starting a new session.</para>
                </description>
+               <see-also>
+                       <ref type="manager">Filter</ref>
+               </see-also>
        </manager>
        <manager name="BlindTransfer" language="en_US">
                <synopsis>
@@ -1060,8 +1301,105 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </description>
                <see-also>
                        <ref type="manager">Redirect</ref>
+                       <ref type="managerEvent">BlindTransfer</ref>
                </see-also>
        </manager>
+       <managerEvent name="ExtensionStatus" language="en_US">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a hint changes due to a device state change.</synopsis>
+                       <syntax>
+                               <parameter name="Exten">
+                                       <para>Name of the extension.</para>
+                               </parameter>
+                               <parameter name="Context">
+                                       <para>Context that owns the extension.</para>
+                               </parameter>
+                               <parameter name="Hint">
+                                       <para>Hint set for the extension</para>
+                               </parameter>
+                               <parameter name="Status">
+                                       <para>Numerical value of the extension status. Extension
+                                       status is determined by the combined device state of all items
+                                       contained in the hint.</para>
+                                       <enumlist>
+                                               <enum name="-2">
+                                                       <para>The extension was removed from the dialplan.</para>
+                                               </enum>
+                                               <enum name="-1">
+                                                       <para>The extension's hint was removed from the dialplan.</para>
+                                               </enum>
+                                               <enum name="0">
+                                                       <para><literal>Idle</literal> - Related device(s) are in an idle
+                                                       state.</para>
+                                               </enum>
+                                               <enum name="1">
+                                                       <para><literal>InUse</literal> - Related device(s) are in active
+                                                       calls but may take more calls.</para>
+                                               </enum>
+                                               <enum name="2">
+                                                       <para><literal>Busy</literal> - Related device(s) are in active
+                                                       calls and may not take any more calls.</para>
+                                               </enum>
+                                               <enum name="4">
+                                                       <para><literal>Unavailable</literal> - Related device(s) are
+                                                       not reachable.</para>
+                                               </enum>
+                                               <enum name="8">
+                                                       <para><literal>Ringing</literal> - Related device(s) are
+                                                       currently ringing.</para>
+                                               </enum>
+                                               <enum name="9">
+                                                       <para><literal>InUse&amp;Ringing</literal> - Related device(s)
+                                                       are currently ringing and in active calls.</para>
+                                               </enum>
+                                               <enum name="16">
+                                                       <para><literal>Hold</literal> - Related device(s) are
+                                                       currently on hold.</para>
+                                               </enum>
+                                               <enum name="17">
+                                                       <para><literal>InUse&amp;Hold</literal> - Related device(s)
+                                                       are currently on hold and in active calls.</para>
+                                               </enum>
+                                       </enumlist>
+                               </parameter>
+                               <parameter name="StatusText">
+                                       <para>Text representation of <literal>Status</literal>.</para>
+                                       <enumlist>
+                                               <enum name="Idle" />
+                                               <enum name="InUse" />
+                                               <enum name="Busy" />
+                                               <enum name="Unavailable" />
+                                               <enum name="Ringing" />
+                                               <enum name="InUse&amp;Ringing" />
+                                               <enum name="Hold" />
+                                               <enum name="InUse&amp;Hold" />
+                                               <enum name="Unknown">
+                                                       <para>Status does not match any of the above values.</para>
+                                               </enum>
+                                       </enumlist>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="manager">ExtensionState</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent name="PresenceStatus" language="en_US">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a hint changes due to a presence state change.</synopsis>
+                       <syntax>
+                               <parameter name="Exten" />
+                               <parameter name="Context" />
+                               <parameter name="Hint" />
+                               <parameter name="Status" />
+                               <parameter name="Subtype" />
+                               <parameter name="Message" />
+                       </syntax>
+                       <see-also>
+                               <ref type="manager">PresenceState</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
  ***/
 
 /*! \addtogroup Group_AMI AMI functions
@@ -1080,7 +1418,8 @@ enum error_type {
        FAILURE_EMPTYCAT,
        FAILURE_UPDATE,
        FAILURE_DELETE,
-       FAILURE_APPEND
+       FAILURE_APPEND,
+       FAILURE_TEMPLATE
 };
 
 enum add_filter_result {
@@ -1150,10 +1489,18 @@ static struct stasis_forward *rtp_topic_forwarder;
 /*! \brief The \ref stasis_subscription for forwarding the Security topic to the AMI topic */
 static struct stasis_forward *security_topic_forwarder;
 
+#ifdef TEST_FRAMEWORK
+/*! \brief The \ref stasis_subscription for forwarding the Test topic to the AMI topic */
+static struct stasis_forward *test_suite_forwarder;
+#endif
+
 #define MGR_SHOW_TERMINAL_WIDTH 80
 
 #define MAX_VARS 128
 
+/*! \brief Fake event class used to end sessions at shutdown */
+#define EVENT_FLAG_SHUTDOWN -1
+
 /*! \brief
  * Descriptor for a manager session, either on the AMI socket or over HTTP.
  *
@@ -1180,6 +1527,8 @@ static void acl_change_stasis_subscribe(void)
        if (!acl_change_sub) {
                acl_change_sub = stasis_subscribe(ast_security_topic(),
                        acl_change_stasis_cb, NULL);
+               stasis_subscription_accept_message_type(acl_change_sub, ast_named_acl_change_type());
+               stasis_subscription_set_filter(acl_change_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
        }
 }
 
@@ -1223,8 +1572,7 @@ static void acl_change_stasis_unsubscribe(void)
 struct mansession_session {
                                /*! \todo XXX need to document which fields it is protecting */
        struct ast_sockaddr addr;       /*!< address we are connecting from */
-       FILE *f;                /*!< fdopen() on the underlying fd */
-       int fd;                 /*!< descriptor used for output. Either the socket (AMI) or a temporary file (HTTP) */
+       struct ast_iostream *stream;    /*!< AMI stream */
        int inuse;              /*!< number of HTTP sessions using this entry */
        int needdestroy;        /*!< Whether an HTTP session should be destroyed */
        pthread_t waiting_thread;       /*!< Sleeping thread using this descriptor */
@@ -1266,9 +1614,8 @@ enum mansession_message_parsing {
  */
 struct mansession {
        struct mansession_session *session;
+       struct ast_iostream *stream;
        struct ast_tcptls_session_instance *tcptls_session;
-       FILE *f;
-       int fd;
        enum mansession_message_parsing parsing;
        int write_error:1;
        struct manager_custom_hook *hook;
@@ -1310,11 +1657,26 @@ static AST_RWLIST_HEAD_STATIC(actions, manager_action);
 /*! \brief list of hooks registered */
 static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook);
 
+#ifdef AST_XML_DOCS
 /*! \brief A container of event documentation nodes */
 static AO2_GLOBAL_OBJ_STATIC(event_docs);
+#endif
 
+static int __attribute__((format(printf, 9, 0))) __manager_event_sessions(
+       struct ao2_container *sessions,
+       int category,
+       const char *event,
+       int chancount,
+       struct ast_channel **chans,
+       const char *file,
+       int line,
+       const char *func,
+       const char *fmt,
+       ...);
 static enum add_filter_result manager_add_filter(const char *filter_pattern, struct ao2_container *whitefilters, struct ao2_container *blackfilters);
 
+static int match_filter(struct mansession *s, char *eventdata);
+
 /*!
  * @{ \brief Define AMI message types.
  */
@@ -1410,7 +1772,12 @@ void manager_json_to_ast_str(struct ast_json *obj, const char *key,
 {
        struct ast_json_iter *i;
 
-       if (!obj || (!res && !(*res) && (!(*res = ast_str_create(1024))))) {
+       /* If obj or res is not given, just return */
+       if (!obj || !res) {
+               return;
+       }
+
+       if (!*res && !(*res = ast_str_create(1024))) {
                return;
        }
 
@@ -1441,45 +1808,86 @@ void manager_json_to_ast_str(struct ast_json *obj, const char *key,
        }
 }
 
-
 struct ast_str *ast_manager_str_from_json_object(struct ast_json *blob, key_exclusion_cb exclusion_cb)
 {
        struct ast_str *res = ast_str_create(1024);
-       manager_json_to_ast_str(blob, NULL, &res, exclusion_cb);
+
+       if (!ast_json_is_null(blob)) {
+          manager_json_to_ast_str(blob, NULL, &res, exclusion_cb);
+       }
+
        return res;
 }
 
+#define manager_event_sessions(sessions, category, event, contents , ...)      \
+       __manager_event_sessions(sessions, category, event, 0, NULL, __FILE__, __LINE__, __PRETTY_FUNCTION__, contents , ## __VA_ARGS__)
+
+#define any_manager_listeners(sessions)        \
+       ((sessions && ao2_container_count(sessions)) || !AST_RWLIST_EMPTY(&manager_hooks))
+
 static void manager_default_msg_cb(void *data, struct stasis_subscription *sub,
                                    struct stasis_message *message)
 {
-       RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+       struct ao2_container *sessions;
+       struct ast_manager_event_blob *ev;
 
-       ev = stasis_message_to_ami(message);
+       if (!stasis_message_can_be_ami(message)) {
+               /* Not an AMI message; disregard */
+               return;
+       }
 
-       if (ev == NULL) {
-               /* Not and AMI message; disregard */
+       sessions = ao2_global_obj_ref(mgr_sessions);
+       if (!any_manager_listeners(sessions)) {
+               /* Nobody is listening */
+               ao2_cleanup(sessions);
                return;
        }
 
-       manager_event(ev->event_flags, ev->manager_event, "%s",
-               ev->extra_fields);
+       ev = stasis_message_to_ami(message);
+       if (!ev) {
+               /* Conversion failure */
+               ao2_cleanup(sessions);
+               return;
+       }
+
+       manager_event_sessions(sessions, ev->event_flags, ev->manager_event,
+               "%s", ev->extra_fields);
+       ao2_ref(ev, -1);
+       ao2_cleanup(sessions);
 }
 
 static void manager_generic_msg_cb(void *data, struct stasis_subscription *sub,
                                    struct stasis_message *message)
 {
-       struct ast_json_payload *payload = stasis_message_data(message);
-       int class_type = ast_json_integer_get(ast_json_object_get(payload->json, "class_type"));
-       const char *type = ast_json_string_get(ast_json_object_get(payload->json, "type"));
-       struct ast_json *event = ast_json_object_get(payload->json, "event");
-       RAII_VAR(struct ast_str *, event_buffer, NULL, ast_free);
+       struct ast_json_payload *payload;
+       int class_type;
+       const char *type;
+       struct ast_json *event;
+       struct ast_str *event_buffer;
+       struct ao2_container *sessions;
+
+       sessions = ao2_global_obj_ref(mgr_sessions);
+       if (!any_manager_listeners(sessions)) {
+               /* Nobody is listening */
+               ao2_cleanup(sessions);
+               return;
+       }
+
+       payload = stasis_message_data(message);
+       class_type = ast_json_integer_get(ast_json_object_get(payload->json, "class_type"));
+       type = ast_json_string_get(ast_json_object_get(payload->json, "type"));
+       event = ast_json_object_get(payload->json, "event");
 
        event_buffer = ast_manager_str_from_json_object(event, NULL);
        if (!event_buffer) {
                ast_log(AST_LOG_WARNING, "Error while creating payload for event %s\n", type);
+               ao2_cleanup(sessions);
                return;
        }
-       manager_event(class_type, type, "%s", ast_str_buffer(event_buffer));
+       manager_event_sessions(sessions, class_type, type,
+               "%s", ast_str_buffer(event_buffer));
+       ast_free(event_buffer);
+       ao2_cleanup(sessions);
 }
 
 void ast_manager_publish_event(const char *type, int class_type, struct ast_json *obj)
@@ -1488,7 +1896,7 @@ void ast_manager_publish_event(const char *type, int class_type, struct ast_json
        RAII_VAR(struct ast_json_payload *, payload, NULL, ao2_cleanup);
        RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
 
-       if (!obj) {
+       if (!obj || !ast_manager_get_generic_type()) {
                return;
        }
 
@@ -1621,6 +2029,9 @@ static const struct permalias {
        { 0, "none" },
 };
 
+/*! Maximum string length of the AMI authority permission string buildable from perms[]. */
+#define MAX_AUTH_PERM_STRING   150
+
 /*! \brief Checks to see if a string which can be used to evaluate functions should be rejected */
 static int function_capable_string_allowed_with_auths(const char *evaluating, int writepermlist)
 {
@@ -1649,8 +2060,10 @@ static const char *user_authority_to_str(int authority, struct ast_str **res)
                }
        }
 
-       if (ast_str_strlen(*res) == 0)  /* replace empty string with something sensible */
+       if (ast_str_strlen(*res) == 0) {
+               /* replace empty string with something sensible */
                ast_str_append(res, 0, "<none>");
+       }
 
        return ast_str_buffer(*res);
 }
@@ -1664,15 +2077,19 @@ static const char *authority_to_str(int authority, struct ast_str **res)
        char *sep = "";
 
        ast_str_reset(*res);
-       for (i = 0; i < ARRAY_LEN(perms) - 1; i++) {
-               if (authority & perms[i].num) {
-                       ast_str_append(res, 0, "%s%s", sep, perms[i].label);
-                       sep = ",";
+       if (authority != EVENT_FLAG_SHUTDOWN) {
+               for (i = 0; i < ARRAY_LEN(perms) - 1; i++) {
+                       if (authority & perms[i].num) {
+                               ast_str_append(res, 0, "%s%s", sep, perms[i].label);
+                               sep = ",";
+                       }
                }
        }
 
-       if (ast_str_strlen(*res) == 0)  /* replace empty string with something sensible */
+       if (ast_str_strlen(*res) == 0) {
+               /* replace empty string with something sensible */
                ast_str_append(res, 0, "<none>");
+       }
 
        return ast_str_buffer(*res);
 }
@@ -1780,10 +2197,6 @@ static void session_destructor(void *obj)
                ast_datastore_free(datastore);
        }
 
-       if (session->f != NULL) {
-               fflush(session->f);
-               fclose(session->f);
-       }
        if (eqe) {
                ast_atomic_fetchadd_int(&eqe->usecount, -1);
        }
@@ -1811,14 +2224,13 @@ static struct mansession_session *build_mansession(const struct ast_sockaddr *ad
                return NULL;
        }
 
-       newsession->whitefilters = ao2_container_alloc(1, NULL, NULL);
-       newsession->blackfilters = ao2_container_alloc(1, NULL, NULL);
+       newsession->whitefilters = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+       newsession->blackfilters = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
        if (!newsession->whitefilters || !newsession->blackfilters) {
                ao2_ref(newsession, -1);
                return NULL;
        }
 
-       newsession->fd = -1;
        newsession->waiting_thread = AST_PTHREADT_NULL;
        newsession->writetimeout = 100;
        newsession->send_events = -1;
@@ -1907,14 +2319,20 @@ static int manager_displayconnects(struct mansession_session *session)
        return ret;
 }
 
+#ifdef AST_XML_DOCS
+static void print_event_instance(struct ast_cli_args *a, struct ast_xml_doc_item *instance);
+#endif
+
 static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct manager_action *cur;
        struct ast_str *authority;
-       int num, l, which;
-       char *ret = NULL;
+       int num;
+       int l;
+       const char *auth_str;
 #ifdef AST_XML_DOCS
-       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64], arguments_title[64], privilege_title[64];
+       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64];
+       char arguments_title[64], privilege_title[64], final_response_title[64], list_responses_title[64];
 #endif
 
        switch (cmd) {
@@ -1926,22 +2344,23 @@ static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_
                return NULL;
        case CLI_GENERATE:
                l = strlen(a->word);
-               which = 0;
                AST_RWLIST_RDLOCK(&actions);
                AST_RWLIST_TRAVERSE(&actions, cur, list) {
-                       if (!strncasecmp(a->word, cur->action, l) && ++which > a->n) {
-                               ret = ast_strdup(cur->action);
-                               break;  /* make sure we exit even if ast_strdup() returns NULL */
+                       if (!strncasecmp(a->word, cur->action, l)) {
+                               if (ast_cli_completion_add(ast_strdup(cur->action))) {
+                                       break;
+                               }
                        }
                }
                AST_RWLIST_UNLOCK(&actions);
-               return ret;
+               return NULL;
        }
-       authority = ast_str_alloca(80);
        if (a->argc < 4) {
                return CLI_SHOWUSAGE;
        }
 
+       authority = ast_str_alloca(MAX_AUTH_PERM_STRING);
+
 #ifdef AST_XML_DOCS
        /* setup the titles */
        term_color(synopsis_title, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
@@ -1950,13 +2369,15 @@ static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_
        term_color(seealso_title, "[See Also]\n", COLOR_MAGENTA, 0, 40);
        term_color(arguments_title, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
        term_color(privilege_title, "[Privilege]\n", COLOR_MAGENTA, 0, 40);
+       term_color(final_response_title, "[Final Response]\n", COLOR_MAGENTA, 0, 40);
+       term_color(list_responses_title, "[List Responses]\n", COLOR_MAGENTA, 0, 40);
 #endif
 
        AST_RWLIST_RDLOCK(&actions);
        AST_RWLIST_TRAVERSE(&actions, cur, list) {
                for (num = 3; num < a->argc; num++) {
                        if (!strcasecmp(cur->action, a->argv[num])) {
-                               authority_to_str(cur->authority, &authority);
+                               auth_str = authority_to_str(cur->authority, &authority);
 
 #ifdef AST_XML_DOCS
                                if (cur->docsrc == AST_XML_DOC) {
@@ -1965,20 +2386,65 @@ static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_
                                        char *description = ast_xmldoc_printable(S_OR(cur->description, "Not available"), 1);
                                        char *arguments = ast_xmldoc_printable(S_OR(cur->arguments, "Not available"), 1);
                                        char *seealso = ast_xmldoc_printable(S_OR(cur->seealso, "Not available"), 1);
-                                       char *privilege = ast_xmldoc_printable(S_OR(authority->str, "Not available"), 1);
-                                       ast_cli(a->fd, "%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n",
+                                       char *privilege = ast_xmldoc_printable(S_OR(auth_str, "Not available"), 1);
+                                       char *responses = ast_xmldoc_printable("None", 1);
+
+                                       if (!syntax || !synopsis || !description || !arguments
+                                                       || !seealso || !privilege || !responses) {
+                                               ast_free(syntax);
+                                               ast_free(synopsis);
+                                               ast_free(description);
+                                               ast_free(arguments);
+                                               ast_free(seealso);
+                                               ast_free(privilege);
+                                               ast_free(responses);
+                                               ast_cli(a->fd, "Allocation failure.\n");
+                                               AST_RWLIST_UNLOCK(&actions);
+
+                                               return CLI_FAILURE;
+                                       }
+
+                                       ast_cli(a->fd, "%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s",
                                                syntax_title, syntax,
                                                synopsis_title, synopsis,
                                                description_title, description,
                                                arguments_title, arguments,
                                                seealso_title, seealso,
-                                               privilege_title, privilege);
+                                               privilege_title, privilege,
+                                               list_responses_title);
+
+                                       if (!cur->list_responses) {
+                                               ast_cli(a->fd, "%s\n\n", responses);
+                                       } else {
+                                               struct ast_xml_doc_item *temp;
+                                               for (temp = cur->list_responses; temp; temp = AST_LIST_NEXT(temp, next)) {
+                                                       ast_cli(a->fd, "Event: %s\n", temp->name);
+                                                       print_event_instance(a, temp);
+                                               }
+                                       }
+
+                                       ast_cli(a->fd, "%s", final_response_title);
+
+                                       if (!cur->final_response) {
+                                               ast_cli(a->fd, "%s\n\n", responses);
+                                       } else {
+                                               ast_cli(a->fd, "Event: %s\n", cur->final_response->name);
+                                               print_event_instance(a, cur->final_response);
+                                       }
+
+                                       ast_free(syntax);
+                                       ast_free(synopsis);
+                                       ast_free(description);
+                                       ast_free(arguments);
+                                       ast_free(seealso);
+                                       ast_free(privilege);
+                                       ast_free(responses);
                                } else
 #endif
                                {
                                        ast_cli(a->fd, "Action: %s\nSynopsis: %s\nPrivilege: %s\n%s\n",
                                                cur->action, cur->synopsis,
-                                               authority->str,
+                                               auth_str,
                                                S_OR(cur->description, ""));
                                }
                        }
@@ -2017,10 +2483,9 @@ static char *handle_mandebug(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_manager_user *user = NULL;
-       int l, which;
-       char *ret = NULL;
-       struct ast_str *rauthority = ast_str_alloca(128);
-       struct ast_str *wauthority = ast_str_alloca(128);
+       int l;
+       struct ast_str *rauthority = ast_str_alloca(MAX_AUTH_PERM_STRING);
+       struct ast_str *wauthority = ast_str_alloca(MAX_AUTH_PERM_STRING);
        struct ast_variable *v;
 
        switch (cmd) {
@@ -2032,19 +2497,19 @@ static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli
                return NULL;
        case CLI_GENERATE:
                l = strlen(a->word);
-               which = 0;
                if (a->pos != 3) {
                        return NULL;
                }
                AST_RWLIST_RDLOCK(&users);
                AST_RWLIST_TRAVERSE(&users, user, list) {
-                       if ( !strncasecmp(a->word, user->username, l) && ++which > a->n ) {
-                               ret = ast_strdup(user->username);
-                               break;
+                       if (!strncasecmp(a->word, user->username, l)) {
+                               if (ast_cli_completion_add(ast_strdup(user->username))) {
+                                       break;
+                               }
                        }
                }
                AST_RWLIST_UNLOCK(&users);
-               return ret;
+               return NULL;
        }
 
        if (a->argc != 4) {
@@ -2068,7 +2533,7 @@ static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli
                "        write perm: %s\n"
                "   displayconnects: %s\n"
                "allowmultiplelogin: %s\n",
-               (user->username ? user->username : "(N/A)"),
+               S_OR(user->username, "(N/A)"),
                (user->secret ? "<Set>" : "(N/A)"),
                ((user->acl && !ast_acl_list_is_empty(user->acl)) ? "yes" : "no"),
                user_authority_to_str(user->readperm, &rauthority),
@@ -2204,7 +2669,7 @@ static char *handle_showmanconn(struct ast_cli_entry *e, int cmd, struct ast_cli
                                ast_sockaddr_stringify_addr(&session->addr),
                                (int) (session->sessionstart),
                                (int) (now - session->sessionstart),
-                               session->fd,
+                               session->stream ? ast_iostream_get_fd(session->stream) : -1,
                                session->inuse,
                                session->readperm,
                                session->writeperm);
@@ -2246,6 +2711,8 @@ static char *handle_showmaneventq(struct ast_cli_entry *e, int cmd, struct ast_c
        return CLI_SUCCESS;
 }
 
+static int reload_module(void);
+
 /*! \brief CLI command manager reload */
 static char *handle_manager_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
@@ -2262,7 +2729,7 @@ static char *handle_manager_reload(struct ast_cli_entry *e, int cmd, struct ast_
        if (a->argc > 2) {
                return CLI_SHOWUSAGE;
        }
-       reload_manager();
+       reload_module();
        return CLI_SUCCESS;
 }
 
@@ -2339,6 +2806,34 @@ const char *astman_get_header(const struct message *m, char *var)
 }
 
 /*!
+ * \brief Append additional headers into the message structure from params.
+ *
+ * \note You likely want to initialize m->hdrcount to 0 before calling this.
+ */
+static void astman_append_headers(struct message *m, const struct ast_variable *params)
+{
+       const struct ast_variable *v;
+
+       for (v = params; v && m->hdrcount < ARRAY_LEN(m->headers); v = v->next) {
+               if (ast_asprintf((char**)&m->headers[m->hdrcount], "%s: %s", v->name, v->value) > -1) {
+                       ++m->hdrcount;
+               }
+       }
+}
+
+/*!
+ * \brief Free headers inside message structure, but not the message structure itself.
+ */
+static void astman_free_headers(struct message *m)
+{
+       while (m->hdrcount) {
+               --m->hdrcount;
+               ast_free((void *) m->headers[m->hdrcount]);
+               m->headers[m->hdrcount] = NULL;
+       }
+}
+
+/*!
  * \internal
  * \brief Process one "Variable:" header value string.
  *
@@ -2467,49 +2962,53 @@ int ast_hook_send_action(struct manager_custom_hook *hook, const char *msg)
        }
 
        action = astman_get_header(&m, "Action");
-       if (strcasecmp(action, "login")) {
+
+       do {
+               if (!strcasecmp(action, "login")) {
+                       break;
+               }
+
                act_found = action_find(action);
-               if (act_found) {
-                       /*
-                        * we have to simulate a session for this action request
-                        * to be able to pass it down for processing
-                        * This is necessary to meet the previous design of manager.c
-                        */
-                       s.hook = hook;
-                       s.f = (void*)1; /* set this to something so our request will make it through all functions that test it*/
+               if (!act_found) {
+                       break;
+               }
 
-                       ao2_lock(act_found);
-                       if (act_found->registered && act_found->func) {
-                               if (act_found->module) {
-                                       ast_module_ref(act_found->module);
-                               }
-                               ao2_unlock(act_found);
+               /*
+                * we have to simulate a session for this action request
+                * to be able to pass it down for processing
+                * This is necessary to meet the previous design of manager.c
+                */
+               s.hook = hook;
+
+               ret = -1;
+               ao2_lock(act_found);
+               if (act_found->registered && act_found->func) {
+                       struct ast_module *mod_ref = ast_module_running_ref(act_found->module);
+
+                       ao2_unlock(act_found);
+                       /* If the action is in a module it must be running. */
+                       if (!act_found->module || mod_ref) {
                                ret = act_found->func(&s, &m);
-                               ao2_lock(act_found);
-                               if (act_found->module) {
-                                       ast_module_unref(act_found->module);
-                               }
-                       } else {
-                               ret = -1;
+                               ast_module_unref(mod_ref);
                        }
+               } else {
                        ao2_unlock(act_found);
-                       ao2_t_ref(act_found, -1, "done with found action object");
                }
-       }
+               ao2_t_ref(act_found, -1, "done with found action object");
+       } while (0);
+
        ast_free(dup_str);
        return ret;
 }
 
-
 /*!
  * helper function to send a string to the socket.
  * Return -1 on error (e.g. buffer full).
  */
 static int send_string(struct mansession *s, char *string)
 {
-       int res;
-       FILE *f = s->f ? s->f : s->session->f;
-       int fd = s->f ? s->fd : s->session->fd;
+       struct ast_iostream *stream;
+       int len, res;
 
        /* It's a result from one of the hook's action invocation */
        if (s->hook) {
@@ -2521,7 +3020,14 @@ static int send_string(struct mansession *s, char *string)
                return 0;
        }
 
-       if ((res = ast_careful_fwrite(f, fd, string, strlen(string), s->session->writetimeout))) {
+       stream = s->stream ? s->stream : s->session->stream;
+
+       len = strlen(string);
+       ast_iostream_set_timeout_inactivity(stream, s->session->writetimeout);
+       res = ast_iostream_write(stream, string, len);
+       ast_iostream_set_timeout_disable(stream);
+
+       if (res < len) {
                s->write_error = 1;
        }
 
@@ -2547,6 +3053,7 @@ AST_THREADSTORAGE(userevent_buf);
  */
 void astman_append(struct mansession *s, const char *fmt, ...)
 {
+       int res;
        va_list ap;
        struct ast_str *buf;
 
@@ -2555,13 +3062,16 @@ void astman_append(struct mansession *s, const char *fmt, ...)
        }
 
        va_start(ap, fmt);
-       ast_str_set_va(&buf, 0, fmt, ap);
+       res = ast_str_set_va(&buf, 0, fmt, ap);
        va_end(ap);
+       if (res == AST_DYNSTR_BUILD_FAILED) {
+               return;
+       }
 
-       if (s->f != NULL || s->session->f != NULL) {
+       if (s->hook || (s->tcptls_session != NULL && s->tcptls_session->stream != NULL)) {
                send_string(s, ast_str_buffer(buf));
        } else {
-               ast_verbose("fd == -1 in astman_append, should not happen\n");
+               ast_verbose("No connection stream in astman_append, should not happen\n");
        }
 }
 
@@ -2616,6 +3126,7 @@ void astman_send_error(struct mansession *s, const struct message *m, char *erro
 
 void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...)
 {
+       int res;
        va_list ap;
        struct ast_str *buf;
        char *msg;
@@ -2625,8 +3136,11 @@ void astman_send_error_va(struct mansession *s, const struct message *m, const c
        }
 
        va_start(ap, fmt);
-       ast_str_set_va(&buf, 0, fmt, ap);
+       res = ast_str_set_va(&buf, 0, fmt, ap);
        va_end(ap);
+       if (res == AST_DYNSTR_BUILD_FAILED) {
+               return;
+       }
 
        /* astman_append will use the same underlying buffer, so copy the message out
         * before sending the response */
@@ -2652,6 +3166,25 @@ void astman_send_listack(struct mansession *s, const struct message *m, char *ms
        astman_send_response_full(s, m, "Success", msg, listflag);
 }
 
+void astman_send_list_complete_start(struct mansession *s, const struct message *m, const char *event_name, int count)
+{
+       const char *id = astman_get_header(m, "ActionID");
+
+       astman_append(s, "Event: %s\r\n", event_name);
+       if (!ast_strlen_zero(id)) {
+               astman_append(s, "ActionID: %s\r\n", id);
+       }
+       astman_append(s,
+               "EventList: Complete\r\n"
+               "ListItems: %d\r\n",
+               count);
+}
+
+void astman_send_list_complete_end(struct mansession *s)
+{
+       astman_append(s, "\r\n");
+}
+
 /*! \brief Lock the 'mansession' structure. */
 static void mansession_lock(struct mansession *s)
 {
@@ -2943,7 +3476,7 @@ static int authenticate(struct mansession *s, const struct message *m)
                        MD5Update(&md5, (unsigned char *) user->secret, strlen(user->secret));
                        MD5Final(digest, &md5);
                        for (x = 0; x < 16; x++)
-                               len += sprintf(md5key + len, "%2.2x", (unsigned)digest[x]);
+                               len += sprintf(md5key + len, "%02hhx", digest[x]);
                        if (!strcmp(md5key, key)) {
                                error = 0;
                        } else {
@@ -3027,9 +3560,11 @@ static int action_getconfig(struct mansession *s, const struct message *m)
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
        const char *category = astman_get_header(m, "Category");
+       const char *filter = astman_get_header(m, "Filter");
+       const char *category_name;
        int catcount = 0;
        int lineno = 0;
-       char *cur_category = NULL;
+       struct ast_category *cur_category = NULL;
        struct ast_variable *v;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
 
@@ -3037,6 +3572,7 @@ static int action_getconfig(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Filename not specified");
                return 0;
        }
+
        cfg = ast_config_load2(fn, "manager", config_flags);
        if (cfg == CONFIG_STATUS_FILEMISSING) {
                astman_send_error(s, m, "Config file not found");
@@ -3047,19 +3583,34 @@ static int action_getconfig(struct mansession *s, const struct message *m)
        }
 
        astman_start_ack(s, m);
-       while ((cur_category = ast_category_browse(cfg, cur_category))) {
-               if (ast_strlen_zero(category) || (!ast_strlen_zero(category) && !strcmp(category, cur_category))) {
-                       lineno = 0;
-                       astman_append(s, "Category-%06d: %s\r\n", catcount, cur_category);
-                       for (v = ast_variable_browse(cfg, cur_category); v; v = v->next) {
-                               astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
-                       }
-                       catcount++;
+       while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) {
+               struct ast_str *templates;
+
+               category_name = ast_category_get_name(cur_category);
+               lineno = 0;
+               astman_append(s, "Category-%06d: %s\r\n", catcount, category_name);
+
+               if (ast_category_is_template(cur_category)) {
+                       astman_append(s, "IsTemplate-%06d: %d\r\n", catcount, 1);
+               }
+
+               if ((templates = ast_category_get_templates(cur_category))
+                       && ast_str_strlen(templates) > 0) {
+                       astman_append(s, "Templates-%06d: %s\r\n", catcount, ast_str_buffer(templates));
+                       ast_free(templates);
+               }
+
+               for (v = ast_category_first(cur_category); v; v = v->next) {
+                       astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
                }
+
+               catcount++;
        }
+
        if (!ast_strlen_zero(category) && catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */
                astman_append(s, "No categories found\r\n");
        }
+
        ast_config_destroy(cfg);
        astman_append(s, "\r\n");
 
@@ -3070,7 +3621,8 @@ static int action_listcategories(struct mansession *s, const struct message *m)
 {
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
-       char *category = NULL;
+       const char *match = astman_get_header(m, "Match");
+       struct ast_category *category = NULL;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
        int catcount = 0;
 
@@ -3078,6 +3630,7 @@ static int action_listcategories(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Filename not specified");
                return 0;
        }
+
        if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
                astman_send_error(s, m, "Config file not found");
                return 0;
@@ -3085,23 +3638,23 @@ static int action_listcategories(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Config file has invalid format");
                return 0;
        }
+
        astman_start_ack(s, m);
-       while ((category = ast_category_browse(cfg, category))) {
-               astman_append(s, "Category-%06d: %s\r\n", catcount, category);
+       while ((category = ast_category_browse_filtered(cfg, NULL, category, match))) {
+               astman_append(s, "Category-%06d: %s\r\n", catcount, ast_category_get_name(category));
                catcount++;
        }
+
        if (catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */
                astman_append(s, "Error: no categories found\r\n");
        }
+
        ast_config_destroy(cfg);
        astman_append(s, "\r\n");
 
        return 0;
 }
 
-
-
-
 /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
 static void json_escape(char *out, const char *in)
 {
@@ -3136,7 +3689,10 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 {
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
-       char *category = NULL;
+       const char *filter = astman_get_header(m, "Filter");
+       const char *category = astman_get_header(m, "Category");
+       struct ast_category *cur_category = NULL;
+       const char *category_name;
        struct ast_variable *v;
        int comma1 = 0;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
@@ -3156,14 +3712,30 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 
        astman_start_ack(s, m);
        astman_append(s, "JSON: {");
-       while ((category = ast_category_browse(cfg, category))) {
+       while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) {
                int comma2 = 0;
+               struct ast_str *templates;
 
+               category_name = ast_category_get_name(cur_category);
                astman_append(s, "%s\"", comma1 ? "," : "");
-               astman_append_json(s, category);
-               astman_append(s, "\":[");
+               astman_append_json(s, category_name);
+               astman_append(s, "\":{");
                comma1 = 1;
-               for (v = ast_variable_browse(cfg, category); v; v = v->next) {
+
+               if (ast_category_is_template(cur_category)) {
+                       astman_append(s, "\"istemplate\":1");
+                       comma2 = 1;
+               }
+
+               if ((templates = ast_category_get_templates(cur_category))
+                       && ast_str_strlen(templates) > 0) {
+                       astman_append(s, "%s", comma2 ? "," : "");
+                       astman_append(s, "\"templates\":\"%s\"", ast_str_buffer(templates));
+                       ast_free(templates);
+                       comma2 = 1;
+               }
+
+               for (v = ast_category_first(cur_category); v; v = v->next) {
                        astman_append(s, "%s\"", comma2 ? "," : "");
                        astman_append_json(s, v->name);
                        astman_append(s, "\":\"");
@@ -3171,7 +3743,8 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
                        astman_append(s, "\"");
                        comma2 = 1;
                }
-               astman_append(s, "]");
+
+               astman_append(s, "}");
        }
        astman_append(s, "}\r\n\r\n");
 
@@ -3185,19 +3758,28 @@ static enum error_type handle_updates(struct mansession *s, const struct message
 {
        int x;
        char hdr[40];
-       const char *action, *cat, *var, *value, *match, *line;
-       struct ast_category *category;
+       const char *action, *cat, *var, *value, *match, *line, *options;
        struct ast_variable *v;
        struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
        enum error_type result = 0;
 
        for (x = 0; x < 100000; x++) {  /* 100000 = the max number of allowed updates + 1 */
                unsigned int object = 0;
+               char *dupoptions;
+               int allowdups = 0;
+               int istemplate = 0;
+               int ignoreerror = 0;
+               char *inherit = NULL;
+               char *catfilter = NULL;
+               char *token;
+               int foundvar = 0;
+               int foundcat = 0;
+               struct ast_category *category = NULL;
 
                snprintf(hdr, sizeof(hdr), "Action-%06d", x);
                action = astman_get_header(m, hdr);
                if (ast_strlen_zero(action))            /* breaks the for loop if no action header */
-                       break;                          /* this could cause problems if actions come in misnumbered */
+                       break;                                                  /* this could cause problems if actions come in misnumbered */
 
                snprintf(hdr, sizeof(hdr), "Cat-%06d", x);
                cat = astman_get_header(m, hdr);
@@ -3223,38 +3805,131 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                snprintf(hdr, sizeof(hdr), "Line-%06d", x);
                line = astman_get_header(m, hdr);
 
+               snprintf(hdr, sizeof(hdr), "Options-%06d", x);
+               options = astman_get_header(m, hdr);
+               if (!ast_strlen_zero(options)) {
+                       dupoptions = ast_strdupa(options);
+                       while ((token = ast_strsep(&dupoptions, ',', AST_STRSEP_STRIP))) {
+                               if (!strcasecmp("allowdups", token)) {
+                                       allowdups = 1;
+                                       continue;
+                               }
+                               if (!strcasecmp("template", token)) {
+                                       istemplate = 1;
+                                       continue;
+                               }
+                               if (!strcasecmp("ignoreerror", token)) {
+                                       ignoreerror = 1;
+                                       continue;
+                               }
+                               if (ast_begins_with(token, "inherit")) {
+                                       char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       if (c) {
+                                               inherit = ast_strdupa(c);
+                                       }
+                                       continue;
+                               }
+                               if (ast_begins_with(token, "catfilter")) {
+                                       char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       if (c) {
+                                               catfilter = ast_strdupa(c);
+                                       }
+                                       continue;
+                               }
+                       }
+               }
+
                if (!strcasecmp(action, "newcat")) {
-                       if (ast_category_get(cfg,cat)) {        /* check to make sure the cat doesn't */
-                               result = FAILURE_NEWCAT;        /* already exist */
-                               break;
+                       struct ast_category *template;
+                       char *tmpl_name = NULL;
+
+                       if (!allowdups) {
+                               if (ast_category_get(cfg, cat, "TEMPLATES=include")) {
+                                       if (ignoreerror) {
+                                               continue;
+                                       } else {
+                                               result = FAILURE_NEWCAT;        /* already exist */
+                                               break;
+                                       }
+                               }
                        }
-                       if (!(category = ast_category_new(cat, dfn, -1))) {
+
+                       if (istemplate) {
+                               category = ast_category_new_template(cat, dfn, -1);
+                       } else {
+                               category = ast_category_new(cat, dfn, -1);
+                       }
+
+                       if (!category) {
                                result = FAILURE_ALLOCATION;
                                break;
                        }
-                       if (ast_strlen_zero(match)) {
-                               ast_category_append(cfg, category);
-                       } else {
-                               ast_category_insert(cfg, category, match);
+
+                       if (inherit) {
+                               while ((tmpl_name = ast_strsep(&inherit, ',', AST_STRSEP_STRIP))) {
+                                       if ((template = ast_category_get(cfg, tmpl_name, "TEMPLATES=restrict"))) {
+                                               if (ast_category_inherit(category, template)) {
+                                                       result = FAILURE_ALLOCATION;
+                                                       break;
+                                               }
+                                       } else {
+                                               ast_category_destroy(category);
+                                               category = NULL;
+                                               result = FAILURE_TEMPLATE;      /* template not found */
+                                               break;
+                                       }
+                               }
+                       }
+
+                       if (category != NULL) {
+                               if (ast_strlen_zero(match)) {
+                                       ast_category_append(cfg, category);
+                               } else {
+                                       if (ast_category_insert(cfg, category, match)) {
+                                               ast_category_destroy(category);
+                                               result = FAILURE_NEWCAT;
+                                               break;
+                                       }
+                               }
                        }
                } else if (!strcasecmp(action, "renamecat")) {
                        if (ast_strlen_zero(value)) {
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
+
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               ast_category_rename(category, value);
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
                                result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       ast_category_rename(category, value);
                } else if (!strcasecmp(action, "delcat")) {
-                       if (ast_category_delete(cfg, cat)) {
-                               result = FAILURE_DELCAT;
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               category = ast_category_delete(cfg, category);
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat && !ignoreerror) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
                } else if (!strcasecmp(action, "emptycat")) {
-                       if (ast_category_empty(cfg, cat)) {
-                               result = FAILURE_EMPTYCAT;
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               ast_category_empty(category);
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
                } else if (!strcasecmp(action, "update")) {
@@ -3262,11 +3937,22 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg,cat))) {
+
+                       foundcat = 0;
+                       foundvar = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!ast_variable_update(category, var, value, match, object)) {
+                                       foundvar = 1;
+                               }
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
                                result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       if (ast_variable_update(category, var, value, match, object)) {
+
+                       if (!foundvar) {
                                result = FAILURE_UPDATE;
                                break;
                        }
@@ -3275,12 +3961,23 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
+
+                       foundcat = 0;
+                       foundvar = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!ast_variable_delete(category, var, match, line)) {
+                                       foundvar = 1;
+                               }
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
                                result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       if (ast_variable_delete(category, var, match, line)) {
-                               result = FAILURE_DELETE;
+
+                       if (!foundvar && !ignoreerror) {
+                               result = FAILURE_UPDATE;
                                break;
                        }
                } else if (!strcasecmp(action, "append")) {
@@ -3288,32 +3985,44 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
-                               result = UNKNOWN_CATEGORY;
-                               break;
+
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!(v = ast_variable_new(var, value, dfn))) {
+                                       result = FAILURE_ALLOCATION;
+                                       break;
+                               }
+                               if (object || (match && !strcasecmp(match, "object"))) {
+                                       v->object = 1;
+                               }
+                               ast_variable_append(category, v);
+                               foundcat = 1;
                        }
-                       if (!(v = ast_variable_new(var, value, dfn))) {
-                               result = FAILURE_ALLOCATION;
+
+                       if (!foundcat) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       if (object || (match && !strcasecmp(match, "object"))) {
-                               v->object = 1;
-                       }
-                       ast_variable_append(category, v);
                } else if (!strcasecmp(action, "insert")) {
                        if (ast_strlen_zero(var) || ast_strlen_zero(line)) {
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
-                               result = UNKNOWN_CATEGORY;
-                               break;
+
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!(v = ast_variable_new(var, value, dfn))) {
+                                       result = FAILURE_ALLOCATION;
+                                       break;
+                               }
+                               ast_variable_insert(category, v, line);
+                               foundcat = 1;
                        }
-                       if (!(v = ast_variable_new(var, value, dfn))) {
-                               result = FAILURE_ALLOCATION;
+
+                       if (!foundcat) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       ast_variable_insert(category, v, line);
                }
                else {
                        ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action);
@@ -3333,6 +4042,8 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
        const char *dfn = astman_get_header(m, "DstFilename");
        int res;
        const char *rld = astman_get_header(m, "Reload");
+       int preserve_effective_context = CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT;
+       const char *preserve_effective_context_string = astman_get_header(m, "PreserveEffectiveContext");
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
        enum error_type result;
 
@@ -3350,7 +4061,10 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
        result = handle_updates(s, m, cfg, dfn);
        if (!result) {
                ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
-               res = ast_config_text_file_save(dfn, cfg, "Manager");
+               if (!ast_strlen_zero(preserve_effective_context_string) && !ast_true(preserve_effective_context_string)) {
+                       preserve_effective_context = CONFIG_SAVE_FLAG_NONE;
+               }
+               res = ast_config_text_file_save2(dfn, cfg, "Manager", preserve_effective_context);
                ast_config_destroy(cfg);
                if (res) {
                        astman_send_error(s, m, "Save of config failed");
@@ -3399,6 +4113,9 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
                case FAILURE_APPEND:
                        astman_send_error(s, m, "Append did not complete successfully");
                        break;
+               case FAILURE_TEMPLATE:
+                       astman_send_error(s, m, "Template category not found");
+                       break;
                }
        }
        return 0;
@@ -3495,7 +4212,7 @@ static int action_waitevent(struct mansession *s, const struct message *m)
                        break;
                }
                if (s->session->managerid == 0) {       /* AMI session */
-                       if (ast_wait_for_input(s->session->fd, 1000)) {
+                       if (ast_wait_for_input(ast_iostream_get_fd(s->session->stream), 1000)) {
                                break;
                        }
                } else {        /* HTTP session */
@@ -3509,8 +4226,9 @@ static int action_waitevent(struct mansession *s, const struct message *m)
                struct eventqent *eqe = s->session->last_ev;
                astman_send_response(s, m, "Success", "Waiting for Event completed.");
                while ((eqe = advance_event(eqe))) {
-                       if (((s->session->readperm & eqe->category) == eqe->category) &&
-                           ((s->session->send_events & eqe->category) == eqe->category)) {
+                       if (((s->session->readperm & eqe->category) == eqe->category)
+                               && ((s->session->send_events & eqe->category) == eqe->category)
+                               && match_filter(s, eqe->eventdata)) {
                                astman_append(s, "%s", eqe->eventdata);
                        }
                        s->session->last_ev = eqe;
@@ -3531,7 +4249,7 @@ static int action_waitevent(struct mansession *s, const struct message *m)
 static int action_listcommands(struct mansession *s, const struct message *m)
 {
        struct manager_action *cur;
-       struct ast_str *temp = ast_str_alloca(256);
+       struct ast_str *temp = ast_str_alloca(MAX_AUTH_PERM_STRING);
 
        astman_start_ack(s, m);
        AST_RWLIST_RDLOCK(&actions);
@@ -3618,12 +4336,30 @@ static int action_login(struct mansession *s, const struct message *m)
        }
        astman_send_ack(s, m, "Authentication accepted");
        if ((s->session->send_events & EVENT_FLAG_SYSTEM)
+               && (s->session->readperm & EVENT_FLAG_SYSTEM)
                && ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
-               struct ast_str *auth = ast_str_alloca(80);
+               struct ast_str *auth = ast_str_alloca(MAX_AUTH_PERM_STRING);
                const char *cat_str = authority_to_str(EVENT_FLAG_SYSTEM, &auth);
+               long uptime = 0;
+               long lastreloaded = 0;
+               struct timeval tmp;
+               struct timeval curtime = ast_tvnow();
+
+               if (ast_startuptime.tv_sec) {
+                       tmp = ast_tvsub(curtime, ast_startuptime);
+                       uptime = tmp.tv_sec;
+               }
+
+               if (ast_lastreloadtime.tv_sec) {
+                       tmp = ast_tvsub(curtime, ast_lastreloadtime);
+                       lastreloaded = tmp.tv_sec;
+               }
+
                astman_append(s, "Event: FullyBooted\r\n"
                        "Privilege: %s\r\n"
-                       "Status: Fully Booted\r\n\r\n", cat_str);
+                       "Uptime: %ld\r\n"
+                       "LastReload: %ld\r\n"
+                       "Status: Fully Booted\r\n\r\n", cat_str, uptime, lastreloaded);
        }
        return 0;
 }
@@ -3758,12 +4494,8 @@ static int action_hangup(struct mansession *s, const struct message *m)
        regfree(&regexbuf);
        ast_free(regex_string);
 
-       astman_append(s,
-               "Event: ChannelsHungupListComplete\r\n"
-               "EventList: Complete\r\n"
-               "ListItems: %d\r\n"
-               "%s"
-               "\r\n", channels_matched, idText);
+       astman_send_list_complete_start(s, m, "ChannelsHungupListComplete", channels_matched);
+       astman_send_list_complete_end(s);
 
        return 0;
 }
@@ -3853,19 +4585,130 @@ static int action_getvar(struct mansession *s, const struct message *m)
        return 0;
 }
 
+static void generate_status(struct mansession *s, struct ast_channel *chan, char **vars, int varc, int all_variables, char *id_text, int *count)
+{
+       struct timeval now;
+       long elapsed_seconds;
+       struct ast_bridge *bridge;
+       RAII_VAR(struct ast_str *, variable_str, NULL, ast_free);
+       struct ast_str *write_transpath = ast_str_alloca(256);
+       struct ast_str *read_transpath = ast_str_alloca(256);
+       struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
+       struct ast_party_id effective_id;
+       int i;
+       RAII_VAR(struct ast_channel_snapshot *, snapshot,
+               ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan)),
+               ao2_cleanup);
+       RAII_VAR(struct ast_str *, snapshot_str, NULL, ast_free);
+
+       if (!snapshot) {
+               return;
+       }
+
+       snapshot_str = ast_manager_build_channel_state_string(snapshot);
+       if (!snapshot_str) {
+               return;
+       }
+
+       if (all_variables) {
+               variable_str = ast_str_create(2048);
+       } else {
+               variable_str = ast_str_create(1024);
+       }
+       if (!variable_str) {
+               return;
+       }
+
+       now = ast_tvnow();
+       elapsed_seconds = ast_tvdiff_sec(now, ast_channel_creationtime(chan));
+
+       /* Even if all_variables has been specified, explicitly requested variables
+        * may be global variables or dialplan functions */
+       for (i = 0; i < varc; i++) {
+               char valbuf[512], *ret = NULL;
+
+               if (vars[i][strlen(vars[i]) - 1] == ')') {
+                       if (ast_func_read(chan, vars[i], valbuf, sizeof(valbuf)) < 0) {
+                               valbuf[0] = '\0';
+                       }
+                       ret = valbuf;
+               } else {
+                       pbx_retrieve_variable(chan, vars[i], &ret, valbuf, sizeof(valbuf), NULL);
+               }
+
+               ast_str_append(&variable_str, 0, "Variable: %s=%s\r\n", vars[i], ret);
+       }
+
+       /* Walk all channel variables and add them */
+       if (all_variables) {
+               struct ast_var_t *variables;
+
+               AST_LIST_TRAVERSE(ast_channel_varshead(chan), variables, entries) {
+                       ast_str_append(&variable_str, 0, "Variable: %s=%s\r\n",
+                               ast_var_name(variables), ast_var_value(variables));
+               }
+       }
+
+       bridge = ast_channel_get_bridge(chan);
+       effective_id = ast_channel_connected_effective_id(chan);
+
+       astman_append(s,
+               "Event: Status\r\n"
+               "Privilege: Call\r\n"
+               "%s"
+               "Type: %s\r\n"
+               "DNID: %s\r\n"
+               "EffectiveConnectedLineNum: %s\r\n"
+               "EffectiveConnectedLineName: %s\r\n"
+               "TimeToHangup: %ld\r\n"
+               "BridgeID: %s\r\n"
+               "Application: %s\r\n"
+               "Data: %s\r\n"
+               "Nativeformats: %s\r\n"
+               "Readformat: %s\r\n"
+               "Readtrans: %s\r\n"
+               "Writeformat: %s\r\n"
+               "Writetrans: %s\r\n"
+               "Callgroup: %llu\r\n"
+               "Pickupgroup: %llu\r\n"
+               "Seconds: %ld\r\n"
+               "%s"
+               "%s"
+               "\r\n",
+               ast_str_buffer(snapshot_str),
+               ast_channel_tech(chan)->type,
+               S_OR(ast_channel_dialed(chan)->number.str, ""),
+               S_COR(effective_id.number.valid, effective_id.number.str, "<unknown>"),
+               S_COR(effective_id.name.valid, effective_id.name.str, "<unknown>"),
+               (long)ast_channel_whentohangup(chan)->tv_sec,
+               bridge ? bridge->uniqueid : "",
+               ast_channel_appl(chan),
+               ast_channel_data(chan),
+               ast_format_cap_get_names(ast_channel_nativeformats(chan), &codec_buf),
+               ast_format_get_name(ast_channel_readformat(chan)),
+               ast_translate_path_to_str(ast_channel_readtrans(chan), &read_transpath),
+               ast_format_get_name(ast_channel_writeformat(chan)),
+               ast_translate_path_to_str(ast_channel_writetrans(chan), &write_transpath),
+               ast_channel_callgroup(chan),
+               ast_channel_pickupgroup(chan),
+               (long)elapsed_seconds,
+               ast_str_buffer(variable_str),
+               id_text);
+       ++*count;
+
+       ao2_cleanup(bridge);
+}
+
 /*! \brief Manager "status" command to show channels */
-/* Needs documentation... */
 static int action_status(struct mansession *s, const struct message *m)
 {
        const char *name = astman_get_header(m, "Channel");
        const char *chan_variables = astman_get_header(m, "Variables");
+       const char *all_chan_variables = astman_get_header(m, "AllVariables");
+       int all_variables = 0;
        const char *id = astman_get_header(m, "ActionID");
        char *variables = ast_strdupa(S_OR(chan_variables, ""));
-       struct ast_str *variable_str = ast_str_create(1024);
-       struct ast_str *write_transpath = ast_str_alloca(256);
-       struct ast_str *read_transpath = ast_str_alloca(256);
        struct ast_channel *chan;
-       char nativeformats[256];
        int channels = 0;
        int all = ast_strlen_zero(name); /* set if we want all channels */
        char id_text[256];
@@ -3874,20 +4717,17 @@ static int action_status(struct mansession *s, const struct message *m)
                AST_APP_ARG(name)[100];
        );
 
-       if (!variable_str) {
-               astman_send_error(s, m, "Memory Allocation Failure");
-               return 1;
+       if (!ast_strlen_zero(all_chan_variables)) {
+               all_variables = ast_true(all_chan_variables);
        }
 
        if (!(function_capable_string_allowed_with_auths(variables, s->session->writeperm))) {
-               ast_free(variable_str);
                astman_send_error(s, m, "Status Access Forbidden: Variables");
                return 0;
        }
 
        if (all) {
                if (!(it_chans = ast_channel_iterator_all_new())) {
-                       ast_free(variable_str);
                        astman_send_error(s, m, "Memory Allocation Failure");
                        return 1;
                }
@@ -3896,12 +4736,11 @@ static int action_status(struct mansession *s, const struct message *m)
                chan = ast_channel_get_by_name(name);
                if (!chan) {
                        astman_send_error(s, m, "No such channel");
-                       ast_free(variable_str);
                        return 0;
                }
        }
 
-       astman_send_ack(s, m, "Channel status will follow");
+       astman_send_listack(s, m, "Channel status will follow", "start");
 
        if (!ast_strlen_zero(id)) {
                snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
@@ -3915,106 +4754,9 @@ static int action_status(struct mansession *s, const struct message *m)
 
        /* if we look by name, we break after the first iteration */
        for (; chan; all ? chan = ast_channel_iterator_next(it_chans) : 0) {
-               struct timeval now;
-               long elapsed_seconds;
-               struct ast_bridge *bridge;
-
                ast_channel_lock(chan);
 
-               now = ast_tvnow();
-               elapsed_seconds = ast_tvdiff_sec(now, ast_channel_creationtime(chan));
-
-               if (!ast_strlen_zero(chan_variables)) {
-                       int i;
-                       ast_str_reset(variable_str);
-                       for (i = 0; i < vars.argc; i++) {
-                               char valbuf[512], *ret = NULL;
-
-                               if (vars.name[i][strlen(vars.name[i]) - 1] == ')') {
-                                       if (ast_func_read(chan, vars.name[i], valbuf, sizeof(valbuf)) < 0) {
-                                               valbuf[0] = '\0';
-                                       }
-                                       ret = valbuf;
-                               } else {
-                                       pbx_retrieve_variable(chan, vars.name[i], &ret, valbuf, sizeof(valbuf), NULL);
-                               }
-
-                               ast_str_append(&variable_str, 0, "Variable: %s=%s\r\n", vars.name[i], ret);
-                       }
-               }
-
-               channels++;
-
-               bridge = ast_channel_get_bridge(chan);
-
-               astman_append(s,
-                       "Event: Status\r\n"
-                       "Privilege: Call\r\n"
-                       "Channel: %s\r\n"
-                       "ChannelState: %u\r\n"
-                       "ChannelStateDesc: %s\r\n"
-                       "CallerIDNum: %s\r\n"
-                       "CallerIDName: %s\r\n"
-                       "ConnectedLineNum: %s\r\n"
-                       "ConnectedLineName: %s\r\n"
-                       "Accountcode: %s\r\n"
-                       "Context: %s\r\n"
-                       "Exten: %s\r\n"
-                       "Priority: %d\r\n"
-                       "Uniqueid: %s\r\n"
-                       "Type: %s\r\n"
-                       "DNID: %s\r\n"
-                       "EffectiveConnectedLineNum: %s\r\n"
-                       "EffectiveConnectedLineName: %s\r\n"
-                       "TimeToHangup: %ld\r\n"
-                       "BridgeID: %s\r\n"
-                       "Linkedid: %s\r\n"
-                       "Application: %s\r\n"
-                       "Data: %s\r\n"
-                       "Nativeformats: %s\r\n"
-                       "Readformat: %s\r\n"
-                       "Readtrans: %s\r\n"
-                       "Writeformat: %s\r\n"
-                       "Writetrans: %s\r\n"
-                       "Callgroup: %llu\r\n"
-                       "Pickupgroup: %llu\r\n"
-                       "Seconds: %ld\r\n"
-                       "%s"
-                       "%s"
-                       "\r\n",
-                       ast_channel_name(chan),
-                       ast_channel_state(chan),
-                       ast_state2str(ast_channel_state(chan)),
-                       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"),
-                       S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>"),
-                       S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, "<unknown>"),
-                       S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, "<unknown>"),
-                       ast_channel_accountcode(chan),
-                       ast_channel_context(chan),
-                       ast_channel_exten(chan),
-                       ast_channel_priority(chan),
-                       ast_channel_uniqueid(chan),
-                       ast_channel_tech(chan)->type,
-                       S_OR(ast_channel_dialed(chan)->number.str, ""),
-                       S_COR(ast_channel_connected_effective_id(chan).number.valid, ast_channel_connected_effective_id(chan).number.str, "<unknown>"),
-                       S_COR(ast_channel_connected_effective_id(chan).name.valid, ast_channel_connected_effective_id(chan).name.str, "<unknown>"),
-                       ast_channel_whentohangup(chan)->tv_sec,
-                       bridge ? bridge->uniqueid : "",
-                       ast_channel_linkedid(chan),
-                       ast_channel_appl(chan),
-                       ast_channel_data(chan),
-                       ast_getformatname_multiple(nativeformats, sizeof(nativeformats), ast_channel_nativeformats(chan)),
-                       ast_getformatname(ast_channel_readformat(chan)),
-                       ast_translate_path_to_str(ast_channel_readtrans(chan), &read_transpath),
-                       ast_getformatname(ast_channel_writeformat(chan)),
-                       ast_translate_path_to_str(ast_channel_writetrans(chan), &write_transpath),
-                       ast_channel_callgroup(chan),
-                       ast_channel_pickupgroup(chan),
-                       (long)elapsed_seconds,
-                       ast_str_buffer(variable_str),
-                       id_text);
-
-               ao2_cleanup(bridge);
+               generate_status(s, chan, vars.name, vars.argc, all_variables, id_text, &channels);
 
                ast_channel_unlock(chan);
                chan = ast_channel_unref(chan);
@@ -4024,23 +4766,22 @@ static int action_status(struct mansession *s, const struct message *m)
                ast_channel_iterator_destroy(it_chans);
        }
 
-       astman_append(s,
-               "Event: StatusComplete\r\n"
-               "%s"
-               "Items: %d\r\n"
-               "\r\n", id_text, channels);
-
-       ast_free(variable_str);
+       astman_send_list_complete_start(s, m, "StatusComplete", channels);
+       astman_append(s, "Items: %d\r\n", channels);
+       astman_send_list_complete_end(s);
 
        return 0;
 }
 
 static int action_sendtext(struct mansession *s, const struct message *m)
 {
-       struct ast_channel *c = NULL;
+       struct ast_channel *c;
        const char *name = astman_get_header(m, "Channel");
        const char *textmsg = astman_get_header(m, "Message");
-       int res = 0;
+       struct ast_control_read_action_payload *frame_payload;
+       int payload_size;
+       int frame_size;
+       int res;
 
        if (ast_strlen_zero(name)) {
                astman_send_error(s, m, "No channel specified");
@@ -4052,13 +4793,29 @@ static int action_sendtext(struct mansession *s, const struct message *m)
                return 0;
        }
 
-       if (!(c = ast_channel_get_by_name(name))) {
+       c = ast_channel_get_by_name(name);
+       if (!c) {
                astman_send_error(s, m, "No such channel");
                return 0;
        }
 
-       res = ast_sendtext(c, textmsg);
-       c = ast_channel_unref(c);
+       payload_size = strlen(textmsg) + 1;
+       frame_size = payload_size + sizeof(*frame_payload);
+
+       frame_payload = ast_malloc(frame_size);
+       if (!frame_payload) {
+               ast_channel_unref(c);
+               astman_send_error(s, m, "Failure");
+               return 0;
+       }
+
+       frame_payload->action = AST_FRAME_READ_ACTION_SEND_TEXT;
+       frame_payload->payload_size = payload_size;
+       memcpy(frame_payload->payload, textmsg, payload_size);
+       res = ast_queue_control_data(c, AST_CONTROL_READ_ACTION, frame_payload, frame_size);
+
+       ast_free(frame_payload);
+       ast_channel_unref(c);
 
        if (res >= 0) {
                astman_send_ack(s, m, "Success");
@@ -4086,6 +4843,8 @@ static int action_redirect(struct mansession *s, const struct message *m)
        int pi = 0;
        int pi2 = 0;
        int res;
+       int chan1_wait = 0;
+       int chan2_wait = 0;
 
        if (ast_strlen_zero(name)) {
                astman_send_error(s, m, "Channel not specified");
@@ -4170,16 +4929,20 @@ static int action_redirect(struct mansession *s, const struct message *m)
        }
 
        /* Dual channel redirect in progress. */
-       if (ast_channel_pbx(chan)) {
-               ast_channel_lock(chan);
+       ast_channel_lock(chan);
+       if (ast_channel_is_bridged(chan)) {
                ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
-               ast_channel_unlock(chan);
+               chan1_wait = 1;
        }
-       if (ast_channel_pbx(chan2)) {
-               ast_channel_lock(chan2);
+       ast_channel_unlock(chan);
+
+       ast_channel_lock(chan2);
+       if (ast_channel_is_bridged(chan2)) {
                ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
-               ast_channel_unlock(chan2);
+               chan2_wait = 1;
        }
+       ast_channel_unlock(chan2);
+
        res = ast_async_goto(chan, context, exten, pi);
        if (!res) {
                if (!ast_strlen_zero(context2)) {
@@ -4197,15 +4960,11 @@ static int action_redirect(struct mansession *s, const struct message *m)
        }
 
        /* Release the bridge wait. */
-       if (ast_channel_pbx(chan)) {
-               ast_channel_lock(chan);
-               ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
-               ast_channel_unlock(chan);
+       if (chan1_wait) {
+               ast_channel_clear_flag(chan, AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
        }
-       if (ast_channel_pbx(chan2)) {
-               ast_channel_lock(chan2);
-               ast_clear_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
-               ast_channel_unlock(chan2);
+       if (chan2_wait) {
+               ast_channel_clear_flag(chan2, AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
        }
 
        chan2 = ast_channel_unref(chan2);
@@ -4218,7 +4977,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m)
        const char *name = astman_get_header(m, "Channel");
        const char *exten = astman_get_header(m, "Exten");
        const char *context = astman_get_header(m, "Context");
-       RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+       struct ast_channel *chan;
 
        if (ast_strlen_zero(name)) {
                astman_send_error(s, m, "No channel specified");
@@ -4255,6 +5014,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m)
                break;
        }
 
+       ast_channel_unref(chan);
        return 0;
 }
 
@@ -4291,31 +5051,72 @@ static int action_atxfer(struct mansession *s, const struct message *m)
        }
        ast_channel_unlock(chan);
 
-       if (!ast_strlen_zero(context)) {
-               pbx_builtin_setvar_helper(chan, "TRANSFER_CONTEXT", context);
+       if (!ast_strlen_zero(context)) {
+               pbx_builtin_setvar_helper(chan, "TRANSFER_CONTEXT", context);
+       }
+
+       for (digit = feature_code; *digit; ++digit) {
+               struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit };
+               ast_queue_frame(chan, &f);
+       }
+
+       for (digit = exten; *digit; ++digit) {
+               struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit };
+               ast_queue_frame(chan, &f);
+       }
+
+       chan = ast_channel_unref(chan);
+
+       astman_send_ack(s, m, "Atxfer successfully queued");
+
+       return 0;
+}
+
+static int action_cancel_atxfer(struct mansession *s, const struct message *m)
+{
+       const char *name = astman_get_header(m, "Channel");
+       struct ast_channel *chan = NULL;
+       char *feature_code;
+       const char *digit;
+
+       if (ast_strlen_zero(name)) {
+               astman_send_error(s, m, "No channel specified");
+               return 0;
+       }
+
+       if (!(chan = ast_channel_get_by_name(name))) {
+               astman_send_error(s, m, "Channel specified does not exist");
+               return 0;
+       }
+
+       ast_channel_lock(chan);
+       feature_code = ast_get_chan_features_atxferabort(chan);
+       ast_channel_unlock(chan);
+
+       if (!feature_code) {
+               astman_send_error(s, m, "No disconnect feature code found");
+               ast_channel_unref(chan);
+               return 0;
        }
 
        for (digit = feature_code; *digit; ++digit) {
                struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit };
                ast_queue_frame(chan, &f);
        }
-
-       for (digit = exten; *digit; ++digit) {
-               struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit };
-               ast_queue_frame(chan, &f);
-       }
+       ast_free(feature_code);
 
        chan = ast_channel_unref(chan);
 
-       astman_send_ack(s, m, "Atxfer successfully queued");
+       astman_send_ack(s, m, "CancelAtxfer successfully queued");
 
        return 0;
 }
 
+
 static int check_blacklist(const char *cmd)
 {
        char *cmd_copy, *cur_cmd;
-       char *cmd_words[MAX_BLACKLIST_CMD_LEN] = { NULL, };
+       char *cmd_words[AST_MAX_CMD_LEN] = { NULL, };
        int i;
 
        cmd_copy = ast_strdupa(cmd);
@@ -4351,11 +5152,10 @@ static int check_blacklist(const char *cmd)
 static int action_command(struct mansession *s, const struct message *m)
 {
        const char *cmd = astman_get_header(m, "Command");
-       const char *id = astman_get_header(m, "ActionID");
-       char *buf = NULL, *final_buf = NULL;
+       char *buf = NULL, *final_buf = NULL, *delim, *output;
        char template[] = "/tmp/ast-ami-XXXXXX";        /* template for temporary file */
-       int fd;
-       off_t l;
+       int fd, ret;
+       off_t len;
 
        if (ast_strlen_zero(cmd)) {
                astman_send_error(s, m, "No command provided");
@@ -4368,52 +5168,59 @@ static int action_command(struct mansession *s, const struct message *m)
        }
 
        if ((fd = mkstemp(template)) < 0) {
-               ast_log(AST_LOG_WARNING, "Failed to create temporary file for command: %s\n", strerror(errno));
-               astman_send_error(s, m, "Command response construction error");
+               astman_send_error_va(s, m, "Failed to create temporary file: %s", strerror(errno));
                return 0;
        }
 
-       astman_append(s, "Response: Follows\r\nPrivilege: Command\r\n");
-       if (!ast_strlen_zero(id)) {
-               astman_append(s, "ActionID: %s\r\n", id);
-       }
-       /* FIXME: Wedge a ActionID response in here, waiting for later changes */
-       ast_cli_command(fd, cmd);       /* XXX need to change this to use a FILE * */
+       ret = ast_cli_command(fd, cmd);
+       astman_send_response_full(s, m, ret == RESULT_SUCCESS ? "Success" : "Error", MSG_MOREDATA, NULL);
+
        /* Determine number of characters available */
-       if ((l = lseek(fd, 0, SEEK_END)) < 0) {
-               ast_log(LOG_WARNING, "Failed to determine number of characters for command: %s\n", strerror(errno));
+       if ((len = lseek(fd, 0, SEEK_END)) < 0) {
+               astman_append(s, "Message: Failed to determine number of characters: %s\r\n", strerror(errno));
                goto action_command_cleanup;
        }
 
        /* This has a potential to overflow the stack.  Hence, use the heap. */
-       buf = ast_malloc(l + 1);
-       final_buf = ast_malloc(l + 1);
+       buf = ast_malloc(len + 1);
+       final_buf = ast_malloc(len + 1);
 
        if (!buf || !final_buf) {
-               ast_log(LOG_WARNING, "Failed to allocate memory for temporary buffer\n");
+               astman_append(s, "Message: Memory allocation failure\r\n");
                goto action_command_cleanup;
        }
 
        if (lseek(fd, 0, SEEK_SET) < 0) {
-               ast_log(LOG_WARNING, "Failed to set position on temporary file for command: %s\n", strerror(errno));
+               astman_append(s, "Message: Failed to set position on temporary file: %s\r\n", strerror(errno));
                goto action_command_cleanup;
        }
 
-       if (read(fd, buf, l) < 0) {
-               ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
+       if (read(fd, buf, len) < 0) {
+               astman_append(s, "Message: Failed to read from temporary file: %s\r\n", strerror(errno));
                goto action_command_cleanup;
        }
 
-       buf[l] = '\0';
-       term_strip(final_buf, buf, l);
-       final_buf[l] = '\0';
-       astman_append(s, "%s", final_buf);
+       buf[len] = '\0';
+       term_strip(final_buf, buf, len);
+       final_buf[len] = '\0';
+
+       /* Trim trailing newline */
+       if (len && final_buf[len - 1] == '\n') {
+               final_buf[len - 1] = '\0';
+       }
+
+       astman_append(s, "Message: Command output follows\r\n");
+
+       delim = final_buf;
+       while ((output = strsep(&delim, "\n"))) {
+               astman_append(s, "Output: %s\r\n", output);
+       }
 
 action_command_cleanup:
+       astman_append(s, "\r\n");
 
        close(fd);
        unlink(template);
-       astman_append(s, "--END COMMAND--\r\n\r\n");
 
        ast_free(buf);
        ast_free(final_buf);
@@ -4454,7 +5261,7 @@ struct fast_originate_helper {
  */
 static void destroy_fast_originate_helper(struct fast_originate_helper *doomed)
 {
-       ast_format_cap_destroy(doomed->cap);
+       ao2_cleanup(doomed->cap);
        ast_variables_destroy(doomed->vars);
        ast_string_field_free_memory(doomed);
        ast_free(doomed);
@@ -4474,41 +5281,62 @@ static void *fast_originate(void *data)
 
        if (!ast_strlen_zero(in->app)) {
                res = ast_pbx_outgoing_app(in->tech, in->cap, in->data,
-                       in->timeout, in->app, in->appdata, &reason, 1,
+                       in->timeout, in->app, in->appdata, &reason,
+                       AST_OUTGOING_WAIT,
                        S_OR(in->cid_num, NULL),
                        S_OR(in->cid_name, NULL),
                        in->vars, in->account, &chan, &assignedids);
        } else {
                res = ast_pbx_outgoing_exten(in->tech, in->cap, in->data,
-                       in->timeout, in->context, in->exten, in->priority, &reason, 1,
+                       in->timeout, in->context, in->exten, in->priority, &reason,
+                       AST_OUTGOING_WAIT,
                        S_OR(in->cid_num, NULL),
                        S_OR(in->cid_name, NULL),
                        in->vars, in->account, &chan, in->early_media, &assignedids);
        }
-       /* Any vars memory was passed to the ast_pbx_outgoing_xxx() calls. */
-       in->vars = NULL;
 
        if (!chan) {
                snprintf(requested_channel, AST_CHANNEL_NAME, "%s/%s", in->tech, in->data);
        }
        /* Tell the manager what happened with the channel */
        chans[0] = chan;
-       ast_manager_event_multichan(EVENT_FLAG_CALL, "OriginateResponse", chan ? 1 : 0, chans,
-               "%s"
-               "Response: %s\r\n"
-               "Channel: %s\r\n"
-               "Context: %s\r\n"
-               "Exten: %s\r\n"
-               "Reason: %d\r\n"
-               "Uniqueid: %s\r\n"
-               "CallerIDNum: %s\r\n"
-               "CallerIDName: %s\r\n",
-               in->idtext, res ? "Failure" : "Success",
-               chan ? ast_channel_name(chan) : requested_channel, in->context, in->exten, reason,
-               chan ? ast_channel_uniqueid(chan) : "<null>",
-               S_OR(in->cid_num, "<unknown>"),
-               S_OR(in->cid_name, "<unknown>")
-               );
+       if (!ast_strlen_zero(in->app)) {
+               ast_manager_event_multichan(EVENT_FLAG_CALL, "OriginateResponse", chan ? 1 : 0, chans,
+                       "%s"
+                       "Response: %s\r\n"
+                       "Channel: %s\r\n"
+                       "Application: %s\r\n"
+                       "Data: %s\r\n"
+                       "Reason: %d\r\n"
+                       "Uniqueid: %s\r\n"
+                       "CallerIDNum: %s\r\n"
+                       "CallerIDName: %s\r\n",
+                       in->idtext, res ? "Failure" : "Success",
+                       chan ? ast_channel_name(chan) : requested_channel,
+                       in->app, in->appdata, reason,
+                       chan ? ast_channel_uniqueid(chan) : S_OR(in->channelid, "<unknown>"),
+                       S_OR(in->cid_num, "<unknown>"),
+                       S_OR(in->cid_name, "<unknown>")
+                       );
+       } else {
+               ast_manager_event_multichan(EVENT_FLAG_CALL, "OriginateResponse", chan ? 1 : 0, chans,
+                       "%s"
+                       "Response: %s\r\n"
+                       "Channel: %s\r\n"
+                       "Context: %s\r\n"
+                       "Exten: %s\r\n"
+                       "Reason: %d\r\n"
+                       "Uniqueid: %s\r\n"
+                       "CallerIDNum: %s\r\n"
+                       "CallerIDName: %s\r\n",
+                       in->idtext, res ? "Failure" : "Success",
+                       chan ? ast_channel_name(chan) : requested_channel,
+                       in->context, in->exten, reason,
+                       chan ? ast_channel_uniqueid(chan) : S_OR(in->channelid, "<unknown>"),
+                       S_OR(in->cid_num, "<unknown>"),
+                       S_OR(in->cid_name, "<unknown>")
+                       );
+       }
 
        /* Locked and ref'd by ast_pbx_outgoing_exten or ast_pbx_outgoing_app */
        if (chan) {
@@ -4777,8 +5605,7 @@ static int action_originate(struct mansession *s, const struct message *m)
        int reason = 0;
        char tmp[256];
        char tmp2[256];
-       struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_NOLOCK);
-       struct ast_format tmp_fmt;
+       struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
        pthread_t th;
        int bridge_early = 0;
 
@@ -4786,7 +5613,7 @@ static int action_originate(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Internal Error. Memory allocation failure.");
                return 0;
        }
-       ast_format_cap_add(cap, ast_format_set(&tmp_fmt, AST_FORMAT_SLINEAR, 0));
+       ast_format_cap_append(cap, ast_format_slin, 0);
 
        if ((assignedids.uniqueid && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid))
                || (assignedids.uniqueid2 && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid2))) {
@@ -4836,8 +5663,8 @@ static int action_originate(struct mansession *s, const struct message *m)
                }
        }
        if (!ast_strlen_zero(codecs)) {
-               ast_format_cap_remove_all(cap);
-               ast_parse_allow_disallow(NULL, cap, codecs, 1);
+               ast_format_cap_remove_by_type(cap, AST_MEDIA_TYPE_UNKNOWN);
+               ast_format_cap_update_by_allow_disallow(cap, codecs, 1);
        }
 
        if (!ast_strlen_zero(app) && s->session) {
@@ -4933,12 +5760,17 @@ static int action_originate(struct mansession *s, const struct message *m)
                        }
                }
        } else if (!ast_strlen_zero(app)) {
-               res = ast_pbx_outgoing_app(tech, cap, data, to, app, appdata, &reason, 1, l, n, vars, account, NULL, assignedids.uniqueid ? &assignedids : NULL);
-               /* Any vars memory was passed to ast_pbx_outgoing_app(). */
+               res = ast_pbx_outgoing_app(tech, cap, data, to, app, appdata, &reason,
+                               AST_OUTGOING_WAIT, l, n, vars, account, NULL,
+                               assignedids.uniqueid ? &assignedids : NULL);
+               ast_variables_destroy(vars);
        } else {
                if (exten && context && pi) {
-                       res = ast_pbx_outgoing_exten(tech, cap, data, to, context, exten, pi, &reason, 1, l, n, vars, account, NULL, bridge_early, assignedids.uniqueid ? &assignedids : NULL);
-                       /* Any vars memory was passed to ast_pbx_outgoing_exten(). */
+                       res = ast_pbx_outgoing_exten(tech, cap, data, to,
+                                       context, exten, pi, &reason, AST_OUTGOING_WAIT,
+                                       l, n, vars, account, NULL, bridge_early,
+                                       assignedids.uniqueid ? &assignedids : NULL);
+                       ast_variables_destroy(vars);
                } else {
                        astman_send_error(s, m, "Originate with 'Exten' requires 'Context' and 'Priority'");
                        ast_variables_destroy(vars);
@@ -4953,7 +5785,7 @@ static int action_originate(struct mansession *s, const struct message *m)
        }
 
 fast_orig_cleanup:
-       ast_format_cap_destroy(cap);
+       ao2_cleanup(cap);
        return 0;
 }
 
@@ -4999,8 +5831,9 @@ static int action_extensionstate(struct mansession *s, const struct message *m)
 {
        const char *exten = astman_get_header(m, "Exten");
        const char *context = astman_get_header(m, "Context");
-       char hint[256] = "";
+       char hint[256];
        int status;
+
        if (ast_strlen_zero(exten)) {
                astman_send_error(s, m, "Extension not specified");
                return 0;
@@ -5009,16 +5842,18 @@ static int action_extensionstate(struct mansession *s, const struct message *m)
                context = "default";
        }
        status = ast_extension_state(NULL, context, exten);
-       ast_get_hint(hint, sizeof(hint) - 1, NULL, 0, NULL, context, exten);
+       hint[0] = '\0';
+       ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
        astman_start_ack(s, m);
-       astman_append(s,   "Message: Extension Status\r\n"
-                          "Exten: %s\r\n"
-                          "Context: %s\r\n"
-                          "Hint: %s\r\n"
-                          "Status: %d\r\n"
-                          "StatusText: %s\r\n\r\n",
-                     exten, context, hint, status,
-                     ast_extension_state2str(status));
+       astman_append(s, "Message: Extension Status\r\n"
+               "Exten: %s\r\n"
+               "Context: %s\r\n"
+               "Hint: %s\r\n"
+               "Status: %d\r\n"
+               "StatusText: %s\r\n"
+               "\r\n",
+               exten, context, hint, status,
+               ast_extension_state2str(status));
        return 0;
 }
 
@@ -5028,8 +5863,6 @@ static int action_presencestate(struct mansession *s, const struct message *m)
        enum ast_presence_state state;
        char *subtype;
        char *message;
-       char subtype_header[256] = "";
-       char message_header[256] = "";
 
        if (ast_strlen_zero(provider)) {
                astman_send_error(s, m, "No provider specified");
@@ -5042,24 +5875,25 @@ static int action_presencestate(struct mansession *s, const struct message *m)
                return 0;
        }
 
+       astman_start_ack(s, m);
+       astman_append(s, "Message: Presence State\r\n"
+                        "State: %s\r\n", ast_presence_state2str(state));
+
        if (!ast_strlen_zero(subtype)) {
-               snprintf(subtype_header, sizeof(subtype_header),
-                               "Subtype: %s\r\n", subtype);
+               astman_append(s, "Subtype: %s\r\n", subtype);
        }
 
        if (!ast_strlen_zero(message)) {
-               snprintf(message_header, sizeof(message_header),
-                               "Message: %s\r\n", message);
+               /* XXX The Message header here is deprecated as it
+                * duplicates the action response header 'Message'.
+                * Remove it in the next major revision of AMI.
+                */
+               astman_append(s, "Message: %s\r\n"
+                                "PresenceMessage: %s\r\n",
+                                message, message);
        }
+       astman_append(s, "\r\n");
 
-       astman_append(s, "Message: Presence State\r\n"
-                       "State: %s\r\n"
-                       "%s"
-                       "%s"
-                       "\r\n",
-                       ast_presence_state2str(state),
-                       subtype_header,
-                       message_header);
        return 0;
 }
 
@@ -5216,7 +6050,11 @@ static int match_filter(struct mansession *s, char *eventdata)
 {
        int result = 0;
 
-       ast_debug(3, "Examining AMI event:\n%s\n", eventdata);
+       if (manager_debug) {
+               ast_verbose("<-- Examining AMI event: -->\n%s\n", eventdata);
+       } else {
+               ast_debug(3, "Examining AMI event:\n%s\n", eventdata);
+       }
        if (!ao2_container_count(s->session->whitefilters) && !ao2_container_count(s->session->blackfilters)) {
                return 1; /* no filtering means match all */
        } else if (ao2_container_count(s->session->whitefilters) && !ao2_container_count(s->session->blackfilters)) {
@@ -5247,10 +6085,14 @@ static int process_events(struct mansession *s)
        int ret = 0;
 
        ao2_lock(s->session);
-       if (s->session->f != NULL) {
+       if (s->session->stream != NULL) {
                struct eventqent *eqe = s->session->last_ev;
 
                while ((eqe = advance_event(eqe))) {
+                       if (eqe->category == EVENT_FLAG_SHUTDOWN) {
+                               ast_debug(3, "Received CloseSession event\n");
+                               ret = -1;
+                       }
                        if (!ret && s->session->authenticated &&
                            (s->session->readperm & eqe->category) == eqe->category &&
                            (s->session->send_events & eqe->category) == eqe->category) {
@@ -5274,8 +6116,9 @@ static int action_userevent(struct mansession *s, const struct message *m)
 
        ast_str_reset(body);
 
-       for (x = 1; x < m->hdrcount; x++) {
-               if (strncasecmp("UserEvent:", m->headers[x], strlen("UserEvent:"))) {
+       for (x = 0; x < m->hdrcount; x++) {
+               if (strncasecmp("UserEvent:", m->headers[x], strlen("UserEvent:")) &&
+                               strncasecmp("Action:", m->headers[x], strlen("Action:"))) {
                        ast_str_append(&body, 0, "%s\r\n", m->headers[x]);
                }
        }
@@ -5405,9 +6248,9 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
        const char *actionid = astman_get_header(m, "ActionID");
        char idText[256];
        int numchans = 0;
-       RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+       struct ao2_container *channels;
        struct ao2_iterator it_chans;
-       struct stasis_message *msg;
+       struct ast_channel_snapshot *cs;
 
        if (!ast_strlen_zero(actionid)) {
                snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
@@ -5415,29 +6258,44 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
                idText[0] = '\0';
        }
 
-       if (!(channels = stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type()))) {
-               astman_send_error(s, m, "Could not get cached channels");
-               return 0;
-       }
+       channels = ast_channel_cache_by_name();
 
        astman_send_listack(s, m, "Channels will follow", "start");
 
        it_chans = ao2_iterator_init(channels, 0);
-       for (; (msg = ao2_iterator_next(&it_chans)); ao2_ref(msg, -1)) {
-               struct ast_channel_snapshot *cs = stasis_message_data(msg);
+       for (; (cs = ao2_iterator_next(&it_chans)); ao2_ref(cs, -1)) {
                struct ast_str *built = ast_manager_build_channel_state_string_prefix(cs, "");
+               char durbuf[16] = "";
 
                if (!built) {
                        continue;
                }
 
+               if (!ast_tvzero(cs->base->creationtime)) {
+                       int duration, durh, durm, durs;
+
+                       duration = (int)(ast_tvdiff_ms(ast_tvnow(), cs->base->creationtime) / 1000);
+                       durh = duration / 3600;
+                       durm = (duration % 3600) / 60;
+                       durs = duration % 60;
+                       snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
+               }
+
                astman_append(s,
                        "Event: CoreShowChannel\r\n"
                        "%s"
                        "%s"
+                       "Application: %s\r\n"
+                       "ApplicationData: %s\r\n"
+                       "Duration: %s\r\n"
+                       "BridgeId: %s\r\n"
                        "\r\n",
                        idText,
-                       ast_str_buffer(built));
+                       ast_str_buffer(built),
+                       cs->dialplan->appl,
+                       cs->dialplan->data,
+                       durbuf,
+                       cs->bridge->id);
 
                numchans++;
 
@@ -5445,13 +6303,10 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
        }
        ao2_iterator_destroy(&it_chans);
 
-       astman_append(s,
-               "Event: CoreShowChannelsComplete\r\n"
-               "EventList: Complete\r\n"
-               "ListItems: %d\r\n"
-               "%s"
-               "\r\n", numchans, idText);
+       astman_send_list_complete_start(s, m, "CoreShowChannelsComplete", numchans);
+       astman_send_list_complete_end(s);
 
+       ao2_ref(channels, -1);
        return 0;
 }
 
@@ -5475,9 +6330,6 @@ static int manager_modulecheck(struct mansession *s, const struct message *m)
        const char *module = astman_get_header(m, "Module");
        const char *id = astman_get_header(m, "ActionID");
        char idText[256];
-#if !defined(LOW_MEMORY)
-       const char *version;
-#endif
        char filename[PATH_MAX];
        char *cut;
 
@@ -5494,11 +6346,6 @@ static int manager_modulecheck(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Module not loaded");
                return 0;
        }
-       snprintf(cut, (sizeof(filename) - strlen(filename)) - 1, ".c");
-       ast_debug(1, "**** ModuleCheck .c file %s\n", filename);
-#if !defined(LOW_MEMORY)
-       version = ast_file_version_find(filename);
-#endif
 
        if (!ast_strlen_zero(id)) {
                snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
@@ -5507,7 +6354,7 @@ static int manager_modulecheck(struct mansession *s, const struct message *m)
        }
        astman_append(s, "Response: Success\r\n%s", idText);
 #if !defined(LOW_MEMORY)
-       astman_append(s, "Version: %s\r\n\r\n", version ? version : "");
+       astman_append(s, "Version: %s\r\n\r\n", "");
 #endif
        return 0;
 }
@@ -5540,14 +6387,31 @@ static int manager_moduleload(struct mansession *s, const struct message *m)
                        astman_send_ack(s, m, "Module unloaded.");
                }
        } else if (!strcasecmp(loadtype, "reload")) {
+               /* TODO: Unify the ack/error messages here with action_reload */
                if (!ast_strlen_zero(module)) {
-                       res = ast_module_reload(module);
-                       if (res == 0) {
+                       enum ast_module_reload_result reload_res = ast_module_reload(module);
+
+                       switch (reload_res) {
+                       case AST_MODULE_RELOAD_NOT_FOUND:
                                astman_send_error(s, m, "No such module.");
-                       } else if (res == 1) {
+                               break;
+                       case AST_MODULE_RELOAD_NOT_IMPLEMENTED:
                                astman_send_error(s, m, "Module does not support reload action.");
-                       } else {
+                               break;
+                       case AST_MODULE_RELOAD_ERROR:
+                               astman_send_error(s, m, "An unknown error occurred");
+                               break;
+                       case AST_MODULE_RELOAD_IN_PROGRESS:
+                               astman_send_error(s, m, "A reload is in progress");
+                               break;
+                       case AST_MODULE_RELOAD_UNINITIALIZED:
+                               astman_send_error(s, m, "Module not initialized");
+                               break;
+                       case AST_MODULE_RELOAD_QUEUED:
+                       case AST_MODULE_RELOAD_SUCCESS:
+                               /* Treat a queued request as success */
                                astman_send_ack(s, m, "Module reloaded.");
+                               break;
                        }
                } else {
                        ast_module_reload(NULL);        /* Reload all modules */
@@ -5588,6 +6452,14 @@ static int process_message(struct mansession *s, const struct message *m)
                return 0;
        }
 
+       if (ast_shutting_down()) {
+               ast_log(LOG_ERROR, "Unable to process manager action '%s'. Asterisk is shutting down.\n", action);
+               mansession_lock(s);
+               astman_send_error(s, m, "Asterisk is shutting down");
+               mansession_unlock(s);
+               return 0;
+       }
+
        if (!s->session->authenticated
                && strcasecmp(action, "Login")
                && strcasecmp(action, "Logoff")
@@ -5630,21 +6502,21 @@ static int process_message(struct mansession *s, const struct message *m)
                if ((s->session->writeperm & act_found->authority)
                        || act_found->authority == 0) {
                        /* We have the authority to execute the action. */
+                       ret = -1;
                        ao2_lock(act_found);
                        if (act_found->registered && act_found->func) {
-                               ast_debug(1, "Running action '%s'\n", act_found->action);
-                               if (act_found->module) {
-                                       ast_module_ref(act_found->module);
-                               }
+                               struct ast_module *mod_ref = ast_module_running_ref(act_found->module);
+
                                ao2_unlock(act_found);
-                               ret = act_found->func(s, m);
-                               acted = 1;
-                               ao2_lock(act_found);
-                               if (act_found->module) {
-                                       ast_module_unref(act_found->module);
+                               if (mod_ref || !act_found->module) {
+                                       ast_debug(1, "Running action '%s'\n", act_found->action);
+                                       ret = act_found->func(s, m);
+                                       acted = 1;
+                                       ast_module_unref(mod_ref);
                                }
+                       } else {
+                               ao2_unlock(act_found);
                        }
-                       ao2_unlock(act_found);
                }
                if (!acted) {
                        /*
@@ -5718,9 +6590,11 @@ static int get_input(struct mansession *s, char *output)
                return 1;
        }
        if (s->session->inlen >= maxlen) {
-               /* no crlf found, and buffer full - sorry, too long for us */
+               /* no crlf found, and buffer full - sorry, too long for us
+                * keep the last character in case we are in the middle of a CRLF. */
                ast_log(LOG_WARNING, "Discarding message from %s. Line too long: %.25s...\n", ast_sockaddr_stringify_addr(&s->session->addr), src);
-               s->session->inlen = 0;
+               src[0] = src[s->session->inlen - 1];
+               s->session->inlen = 1;
                s->parsing = MESSAGE_LINE_TOO_LONG;
        }
        res = 0;
@@ -5748,7 +6622,7 @@ static int get_input(struct mansession *s, char *output)
                s->session->waiting_thread = pthread_self();
                ao2_unlock(s->session);
 
-               res = ast_wait_for_input(s->session->fd, timeout);
+               res = ast_wait_for_input(ast_iostream_get_fd(s->session->stream), timeout);
 
                ao2_lock(s->session);
                s->session->waiting_thread = AST_PTHREADT_NULL;
@@ -5766,7 +6640,7 @@ static int get_input(struct mansession *s, char *output)
        }
 
        ao2_lock(s->session);
-       res = fread(src + s->session->inlen, 1, maxlen - s->session->inlen, s->session->f);
+       res = ast_iostream_read(s->session->stream, src + s->session->inlen, maxlen - s->session->inlen);
        if (res < 1) {
                res = -1;       /* error return */
        } else {
@@ -5809,7 +6683,6 @@ static int do_message(struct mansession *s)
        struct message m = { 0 };
        char header_buf[sizeof(s->session->inbuf)] = { '\0' };
        int res;
-       int idx;
        int hdr_loss;
        time_t now;
 
@@ -5877,10 +6750,8 @@ static int do_message(struct mansession *s)
                }
        }
 
-       /* Free AMI request headers. */
-       for (idx = 0; idx < m.hdrcount; ++idx) {
-               ast_free((void *) m.headers[idx]);
-       }
+       astman_free_headers(&m);
+
        return res;
 }
 
@@ -5899,13 +6770,11 @@ static void *session_do(void *data)
        struct mansession s = {
                .tcptls_session = data,
        };
-       int flags;
        int res;
+       int arg = 1;
        struct ast_sockaddr ser_remote_address_tmp;
-       struct protoent *p;
 
        if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) {
-               fclose(ser->f);
                ast_atomic_fetchadd_int(&unauth_sessions, -1);
                goto done;
        }
@@ -5914,7 +6783,6 @@ static void *session_do(void *data)
        session = build_mansession(&ser_remote_address_tmp);
 
        if (session == NULL) {
-               fclose(ser->f);
                ast_atomic_fetchadd_int(&unauth_sessions, -1);
                goto done;
        }
@@ -5922,20 +6790,10 @@ static void *session_do(void *data)
        /* here we set TCP_NODELAY on the socket to disable Nagle's algorithm.
         * This is necessary to prevent delays (caused by buffering) as we
         * write to the socket in bits and pieces. */
-       p = getprotobyname("tcp");
-       if (p) {
-               int arg = 1;
-               if( setsockopt(ser->fd, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) {
-                       ast_log(LOG_WARNING, "Failed to set manager tcp connection to TCP_NODELAY mode: %s\nSome manager actions may be slow to respond.\n", strerror(errno));
-               }
-       } else {
-               ast_log(LOG_WARNING, "Failed to set manager tcp connection to TCP_NODELAY, getprotobyname(\"tcp\") failed\nSome manager actions may be slow to respond.\n");
+       if (setsockopt(ast_iostream_get_fd(ser->stream), IPPROTO_TCP, TCP_NODELAY, (char *) &arg, sizeof(arg)) < 0) {
+               ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on manager connection: %s\n", strerror(errno));
        }
-
-       /* make sure socket is non-blocking */
-       flags = fcntl(ser->fd, F_GETFL);
-       flags |= O_NONBLOCK;
-       fcntl(ser->fd, F_SETFL, flags);
+       ast_iostream_nonblock(ser->stream);
 
        ao2_lock(session);
        /* Hook to the tail of the event queue */
@@ -5944,8 +6802,7 @@ static void *session_do(void *data)
        ast_mutex_init(&s.lock);
 
        /* these fields duplicate those in the 'ser' structure */
-       session->fd = s.fd = ser->fd;
-       session->f = s.f = ser->f;
+       session->stream = s.stream = ser->stream;
        ast_sockaddr_copy(&session->addr, &ser_remote_address_tmp);
        s.session = session;
 
@@ -5964,9 +6821,9 @@ static void *session_do(void *data)
         * We cannot let the stream exclusively wait for data to arrive.
         * We have to wake up the task to send async events.
         */
-       ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 0);
+       ast_iostream_set_exclusive_input(ser->stream, 0);
 
-       ast_tcptls_stream_set_timeout_sequence(ser->stream_cookie,
+       ast_iostream_set_timeout_sequence(ser->stream,
                ast_tvnow(), authtimeout * 1000);
 
        astman_append(&s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */
@@ -5975,7 +6832,7 @@ static void *session_do(void *data)
                        break;
                }
                if (session->authenticated) {
-                       ast_tcptls_stream_set_timeout_disable(ser->stream_cookie);
+                       ast_iostream_set_timeout_disable(ser->stream);
                }
        }
        /* session is over, explain why and terminate */
@@ -6063,11 +6920,10 @@ static int append_event(const char *str, int category)
 
 static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan)
 {
-       RAII_VAR(struct varshead *, vars, NULL, ao2_cleanup);
+       struct varshead *vars;
        struct ast_var_t *var;
 
        vars = ast_channel_get_manager_vars(chan);
-
        if (!vars) {
                return;
        }
@@ -6075,62 +6931,67 @@ static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan)
        AST_LIST_TRAVERSE(vars, var, entries) {
                ast_str_append(pbuf, 0, "ChanVariable(%s): %s=%s\r\n", ast_channel_name(chan), var->name, var->value);
        }
+       ao2_ref(vars, -1);
 }
 
 /* XXX see if can be moved inside the function */
 AST_THREADSTORAGE(manager_event_buf);
 #define MANAGER_EVENT_BUF_INITSIZE   256
 
-int __ast_manager_event_multichan(int category, const char *event, int chancount,
-       struct ast_channel **chans, const char *file, int line, const char *func,
-       const char *fmt, ...)
-{
-       RAII_VAR(struct ao2_container *, sessions, ao2_global_obj_ref(mgr_sessions), ao2_cleanup);
-       struct mansession_session *session;
-       struct manager_custom_hook *hook;
-       struct ast_str *auth = ast_str_alloca(80);
+static int __attribute__((format(printf, 9, 0))) __manager_event_sessions_va(
+       struct ao2_container *sessions,
+       int category,
+       const char *event,
+       int chancount,
+       struct ast_channel **chans,
+       const char *file,
+       int line,
+       const char *func,
+       const char *fmt,
+       va_list ap)
+{
+       struct ast_str *auth = ast_str_alloca(MAX_AUTH_PERM_STRING);
        const char *cat_str;
-       va_list ap;
        struct timeval now;
        struct ast_str *buf;
        int i;
 
-       if (!(sessions && ao2_container_count(sessions)) && AST_RWLIST_EMPTY(&manager_hooks)) {
-               return 0;
-       }
-
-       if (!(buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE))) {
+       buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE);
+       if (!buf) {
                return -1;
        }
 
        cat_str = authority_to_str(category, &auth);
        ast_str_set(&buf, 0,
-                       "Event: %s\r\nPrivilege: %s\r\n",
-                        event, cat_str);
+               "Event: %s\r\n"
+               "Privilege: %s\r\n",
+               event, cat_str);
 
        if (timestampevents) {
                now = ast_tvnow();
                ast_str_append(&buf, 0,
-                               "Timestamp: %ld.%06lu\r\n",
-                                (long)now.tv_sec, (unsigned long) now.tv_usec);
+                       "Timestamp: %ld.%06lu\r\n",
+                       (long)now.tv_sec, (unsigned long) now.tv_usec);
        }
        if (manager_debug) {
                static int seq;
+
                ast_str_append(&buf, 0,
-                               "SequenceNumber: %d\r\n",
-                                ast_atomic_fetchadd_int(&seq, 1));
+                       "SequenceNumber: %d\r\n",
+                       ast_atomic_fetchadd_int(&seq, 1));
                ast_str_append(&buf, 0,
-                               "File: %s\r\nLine: %d\r\nFunc: %s\r\n", file, line, func);
+                       "File: %s\r\n"
+                       "Line: %d\r\n"
+                       "Func: %s\r\n",
+                       file, line, func);
        }
        if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
                ast_str_append(&buf, 0,
-                               "SystemName: %s\r\n",
-                                ast_config_AST_SYSTEM_NAME);
+                       "SystemName: %s\r\n",
+                       ast_config_AST_SYSTEM_NAME);
        }
 
-       va_start(ap, fmt);
        ast_str_append_va(&buf, 0, fmt, ap);
-       va_end(ap);
        for (i = 0; i < chancount; i++) {
                append_channel_vars(&buf, chans[i]);
        }
@@ -6141,9 +7002,11 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
 
        /* Wake up any sleeping sessions */
        if (sessions) {
-               struct ao2_iterator i;
-               i = ao2_iterator_init(sessions, 0);
-               while ((session = ao2_iterator_next(&i))) {
+               struct ao2_iterator iter;
+               struct mansession_session *session;
+
+               iter = ao2_iterator_init(sessions, 0);
+               while ((session = ao2_iterator_next(&iter))) {
                        ao2_lock(session);
                        if (session->waiting_thread != AST_PTHREADT_NULL) {
                                pthread_kill(session->waiting_thread, SIGURG);
@@ -6158,10 +7021,12 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
                        ao2_unlock(session);
                        unref_mansession(session);
                }
-               ao2_iterator_destroy(&i);
+               ao2_iterator_destroy(&iter);
        }
 
-       if (!AST_RWLIST_EMPTY(&manager_hooks)) {
+       if (category != EVENT_FLAG_SHUTDOWN && !AST_RWLIST_EMPTY(&manager_hooks)) {
+               struct manager_custom_hook *hook;
+
                AST_RWLIST_RDLOCK(&manager_hooks);
                AST_RWLIST_TRAVERSE(&manager_hooks, hook, list) {
                        hook->helper(category, event, ast_str_buffer(buf));
@@ -6172,6 +7037,50 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
        return 0;
 }
 
+static int __attribute__((format(printf, 9, 0))) __manager_event_sessions(
+       struct ao2_container *sessions,
+       int category,
+       const char *event,
+       int chancount,
+       struct ast_channel **chans,
+       const char *file,
+       int line,
+       const char *func,
+       const char *fmt,
+       ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = __manager_event_sessions_va(sessions, category, event, chancount, chans,
+               file, line, func, fmt, ap);
+       va_end(ap);
+       return res;
+}
+
+int __ast_manager_event_multichan(int category, const char *event, int chancount,
+       struct ast_channel **chans, const char *file, int line, const char *func,
+       const char *fmt, ...)
+{
+       struct ao2_container *sessions = ao2_global_obj_ref(mgr_sessions);
+       va_list ap;
+       int res;
+
+       if (!any_manager_listeners(sessions)) {
+               /* Nobody is listening */
+               ao2_cleanup(sessions);
+               return 0;
+       }
+
+       va_start(ap, fmt);
+       res = __manager_event_sessions_va(sessions, category, event, chancount, chans,
+               file, line, func, fmt, ap);
+       va_end(ap);
+       ao2_cleanup(sessions);
+       return res;
+}
+
 /*! \brief
  * support functions to register/unregister AMI action handlers,
  */
@@ -6205,20 +7114,16 @@ int ast_manager_unregister(const char *action)
        return 0;
 }
 
-static int manager_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
+static int manager_state_cb(const char *context, const char *exten, struct ast_state_cb_info *info, void *data)
 {
        /* Notify managers of change */
        char hint[512];
 
+       hint[0] = '\0';
        ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
 
        switch(info->reason) {
        case AST_HINT_UPDATE_DEVICE:
-               /*** DOCUMENTATION
-                       <managerEventInstance>
-                               <synopsis>Raised when an extension state has changed.</synopsis>
-                       </managerEventInstance>
-               ***/
                manager_event(EVENT_FLAG_CALL, "ExtensionStatus",
                        "Exten: %s\r\n"
                        "Context: %s\r\n"
@@ -6232,11 +7137,6 @@ static int manager_state_cb(char *context, char *exten, struct ast_state_cb_info
                        ast_extension_state2str(info->exten_state));
                break;
        case AST_HINT_UPDATE_PRESENCE:
-               /*** DOCUMENTATION
-                       <managerEventInstance>
-                               <synopsis>Raised when a presence state has changed.</synopsis>
-                       </managerEventInstance>
-               ***/
                manager_event(EVENT_FLAG_CALL, "PresenceStatus",
                        "Exten: %s\r\n"
                        "Context: %s\r\n"
@@ -6270,9 +7170,9 @@ static int ast_manager_register_struct(struct manager_action *act)
                        return -1;
                }
                if (ret > 0) { /* Insert these alphabetically */
-                       prev = cur;
                        break;
                }
+               prev = cur;
        }
 
        ao2_t_ref(act, +1, "action object added to list");
@@ -6306,6 +7206,8 @@ static void action_destroy(void *obj)
                /* The string fields were initialized. */
                ast_string_field_free_memory(doomed);
        }
+       ao2_cleanup(doomed->final_response);
+       ao2_cleanup(doomed->list_responses);
 }
 
 /*! \brief register a new command with manager, including online help. This is
@@ -6314,7 +7216,7 @@ int ast_manager_register2(const char *action, int auth, int (*func)(struct manse
 {
        struct manager_action *cur;
 
-       cur = ao2_alloc(sizeof(*cur), action_destroy);
+       cur = ao2_t_alloc(sizeof(*cur), action_destroy, action);
        if (!cur) {
                return -1;
        }
@@ -6351,6 +7253,9 @@ int ast_manager_register2(const char *action, int auth, int (*func)(struct manse
                ast_string_field_set(cur, arguments, tmpxml);
                ast_free(tmpxml);
 
+               cur->final_response = ast_xmldoc_build_final_response("manager", action, NULL);
+               cur->list_responses = ast_xmldoc_build_list_responses("manager", action, NULL);
+
                cur->docsrc = AST_XML_DOC;
        } else
 #endif
@@ -6729,7 +7634,8 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *g
                        if (xml) {
                                ast_str_append(out, 0, "<response type='object' id='%s'><%s", dest, objtype);
                        }
-                       vco = ao2_container_alloc(37, variable_count_hash_fn, variable_count_cmp_fn);
+                       vco = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 37,
+                               variable_count_hash_fn, NULL, variable_count_cmp_fn);
                        inobj = 1;
                }
 
@@ -6787,23 +7693,9 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *g
 
 static void close_mansession_file(struct mansession *s)
 {
-       if (s->f) {
-               if (fclose(s->f)) {
-                       ast_log(LOG_ERROR, "fclose() failed: %s\n", strerror(errno));
-               }
-               s->f = NULL;
-               s->fd = -1;
-       } else if (s->fd != -1) {
-               /*
-                * Issuing shutdown() is necessary here to avoid a race
-                * condition where the last data written may not appear
-                * in the TCP stream.  See ASTERISK-23548
-                */
-               shutdown(s->fd, SHUT_RDWR);
-               if (close(s->fd)) {
-                       ast_log(LOG_ERROR, "close() failed: %s\n", strerror(errno));
-               }
-               s->fd = -1;
+       if (s->stream) {
+               ast_iostream_close(s->stream);
+               s->stream = NULL;
        } else {
                ast_log(LOG_ERROR, "Attempted to close file/file descriptor on mansession without a valid file or file descriptor.\n");
        }
@@ -6812,17 +7704,20 @@ static void close_mansession_file(struct mansession *s)
 static void process_output(struct mansession *s, struct ast_str **out, struct ast_variable *params, enum output_format format)
 {
        char *buf;
-       size_t l;
+       off_t l;
+       int fd;
 
-       if (!s->f)
+       if (!s->stream)
                return;
 
        /* Ensure buffer is NULL-terminated */
-       fprintf(s->f, "%c", 0);
-       fflush(s->f);
+       ast_iostream_write(s->stream, "", 1);
+
+       fd = ast_iostream_get_fd(s->stream);
 
-       if ((l = ftell(s->f)) > 0) {
-               if (MAP_FAILED == (buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_PRIVATE, s->fd, 0))) {
+       l = lseek(fd, 0, SEEK_CUR);
+       if (l > 0) {
+               if (MAP_FAILED == (buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0))) {
                        ast_log(LOG_WARNING, "mmap failed.  Manager output was not processed\n");
                } else {
                        if (format == FORMAT_XML || format == FORMAT_HTML) {
@@ -6849,14 +7744,12 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
        struct mansession s = { .session = NULL, .tcptls_session = ser };
        struct mansession_session *session = NULL;
        uint32_t ident;
+       int fd;
        int blastaway = 0;
-       struct ast_variable *v;
        struct ast_variable *params = get_params;
        char template[] = "/tmp/ast-http-XXXXXX";       /* template for temporary file */
        struct ast_str *http_header = NULL, *out = NULL;
        struct message m = { 0 };
-       unsigned int idx;
-       size_t hdrlen;
 
        if (method != AST_HTTP_GET && method != AST_HTTP_HEAD && method != AST_HTTP_POST) {
                ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
@@ -6873,7 +7766,7 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
                 */
                if (!(session = build_mansession(remote_address))) {
                        ast_http_request_close_on_completion(ser);
-                       ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)\n");
+                       ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)");
                        return 0;
                }
                ao2_lock(session);
@@ -6899,22 +7792,22 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
 
        if (http_header == NULL || out == NULL) {
                ast_http_request_close_on_completion(ser);
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (ast_str_create() out of memory)\n");
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (ast_str_create() out of memory)");
                goto generic_callback_out;
        }
 
        s.session = session;
-       s.fd = mkstemp(template);       /* create a temporary file for command output */
+       fd = mkstemp(template); /* create a temporary file for command output */
        unlink(template);
-       if (s.fd <= -1) {
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (mkstemp failed)\n");
+       if (fd <= -1) {
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (mkstemp failed)");
                goto generic_callback_out;
        }
-       s.f = fdopen(s.fd, "w+");
-       if (!s.f) {
+       s.stream = ast_iostream_from_fd(&fd);
+       if (!s.stream) {
                ast_log(LOG_WARNING, "HTTP Manager, fdopen failed: %s!\n", strerror(errno));
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (fdopen failed)\n");
-               close(s.fd);
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (fdopen failed)");
+               close(fd);
                goto generic_callback_out;
        }
 
@@ -6939,17 +7832,7 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
                }
        }
 
-       for (v = params; v && m.hdrcount < ARRAY_LEN(m.headers); v = v->next) {
-               hdrlen = strlen(v->name) + strlen(v->value) + 3;
-               m.headers[m.hdrcount] = ast_malloc(hdrlen);
-               if (!m.headers[m.hdrcount]) {
-                       /* Allocation failure */
-                       continue;
-               }
-               snprintf((char *) m.headers[m.hdrcount], hdrlen, "%s: %s", v->name, v->value);
-               ast_debug(1, "HTTP Manager add header %s\n", m.headers[m.hdrcount]);
-               ++m.hdrcount;
-       }
+       astman_append_headers(&m, params);
 
        if (process_message(&s, &m)) {
                if (session->authenticated) {
@@ -6964,11 +7847,7 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
                session->needdestroy = 1;
        }
 
-       /* Free request headers. */
-       for (idx = 0; idx < m.hdrcount; ++idx) {
-               ast_free((void *) m.headers[idx]);
-               m.headers[idx] = NULL;
-       }
+       astman_free_headers(&m);
 
        ast_str_append(&http_header, 0,
                "Content-type: text/%s\r\n"
@@ -7050,11 +7929,16 @@ generic_callback_out:
        ast_free(http_header);
        ast_free(out);
 
-       if (session && blastaway) {
-               session_destroy(session);
-       } else if (session && session->f) {
-               fclose(session->f);
-               session->f = NULL;
+       if (session) {
+               if (blastaway) {
+                       session_destroy(session);
+               } else {
+                       if (session->stream) {
+                               ast_iostream_close(session->stream);
+                               session->stream = NULL;
+                       }
+                       unref_mansession(session);
+               }
        }
 
        return 0;
@@ -7074,8 +7958,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
        struct ast_str *http_header = NULL, *out = NULL;
        size_t result_size;
        struct message m = { 0 };
-       unsigned int idx;
-       size_t hdrlen;
+       int fd;
 
        time_t time_now = time(NULL);
        unsigned long nonce = 0, nc;
@@ -7109,7 +7992,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
        /* Digest found - parse */
        if (ast_string_field_init(&d, 128)) {
                ast_http_request_close_on_completion(ser);
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)\n");
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)");
                return 0;
        }
 
@@ -7138,7 +8021,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
                AST_RWLIST_UNLOCK(&users);
                ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_sockaddr_stringify_addr(&session->addr), d.username);
                ast_http_request_close_on_completion(ser);
-               ast_http_error(ser, 403, "Permission denied", "Permission denied\n");
+               ast_http_error(ser, 403, "Permission denied", "Permission denied");
                return 0;
        }
 
@@ -7146,13 +8029,21 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
 
        /* compute the expected response to compare with what we received */
        {
-               char a2[256];
-               char a2_hash[256];
+               char *a2;
+               /* ast_md5_hash outputs 32 characters plus NULL terminator. */
+               char a2_hash[33];
                char resp[256];
 
                /* XXX Now request method are hardcoded in A2 */
-               snprintf(a2, sizeof(a2), "%s:%s", ast_get_http_method(method), d.uri);
+               if (ast_asprintf(&a2, "%s:%s", ast_get_http_method(method), d.uri) < 0) {
+                       AST_RWLIST_UNLOCK(&users);
+                       ast_http_request_close_on_completion(ser);
+                       ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)");
+                       return 0;
+               }
+
                ast_md5_hash(a2_hash, a2);
+               ast_free(a2);
 
                if (d.qop) {
                        /* RFC 2617 */
@@ -7189,7 +8080,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
                 */
                if (!(session = build_mansession(remote_address))) {
                        ast_http_request_close_on_completion(ser);
-                       ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)\n");
+                       ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)");
                        return 0;
                }
                ao2_lock(session);
@@ -7254,17 +8145,17 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
 
        ast_mutex_init(&s.lock);
        s.session = session;
-       s.fd = mkstemp(template);       /* create a temporary file for command output */
+       fd = mkstemp(template); /* create a temporary file for command output */
        unlink(template);
-       if (s.fd <= -1) {
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (mkstemp failed)\n");
+       if (fd <= -1) {
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (mkstemp failed)");
                goto auth_callback_out;
        }
-       s.f = fdopen(s.fd, "w+");
-       if (!s.f) {
+       s.stream = ast_iostream_from_fd(&fd);
+       if (!s.stream) {
                ast_log(LOG_WARNING, "HTTP Manager, fdopen failed: %s!\n", strerror(errno));
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (fdopen failed)\n");
-               close(s.fd);
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (fdopen failed)");
+               close(fd);
                goto auth_callback_out;
        }
 
@@ -7289,17 +8180,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
                }
        }
 
-       for (v = params; v && m.hdrcount < ARRAY_LEN(m.headers); v = v->next) {
-               hdrlen = strlen(v->name) + strlen(v->value) + 3;
-               m.headers[m.hdrcount] = ast_malloc(hdrlen);
-               if (!m.headers[m.hdrcount]) {
-                       /* Allocation failure */
-                       continue;
-               }
-               snprintf((char *) m.headers[m.hdrcount], hdrlen, "%s: %s", v->name, v->value);
-               ast_verb(4, "HTTP Manager add header %s\n", m.headers[m.hdrcount]);
-               ++m.hdrcount;
-       }
+       astman_append_headers(&m, params);
 
        if (process_message(&s, &m)) {
                if (u_displayconnects) {
@@ -7309,19 +8190,15 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
                session->needdestroy = 1;
        }
 
-       /* Free request headers. */
-       for (idx = 0; idx < m.hdrcount; ++idx) {
-               ast_free((void *) m.headers[idx]);
-               m.headers[idx] = NULL;
-       }
+       astman_free_headers(&m);
 
-       result_size = ftell(s.f); /* Calculate approx. size of result */
+       result_size = lseek(ast_iostream_get_fd(s.stream), 0, SEEK_CUR); /* Calculate approx. size of result */
 
        http_header = ast_str_create(80);
        out = ast_str_create(result_size * 2 + 512);
        if (http_header == NULL || out == NULL) {
                ast_http_request_close_on_completion(ser);
-               ast_http_error(ser, 500, "Server Error", "Internal Server Error (ast_str_create() out of memory)\n");
+               ast_http_error(ser, 500, "Server Error", "Internal Server Error (ast_str_create() out of memory)");
                close_mansession_file(&s);
                goto auth_callback_out;
        }
@@ -7367,11 +8244,10 @@ auth_callback_out:
        ast_free(out);
 
        ao2_lock(session);
-       if (session->f) {
-               fclose(session->f);
+       if (session->stream) {
+               ast_iostream_close(session->stream);
+               session->stream = NULL;
        }
-       session->f = NULL;
-       session->fd = -1;
        ao2_unlock(session);
 
        if (session->needdestroy) {
@@ -7635,7 +8511,7 @@ static char *handle_manager_show_settings(struct ast_cli_entry *e, int cmd, stru
        ast_cli(a->fd, FORMAT, "Manager (AMI):", AST_CLI_YESNO(manager_enabled));
        ast_cli(a->fd, FORMAT, "Web Manager (AMI/HTTP):", AST_CLI_YESNO(webmanager_enabled));
        ast_cli(a->fd, FORMAT, "TCP Bindaddress:", manager_enabled != 0 ? ast_sockaddr_stringify(&ami_desc.local_address) : "Disabled");
-       ast_cli(a->fd, FORMAT2, "HTTP Timeout (minutes):", httptimeout);
+       ast_cli(a->fd, FORMAT2, "HTTP Timeout (seconds):", httptimeout);
        ast_cli(a->fd, FORMAT, "TLS Enable:", AST_CLI_YESNO(ami_tls_cfg.enabled));
        ast_cli(a->fd, FORMAT, "TLS Bindaddress:", ami_tls_cfg.enabled != 0 ? ast_sockaddr_stringify(&amis_desc.local_address) : "Disabled");
        ast_cli(a->fd, FORMAT, "TLS Certfile:", ami_tls_cfg.certfile);
@@ -7742,15 +8618,49 @@ static char *handle_manager_show_events(struct ast_cli_entry *e, int cmd, struct
        return CLI_SUCCESS;
 }
 
+static void print_event_instance(struct ast_cli_args *a, struct ast_xml_doc_item *instance)
+{
+       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64], arguments_title[64];
+
+       term_color(synopsis_title, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
+       term_color(description_title, "[Description]\n", COLOR_MAGENTA, 0, 40);
+       term_color(syntax_title, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
+       term_color(seealso_title, "[See Also]\n", COLOR_MAGENTA, 0, 40);
+       term_color(arguments_title, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
+
+       if (!ast_strlen_zero(ast_str_buffer(instance->synopsis))) {
+               char *synopsis = ast_xmldoc_printable(ast_str_buffer(instance->synopsis), 1);
+               ast_cli(a->fd, "%s%s\n\n", synopsis_title, synopsis);
+               ast_free(synopsis);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->syntax))) {
+               char *syntax = ast_xmldoc_printable(ast_str_buffer(instance->syntax), 1);
+               ast_cli(a->fd, "%s%s\n\n", syntax_title, syntax);
+               ast_free(syntax);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->description))) {
+               char *description = ast_xmldoc_printable(ast_str_buffer(instance->description), 1);
+               ast_cli(a->fd, "%s%s\n\n", description_title, description);
+               ast_free(description);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->arguments))) {
+               char *arguments = ast_xmldoc_printable(ast_str_buffer(instance->arguments), 1);
+               ast_cli(a->fd, "%s%s\n\n", arguments_title, arguments);
+               ast_free(arguments);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->seealso))) {
+               char *seealso = ast_xmldoc_printable(ast_str_buffer(instance->seealso), 1);
+               ast_cli(a->fd, "%s%s\n\n", seealso_title, seealso);
+               ast_free(seealso);
+       }
+}
+
 static char *handle_manager_show_event(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        RAII_VAR(struct ao2_container *, events, NULL, ao2_cleanup);
        struct ao2_iterator it_events;
        struct ast_xml_doc_item *item, *temp;
        int length;
-       int which;
-       char *match = NULL;
-       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64], arguments_title[64];
 
        if (cmd == CLI_INIT) {
                e->command = "manager show event";
@@ -7767,19 +8677,24 @@ static char *handle_manager_show_event(struct ast_cli_entry *e, int cmd, struct
        }
 
        if (cmd == CLI_GENERATE) {
+               if (a->pos != 3) {
+                       return NULL;
+               }
+
                length = strlen(a->word);
-               which = 0;
                it_events = ao2_iterator_init(events, 0);
                while ((item = ao2_iterator_next(&it_events))) {
-                       if (!strncasecmp(a->word, item->name, length) && ++which > a->n) {
-                               match = ast_strdup(item->name);
-                               ao2_ref(item, -1);
-                               break;
+                       if (!strncasecmp(a->word, item->name, length)) {
+                               if (ast_cli_completion_add(ast_strdup(item->name))) {
+                                       ao2_ref(item, -1);
+                                       break;
+                               }
                        }
                        ao2_ref(item, -1);
                }
                ao2_iterator_destroy(&it_events);
-               return match;
+
+               return NULL;
        }
 
        if (a->argc != 4) {
@@ -7791,39 +8706,9 @@ static char *handle_manager_show_event(struct ast_cli_entry *e, int cmd, struct
                return CLI_SUCCESS;
        }
 
-       term_color(synopsis_title, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
-       term_color(description_title, "[Description]\n", COLOR_MAGENTA, 0, 40);
-       term_color(syntax_title, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
-       term_color(seealso_title, "[See Also]\n", COLOR_MAGENTA, 0, 40);
-       term_color(arguments_title, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
-
        ast_cli(a->fd, "Event: %s\n", a->argv[3]);
-       for (temp = item; temp; temp = temp->next) {
-               if (!ast_strlen_zero(ast_str_buffer(temp->synopsis))) {
-                       char *synopsis = ast_xmldoc_printable(ast_str_buffer(temp->synopsis), 1);
-                       ast_cli(a->fd, "%s%s\n\n", synopsis_title, synopsis);
-                       ast_free(synopsis);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->syntax))) {
-                       char *syntax = ast_xmldoc_printable(ast_str_buffer(temp->syntax), 1);
-                       ast_cli(a->fd, "%s%s\n\n", syntax_title, syntax);
-                       ast_free(syntax);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->description))) {
-                       char *description = ast_xmldoc_printable(ast_str_buffer(temp->description), 1);
-                       ast_cli(a->fd, "%s%s\n\n", description_title, description);
-                       ast_free(description);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->arguments))) {
-                       char *arguments = ast_xmldoc_printable(ast_str_buffer(temp->arguments), 1);
-                       ast_cli(a->fd, "%s%s\n\n", arguments_title, arguments);
-                       ast_free(arguments);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->seealso))) {
-                       char *seealso = ast_xmldoc_printable(ast_str_buffer(temp->seealso), 1);
-                       ast_cli(a->fd, "%s%s\n\n", seealso_title, seealso);
-                       ast_free(seealso);
-               }
+       for (temp = item; temp; temp = AST_LIST_NEXT(temp, next)) {
+               print_event_instance(a, temp);
        }
 
        ao2_ref(item, -1);
@@ -7873,49 +8758,6 @@ static void load_channelvars(struct ast_variable *var)
        ast_channel_set_manager_vars(args.argc, args.vars);
 }
 
-#ifdef TEST_FRAMEWORK
-
-static void test_suite_event_cb(void *data, struct stasis_subscription *sub,
-               struct stasis_message *message)
-{
-       struct ast_test_suite_message_payload *payload;
-       struct ast_json *blob;
-       const char *type;
-
-       if (stasis_message_type(message) != ast_test_suite_message_type()) {
-               return;
-       }
-
-       payload = stasis_message_data(message);
-       if (!payload) {
-               return;
-       }
-       blob = ast_test_suite_get_blob(payload);
-       if (!blob) {
-               return;
-       }
-
-       type = ast_json_string_get(ast_json_object_get(blob, "type"));
-       if (ast_strlen_zero(type) || strcmp("testevent", type)) {
-               return;
-       }
-
-       manager_event(EVENT_FLAG_TEST, "TestEvent",
-               "Type: StateChange\r\n"
-               "State: %s\r\n"
-               "AppFile: %s\r\n"
-               "AppFunction: %s\r\n"
-               "AppLine: %jd\r\n"
-               "%s\r\n",
-               ast_json_string_get(ast_json_object_get(blob, "state")),
-               ast_json_string_get(ast_json_object_get(blob, "appfile")),
-               ast_json_string_get(ast_json_object_get(blob, "appfunction")),
-               ast_json_integer_get(ast_json_object_get(blob, "line")),
-               ast_json_string_get(ast_json_object_get(blob, "data")));
-}
-
-#endif
-
 /*!
  * \internal
  * \brief Free a user record.  Should already be removed from the list
@@ -7943,6 +8785,9 @@ static void manager_shutdown(void)
 {
        struct ast_manager_user *user;
 
+       /* This event is not actually transmitted, but causes all TCP sessions to be closed */
+       manager_event(EVENT_FLAG_SHUTDOWN, "CloseSession", "CloseSession: true\r\n");
+
        ast_manager_unregister("Ping");
        ast_manager_unregister("Events");
        ast_manager_unregister("Logoff");
@@ -7959,6 +8804,7 @@ static void manager_shutdown(void)
        ast_manager_unregister("ListCategories");
        ast_manager_unregister("Redirect");
        ast_manager_unregister("Atxfer");
+       ast_manager_unregister("CancelAtxfer");
        ast_manager_unregister("Originate");
        ast_manager_unregister("Command");
        ast_manager_unregister("ExtensionState");
@@ -7987,6 +8833,11 @@ static void manager_shutdown(void)
        ao2_t_global_obj_release(event_docs, "Dispose of event_docs");
 #endif
 
+#ifdef TEST_FRAMEWORK
+       stasis_forward_cancel(test_suite_forwarder);
+       test_suite_forwarder = NULL;
+#endif
+
        if (stasis_router) {
                stasis_message_router_unsubscribe_and_join(stasis_router);
                stasis_router = NULL;
@@ -8008,12 +8859,19 @@ static void manager_shutdown(void)
        ami_tls_cfg.pvtfile = NULL;
        ast_free(ami_tls_cfg.cipher);
        ami_tls_cfg.cipher = NULL;
+       ast_free(ami_tls_cfg.cafile);
+       ami_tls_cfg.cafile = NULL;
+       ast_free(ami_tls_cfg.capath);
+       ami_tls_cfg.capath = NULL;
 
        ao2_global_obj_release(mgr_sessions);
 
        while ((user = AST_LIST_REMOVE_HEAD(&users, list))) {
                manager_free_user(user);
        }
+       acl_change_stasis_unsubscribe();
+
+       ast_free(manager_channelvars);
 }
 
 
@@ -8038,6 +8896,8 @@ static int manager_subscriptions_init(void)
        if (!stasis_router) {
                return -1;
        }
+       stasis_message_router_set_congestion_limits(stasis_router, -1,
+               6 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL);
 
        res |= stasis_message_router_set_default(stasis_router,
                manager_default_msg_cb, NULL);
@@ -8103,6 +8963,10 @@ static void manager_set_defaults(void)
        ami_tls_cfg.pvtfile = ast_strdup("");
        ast_free(ami_tls_cfg.cipher);
        ami_tls_cfg.cipher = ast_strdup("");
+       ast_free(ami_tls_cfg.cafile);
+       ami_tls_cfg.cafile = ast_strdup("");
+       ast_free(ami_tls_cfg.capath);
+       ami_tls_cfg.capath = ast_strdup("");
 }
 
 static int __init_manager(int reload, int by_external_config)
@@ -8128,8 +8992,6 @@ static int __init_manager(int reload, int by_external_config)
 #endif
                int res;
 
-               ast_register_atexit(manager_shutdown);
-
                res = STASIS_MESSAGE_TYPE_INIT(ast_manager_get_generic_type);
                if (res != 0) {
                        return -1;
@@ -8156,6 +9018,7 @@ static int __init_manager(int reload, int by_external_config)
                ast_manager_register_xml_core("ListCategories", EVENT_FLAG_CONFIG, action_listcategories);
                ast_manager_register_xml_core("Redirect", EVENT_FLAG_CALL, action_redirect);
                ast_manager_register_xml_core("Atxfer", EVENT_FLAG_CALL, action_atxfer);
+               ast_manager_register_xml_core("CancelAtxfer", EVENT_FLAG_CALL, action_cancel_atxfer);
                ast_manager_register_xml_core("Originate", EVENT_FLAG_ORIGINATE, action_originate);
                ast_manager_register_xml_core("Command", EVENT_FLAG_COMMAND, action_command);
                ast_manager_register_xml_core("ExtensionState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstate);
@@ -8179,7 +9042,7 @@ static int __init_manager(int reload, int by_external_config)
                ast_manager_register_xml_core("BlindTransfer", EVENT_FLAG_CALL, action_blind_transfer);
 
 #ifdef TEST_FRAMEWORK
-               stasis_subscribe(ast_test_suite_topic(), test_suite_event_cb, NULL);
+               test_suite_forwarder = stasis_forward_all(ast_test_suite_topic(), manager_topic);
 #endif
 
                ast_cli_register_multiple(cli_manager, ARRAY_LEN(cli_manager));
@@ -8200,7 +9063,7 @@ static int __init_manager(int reload, int by_external_config)
 #endif
 
                /* If you have a NULL hash fn, you only need a single bucket */
-               sessions = ao2_container_alloc(1, NULL, mansession_cmp_fn);
+               sessions = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, mansession_cmp_fn);
                if (!sessions) {
                        return -1;
                }
@@ -8456,8 +9319,8 @@ static int __init_manager(int reload, int by_external_config)
                        /* Default allowmultiplelogin from [general] */
                        user->allowmultiplelogin = allowmultiplelogin;
                        user->writetimeout = 100;
-                       user->whitefilters = ao2_container_alloc(1, NULL, NULL);
-                       user->blackfilters = ao2_container_alloc(1, NULL, NULL);
+                       user->whitefilters = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+                       user->blackfilters = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
                        if (!user->whitefilters || !user->blackfilters) {
                                manager_free_user(user);
                                break;
@@ -8484,7 +9347,14 @@ static int __init_manager(int reload, int by_external_config)
                        } else if (!strcasecmp(var->name, "deny") ||
                                       !strcasecmp(var->name, "permit") ||
                                       !strcasecmp(var->name, "acl")) {
-                               ast_append_acl(var->name, var->value, &user->acl, NULL, &acl_subscription_flag);
+                               int acl_error = 0;
+
+                               ast_append_acl(var->name, var->value, &user->acl, &acl_error, &acl_subscription_flag);
+                               if (acl_error) {
+                                       ast_log(LOG_ERROR, "Invalid ACL '%s' for manager user '%s' on line %d. Deleting user\n",
+                                               var->value, user->username, var->lineno);
+                                       user->keep = 0;
+                               }
                        }  else if (!strcasecmp(var->name, "read") ) {
                                user->readperm = get_perm(var->value);
                        }  else if (!strcasecmp(var->name, "write") ) {
@@ -8604,12 +9474,19 @@ static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub,
        __init_manager(1, 1);
 }
 
-int init_manager(void)
+static int unload_module(void)
+{
+       return 0;
+}
+
+static int load_module(void)
 {
-       return __init_manager(0, 0);
+       ast_register_cleanup(manager_shutdown);
+
+       return __init_manager(0, 0) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS;
 }
 
-int reload_manager(void)
+static int reload_module(void)
 {
        return __init_manager(1, 0);
 }
@@ -8654,28 +9531,22 @@ struct ast_datastore *astman_datastore_find(struct mansession *s, const struct a
 }
 
 int ast_str_append_event_header(struct ast_str **fields_string,
-                                       const char *header, const char *value)
+       const char *header, const char *value)
 {
-       struct ast_str *working_str = *fields_string;
-
-       if (!working_str) {
-               working_str = ast_str_create(128);
-               if (!working_str) {
+       if (!*fields_string) {
+               *fields_string = ast_str_create(128);
+               if (!*fields_string) {
                        return -1;
                }
-               *fields_string = working_str;
        }
 
-       ast_str_append(&working_str, 0,
-               "%s: %s\r\n",
-               header, value);
-
-       return 0;
+       return (ast_str_append(fields_string, 0, "%s: %s\r\n", header, value) < 0) ? -1 : 0;
 }
 
 static void manager_event_blob_dtor(void *obj)
 {
        struct ast_manager_event_blob *ev = obj;
+
        ast_string_field_free_memory(ev);
 }
 
@@ -8687,18 +9558,19 @@ ast_manager_event_blob_create(
        const char *extra_fields_fmt,
        ...)
 {
-       RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+       struct ast_manager_event_blob *ev;
        va_list argp;
 
        ast_assert(extra_fields_fmt != NULL);
        ast_assert(manager_event != NULL);
 
-       ev = ao2_alloc(sizeof(*ev), manager_event_blob_dtor);
+       ev = ao2_alloc_options(sizeof(*ev), manager_event_blob_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
        if (!ev) {
                return NULL;
        }
 
        if (ast_string_field_init(ev, 20)) {
+               ao2_ref(ev, -1);
                return NULL;
        }
 
@@ -8706,10 +9578,17 @@ ast_manager_event_blob_create(
        ev->event_flags = event_flags;
 
        va_start(argp, extra_fields_fmt);
-       ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
-                                     argp);
+       ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt, argp);
        va_end(argp);
 
-       ao2_ref(ev, +1);
        return ev;
 }
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk Manager Interface",
+       .support_level = AST_MODULE_SUPPORT_CORE,
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload_module,
+       .load_pri = AST_MODPRI_CORE,
+       .requires = "extconfig,acl,http",
+);