res_rtp_asterisk: Avoid close the rtp/rtcp fd twice.
[asterisk/asterisk.git] / apps / app_chanspy.c
index ca19294..95ebace 100644 (file)
  * \ingroup applications
  */
 
-#include "asterisk.h"
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk.h"
 
 #include <ctype.h>
+#include <errno.h>
 
 #include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
 #include "asterisk/file.h"
@@ -45,135 +48,328 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/say.h"
 #include "asterisk/pbx.h"
 #include "asterisk/translate.h"
+#include "asterisk/manager.h"
 #include "asterisk/module.h"
 #include "asterisk/lock.h"
 #include "asterisk/options.h"
+#include "asterisk/autochan.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/json.h"
+#include "asterisk/format_cache.h"
 
 #define AST_NAME_STRLEN 256
 #define NUM_SPYGROUPS 128
 
-static const char *tdesc = "Listen to a channel, and optionally whisper into it";
-static const char *app_chan = "ChanSpy";
-static const char *desc_chan =
-"  ChanSpy([chanprefix][,options]): This application is used to listen to the\n"
-"audio from an Asterisk channel. This includes the audio coming in and\n"
-"out of the channel being spied on. If the 'chanprefix' parameter is specified,\n"
-"only channels beginning with this string will be spied upon.\n"
-"  While spying, the following actions may be performed:\n"
-"    - Dialing # cycles the volume level.\n"
-"    - Dialing * will stop spying and look for another channel to spy on.\n"
-"    - Dialing a series of digits followed by # builds a channel name to append\n"
-"      to 'chanprefix'. For example, executing ChanSpy(Agent) and then dialing\n"
-"      the digits '1234#' while spying will begin spying on the channel\n"
-"      'Agent/1234'. Note that this feature will be overriden if the 'd' option\n"
-"       is used\n"
-"  Note: The X option supersedes the three features above in that if a valid\n"
-"        single digit extension exists in the correct context ChanSpy will\n"
-"        exit to it. This also disables choosing a channel based on 'chanprefix'\n"
-"        and a digit sequence.\n"
-"  Options:\n"
-"    b                      - Only spy on channels involved in a bridged call.\n"
-"    B                      - Instead of whispering on a single channel barge in on both\n"
-"                             channels involved in the call.\n"
-"    d                      - Override the typical numeric DTMF functionality and instead\n"
-"                             use DTMF to switch between spy modes.\n"
-"                                     4 = spy mode\n"
-"                                     5 = whisper mode\n"
-"                                     6 = barge mode\n"
-"    g(grp)                 - Only spy on channels in which one or more of the groups \n"
-"                             listed in 'grp' matches one or more groups from the\n"
-"                             SPYGROUP variable set on the channel to be spied upon.\n"
-"                             Note that both 'grp' and SPYGROUP can contain either a\n"
-"                             single group or a colon-delimited list of groups, such\n"
-"                             as 'sales:support:accounting'.\n"
-"    n([mailbox][@context]) - Say the name of the person being spied on if that person has recorded\n"
-"                             his/her name. If a context is specified, then that voicemail context will\n"
-"                             be searched when retrieving the name, otherwise the \"default\" context\n"
-"                             will be searched. If no mailbox is specified, then the channel name will\n"
-"                             be used when searching for the name (i.e. if SIP/1000 is the channel being\n"
-"                             spied on and no mailbox is specified, then \"1000\" will be used when searching\n"
-"                             for the name).\n"
-"    q                      - Don't play a beep when beginning to spy on a channel, or speak the\n"
-"                             selected channel name.\n"
-"    r[(basename)]          - Record the session to the monitor spool directory. An\n"
-"                             optional base for the filename may be specified. The\n"
-"                             default is 'chanspy'.\n"
-"    s                      - Skip the playback of the channel type (i.e. SIP, IAX, etc) when\n"
-"                             speaking the selected channel name.\n"
-"    v([value])             - Adjust the initial volume in the range from -4 to 4. A\n"
-"                             negative value refers to a quieter setting.\n"
-"    w                      - Enable 'whisper' mode, so the spying channel can talk to\n"
-"                             the spied-on channel.\n"
-"    W                      - Enable 'private whisper' mode, so the spying channel can\n"
-"                             talk to the spied-on channel but cannot listen to that\n"
-"                             channel.\n"
-"    o                      - Only listen to audio coming from this channel.\n"
-"    X                      - Allow the user to exit ChanSpy to a valid single digit\n"
-"                             numeric extension in the current context or the context\n"
-"                             specified by the SPY_EXIT_CONTEXT channel variable. The\n"
-"                             name of the last channel that was spied on will be stored\n"
-"                             in the SPY_CHANNEL variable.\n"
-"    e(ext)                 - Enable 'enforced' mode, so the spying channel can\n"
-"                             only monitor extensions whose name is in the 'ext' : \n"
-"                             delimited list.\n"
-;
-
-static const char *app_ext = "ExtenSpy";
-static const char *desc_ext =
-"  ExtenSpy(exten[@context][,options]): This application is used to listen to the\n"
-"audio from an Asterisk channel. This includes the audio coming in and\n"
-"out of the channel being spied on. Only channels created by outgoing calls for the\n"
-"specified extension will be selected for spying. If the optional context is not\n"
-"supplied, the current channel's context will be used.\n"
-"  While spying, the following actions may be performed:\n"
-"    - Dialing # cycles the volume level.\n"
-"    - Dialing * will stop spying and look for another channel to spy on.\n"
-"  Note: The X option superseeds the two features above in that if a valid\n"
-"        single digit extension exists in the correct context it ChanSpy will\n"
-"        exit to it.\n"
-"  Options:\n"
-"    b                      - Only spy on channels involved in a bridged call.\n"
-"    B                      - Instead of whispering on a single channel barge in on both\n"
-"                             channels involved in the call.\n"
-"    d                      - Override the typical numeric DTMF functionality and instead\n"
-"                             use DTMF to switch between spy modes.\n"
-"                                     4 = spy mode\n"
-"                                     5 = whisper mode\n"
-"                                     6 = barge mode\n"
-"    g(grp)                 - Only spy on channels in which one or more of the groups \n"
-"                             listed in 'grp' matches one or more groups from the\n"
-"                             SPYGROUP variable set on the channel to be spied upon.\n"
-"                             Note that both 'grp' and SPYGROUP can contain either a\n"
-"                             single group or a colon-delimited list of groups, such\n"
-"                             as 'sales:support:accounting'.\n"
-"    n([mailbox][@context]) - Say the name of the person being spied on if that person has recorded\n"
-"                             his/her name. If a context is specified, then that voicemail context will\n"
-"                             be searched when retrieving the name, otherwise the \"default\" context\n"
-"                             will be searched. If no mailbox is specified, then the channel name will\n"
-"                             be used when searching for the name (i.e. if SIP/1000 is the channel being\n"
-"                             spied on and no mailbox is specified, then \"1000\" will be used when searching\n"
-"                             for the name).\n"
-"    q                      - Don't play a beep when beginning to spy on a channel, or speak the\n"
-"                             selected channel name.\n"
-"    r[(basename)]          - Record the session to the monitor spool directory. An\n"
-"                             optional base for the filename may be specified. The\n"
-"                             default is 'chanspy'.\n"
-"    s                      - Skip the playback of the channel type (i.e. SIP, IAX, etc) when\n"
-"                             speaking the selected channel name.\n"
-"    v([value])             - Adjust the initial volume in the range from -4 to 4. A\n"
-"                             negative value refers to a quieter setting.\n"
-"    w                      - Enable 'whisper' mode, so the spying channel can talk to\n"
-"                             the spied-on channel.\n"
-"    W                      - Enable 'private whisper' mode, so the spying channel can\n"
-"                             talk to the spied-on channel but cannot listen to that\n"
-"                             channel.\n"
-"    o                      - Only listen to audio coming from this channel.\n"
-"    X                      - Allow the user to exit ChanSpy to a valid single digit\n"
-"                             numeric extension in the current context or the context\n"
-"                             specified by the SPY_EXIT_CONTEXT channel variable. The\n"
-"                             name of the last channel that was spied on will be stored\n"
-"                             in the SPY_CHANNEL variable.\n"
-;
+/*** DOCUMENTATION
+       <application name="ChanSpy" language="en_US">
+               <synopsis>
+                       Listen to a channel, and optionally whisper into it.
+               </synopsis>
+               <syntax>
+                       <parameter name="chanprefix" />
+                       <parameter name="options">
+                               <optionlist>
+                                       <option name="b">
+                                               <para>Only spy on channels involved in a bridged call.</para>
+                                       </option>
+                                       <option name="B">
+                                               <para>Instead of whispering on a single channel barge in on both
+                                               channels involved in the call.</para>
+                                       </option>
+                                       <option name="c">
+                                               <argument name="digit" required="true">
+                                                       <para>Specify a DTMF digit that can be used to spy on the next available channel.</para>
+                                               </argument>
+                                       </option>
+                                       <option name="d">
+                                               <para>Override the typical numeric DTMF functionality and instead
+                                               use DTMF to switch between spy modes.</para>
+                                               <enumlist>
+                                                       <enum name="4">
+                                                               <para>spy mode</para>
+                                                       </enum>
+                                                       <enum name="5">
+                                                               <para>whisper mode</para>
+                                                       </enum>
+                                                       <enum name="6">
+                                                               <para>barge mode</para>
+                                                       </enum>
+                                               </enumlist>
+                                       </option>
+                                       <option name="e">
+                                               <argument name="ext" required="true" />
+                                               <para>Enable <emphasis>enforced</emphasis> mode, so the spying channel can
+                                               only monitor extensions whose name is in the <replaceable>ext</replaceable> : delimited 
+                                               list.</para>
+                                       </option>
+                                       <option name="E">
+                                               <para>Exit when the spied-on channel hangs up.</para>
+                                       </option>
+                                       <option name="g">
+                                               <argument name="grp" required="true">
+                                                       <para>Only spy on channels in which one or more of the groups
+                                                       listed in <replaceable>grp</replaceable> matches one or more groups from the
+                                                       <variable>SPYGROUP</variable> variable set on the channel to be spied upon.</para>
+                                               </argument>
+                                               <note><para>both <replaceable>grp</replaceable> and <variable>SPYGROUP</variable> can contain 
+                                               either a single group or a colon-delimited list of groups, such
+                                               as <literal>sales:support:accounting</literal>.</para></note>
+                                       </option>
+                                       <option name="l">
+                                               <para>Allow usage of a long queue to store audio frames.</para>
+                                               <note><para>This may introduce some delay in the received audio feed, but will improve the audio quality.</para></note>
+                                       </option>
+                                       <option name="n" argsep="@">
+                                               <para>Say the name of the person being spied on if that person has recorded
+                                               his/her name. If a context is specified, then that voicemail context will
+                                               be searched when retrieving the name, otherwise the <literal>default</literal> context
+                                               be used when searching for the name (i.e. if SIP/1000 is the channel being
+                                               spied on and no mailbox is specified, then <literal>1000</literal> will be used when searching
+                                               for the name).</para>
+                                               <argument name="mailbox" />
+                                               <argument name="context" />
+                                       </option>
+                                       <option name="o">
+                                               <para>Only listen to audio coming from this channel.</para>
+                                       </option>
+                                       <option name="q">
+                                               <para>Don't play a beep when beginning to spy on a channel, or speak the
+                                               selected channel name.</para>
+                                       </option>
+                                       <option name="r">
+                                               <para>Record the session to the monitor spool directory. An optional base for the filename 
+                                               may be specified. The default is <literal>chanspy</literal>.</para>
+                                               <argument name="basename" />
+                                       </option>
+                                       <option name="s">
+                                               <para>Skip the playback of the channel type (i.e. SIP, IAX, etc) when
+                                               speaking the selected channel name.</para>
+                                       </option>
+                                       <option name="S">
+                                               <para>Stop when no more channels are left to spy on.</para>
+                                       </option>
+                                       <option name="u">
+                                               <para>The <literal>chanprefix</literal> parameter is a channel uniqueid
+                                               or fully specified channel name.</para>
+                                       </option>
+                                       <option name="v">
+                                               <argument name="value" />
+                                               <para>Adjust the initial volume in the range from <literal>-4</literal> 
+                                               to <literal>4</literal>. A negative value refers to a quieter setting.</para>
+                                       </option>
+                                       <option name="w">
+                                               <para>Enable <literal>whisper</literal> mode, so the spying channel can talk to
+                                               the spied-on channel.</para>
+                                       </option>
+                                       <option name="W">
+                                               <para>Enable <literal>private whisper</literal> mode, so the spying channel can
+                                               talk to the spied-on channel but cannot listen to that channel.</para>
+                                       </option>
+                                       <option name="x">
+                                               <argument name="digit" required="true">
+                                                       <para>Specify a DTMF digit that can be used to exit the application while actively
+                                                       spying on a channel. If there is no channel being spied on, the DTMF digit will be
+                                                       ignored.</para>
+                                               </argument>
+                                       </option>
+                                       <option name="X">
+                                               <para>Allow the user to exit ChanSpy to a valid single digit
+                                               numeric extension in the current context or the context
+                                               specified by the <variable>SPY_EXIT_CONTEXT</variable> channel variable. The
+                                               name of the last channel that was spied on will be stored
+                                               in the <variable>SPY_CHANNEL</variable> variable.</para>
+                                       </option>
+                               </optionlist>           
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This application is used to listen to the audio from an Asterisk channel. This includes the audio 
+                       coming in and out of the channel being spied on. If the <literal>chanprefix</literal> parameter is specified,
+                       only channels beginning with this string will be spied upon.</para>
+                       <para>While spying, the following actions may be performed:</para>
+                       <para> - Dialing <literal>#</literal> cycles the volume level.</para>
+                       <para> - Dialing <literal>*</literal> will stop spying and look for another channel to spy on.</para>
+                       <para> - Dialing a series of digits followed by <literal>#</literal> builds a channel name to append
+                       to <literal>chanprefix</literal>. For example, executing ChanSpy(Agent) and then dialing the digits '1234#'
+                       while spying will begin spying on the channel 'Agent/1234'. Note that this feature will be overridden
+                       if the 'd' or 'u' options are used.</para>
+                       <note><para>The <replaceable>X</replaceable> option supersedes the three features above in that if a valid
+                       single digit extension exists in the correct context ChanSpy will exit to it.
+                       This also disables choosing a channel based on <literal>chanprefix</literal> and a digit sequence.</para></note>
+               </description>
+               <see-also>
+                       <ref type="application">ExtenSpy</ref>
+                       <ref type="managerEvent">ChanSpyStart</ref>
+                       <ref type="managerEvent">ChanSpyStop</ref>
+               </see-also>
+       </application>
+       <application name="ExtenSpy" language="en_US">
+               <synopsis>
+                       Listen to a channel, and optionally whisper into it.
+               </synopsis>
+               <syntax>
+                       <parameter name="exten" required="true" argsep="@">
+                               <argument name="exten" required="true">
+                                       <para>Specify extension.</para>
+                               </argument>
+                               <argument name="context">
+                                       <para>Optionally specify a context, defaults to <literal>default</literal>.</para>
+                               </argument>
+                       </parameter>
+                       <parameter name="options">
+                               <optionlist>
+                                       <option name="b">
+                                               <para>Only spy on channels involved in a bridged call.</para>
+                                       </option>
+                                       <option name="B">
+                                               <para>Instead of whispering on a single channel barge in on both
+                                               channels involved in the call.</para>
+                                       </option>
+                                       <option name="c">
+                                               <argument name="digit" required="true">
+                                                       <para>Specify a DTMF digit that can be used to spy on the next available channel.</para>
+                                               </argument>
+                                       </option>
+                                       <option name="d">
+                                               <para>Override the typical numeric DTMF functionality and instead
+                                               use DTMF to switch between spy modes.</para>
+                                               <enumlist>
+                                                       <enum name="4">
+                                                               <para>spy mode</para>
+                                                       </enum>
+                                                       <enum name="5">
+                                                               <para>whisper mode</para>
+                                                       </enum>
+                                                       <enum name="6">
+                                                               <para>barge mode</para>
+                                                       </enum>
+                                               </enumlist>
+                                       </option>
+                                       <option name="e">
+                                               <argument name="ext" required="true" />
+                                               <para>Enable <emphasis>enforced</emphasis> mode, so the spying channel can
+                                               only monitor extensions whose name is in the <replaceable>ext</replaceable> : delimited 
+                                               list.</para>
+                                       </option>
+                                       <option name="E">
+                                               <para>Exit when the spied-on channel hangs up.</para>
+                                       </option>
+                                       <option name="g">
+                                               <argument name="grp" required="true">
+                                                       <para>Only spy on channels in which one or more of the groups
+                                                       listed in <replaceable>grp</replaceable> matches one or more groups from the
+                                                       <variable>SPYGROUP</variable> variable set on the channel to be spied upon.</para>
+                                               </argument>
+                                               <note><para>both <replaceable>grp</replaceable> and <variable>SPYGROUP</variable> can contain 
+                                               either a single group or a colon-delimited list of groups, such
+                                               as <literal>sales:support:accounting</literal>.</para></note>
+                                       </option>
+                                       <option name="l">
+                                               <para>Allow usage of a long queue to store audio frames.</para>
+                                               <note><para>This may introduce some delay in the received audio feed, but will improve the audio quality.</para></note>
+                                       </option>
+                                       <option name="n" argsep="@">
+                                               <para>Say the name of the person being spied on if that person has recorded
+                                               his/her name. If a context is specified, then that voicemail context will
+                                               be searched when retrieving the name, otherwise the <literal>default</literal> context
+                                               be used when searching for the name (i.e. if SIP/1000 is the channel being
+                                               spied on and no mailbox is specified, then <literal>1000</literal> will be used when searching
+                                               for the name).</para>
+                                               <argument name="mailbox" />
+                                               <argument name="context" />
+                                       </option>
+                                       <option name="o">
+                                               <para>Only listen to audio coming from this channel.</para>
+                                       </option>
+                                       <option name="q">
+                                               <para>Don't play a beep when beginning to spy on a channel, or speak the
+                                               selected channel name.</para>
+                                       </option>
+                                       <option name="r">
+                                               <para>Record the session to the monitor spool directory. An optional base for the filename 
+                                               may be specified. The default is <literal>chanspy</literal>.</para>
+                                               <argument name="basename" />
+                                       </option>
+                                       <option name="s">
+                                               <para>Skip the playback of the channel type (i.e. SIP, IAX, etc) when
+                                               speaking the selected channel name.</para>
+                                       </option>
+                                       <option name="S">
+                                               <para>Stop when there are no more extensions left to spy on.</para>
+                                       </option>
+                                       <option name="v">
+                                               <argument name="value" />
+                                               <para>Adjust the initial volume in the range from <literal>-4</literal> 
+                                               to <literal>4</literal>. A negative value refers to a quieter setting.</para>
+                                       </option>
+                                       <option name="w">
+                                               <para>Enable <literal>whisper</literal> mode, so the spying channel can talk to
+                                               the spied-on channel.</para>
+                                       </option>
+                                       <option name="W">
+                                               <para>Enable <literal>private whisper</literal> mode, so the spying channel can
+                                               talk to the spied-on channel but cannot listen to that channel.</para>
+                                       </option>
+                                       <option name="x">
+                                               <argument name="digit" required="true">
+                                                       <para>Specify a DTMF digit that can be used to exit the application while actively
+                                                       spying on a channel. If there is no channel being spied on, the DTMF digit will be
+                                                       ignored.</para>
+                                               </argument>
+                                       </option>
+                                       <option name="X">
+                                               <para>Allow the user to exit ChanSpy to a valid single digit
+                                               numeric extension in the current context or the context
+                                               specified by the <variable>SPY_EXIT_CONTEXT</variable> channel variable. The
+                                               name of the last channel that was spied on will be stored
+                                               in the <variable>SPY_CHANNEL</variable> variable.</para>
+                                       </option>
+                               </optionlist>   
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This application is used to listen to the audio from an Asterisk channel. This includes 
+                       the audio coming in and out of the channel being spied on. Only channels created by outgoing calls for the
+                       specified extension will be selected for spying. If the optional context is not supplied, 
+                       the current channel's context will be used.</para>
+                       <para>While spying, the following actions may be performed:</para>
+                       <para> - Dialing <literal>#</literal> cycles the volume level.</para>
+                        <para> - Dialing <literal>*</literal> will stop spying and look for another channel to spy on.</para>
+                       <note><para>The <replaceable>X</replaceable> option supersedes the three features above in that if a valid
+                       single digit extension exists in the correct context ChanSpy will exit to it.
+                       This also disables choosing a channel based on <literal>chanprefix</literal> and a digit sequence.</para></note>
+               </description>
+               <see-also>
+                       <ref type="application">ChanSpy</ref>
+                       <ref type="managerEvent">ChanSpyStart</ref>
+                       <ref type="managerEvent">ChanSpyStop</ref>
+               </see-also>
+       </application>
+       <application name="DAHDIScan" language="en_US">
+               <synopsis>
+                       Scan DAHDI channels to monitor calls.
+               </synopsis>
+               <syntax>
+                       <parameter name="group">
+                               <para>Limit scanning to a channel <replaceable>group</replaceable> by setting this option.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Allows a call center manager to monitor DAHDI channels in a
+                       convenient way.  Use <literal>#</literal> to select the next channel and use <literal>*</literal> to exit.</para>
+               </description>
+               <see-also>
+                       <ref type="managerEvent">ChanSpyStart</ref>
+                       <ref type="managerEvent">ChanSpyStop</ref>
+               </see-also>
+       </application>
+ ***/
+
+static const char app_chan[] = "ChanSpy";
+
+static const char app_ext[] = "ExtenSpy";
+
+static const char app_dahdiscan[] = "DAHDIScan";
 
 enum {
        OPTION_QUIET             = (1 << 0),    /* Quiet, no announcement */
@@ -189,8 +385,15 @@ enum {
        OPTION_NOTECH            = (1 << 10),   /* Skip technology name playback */
        OPTION_BARGE             = (1 << 11),   /* Barge mode (whisper to both channels) */
        OPTION_NAME              = (1 << 12),   /* Say the name of the person on whom we will spy */
-       OPTION_DTMF_SWITCH_MODES = (1 << 13),   /*Allow numeric DTMF to switch between chanspy modes */
-} chanspy_opt_flags;
+       OPTION_DTMF_SWITCH_MODES = (1 << 13),   /* Allow numeric DTMF to switch between chanspy modes */
+       OPTION_DTMF_EXIT         = (1 << 14),   /* Set DTMF to exit, added for DAHDIScan integration */
+       OPTION_DTMF_CYCLE        = (1 << 15),   /* Custom DTMF for cycling next available channel, (default is '*') */
+       OPTION_DAHDI_SCAN        = (1 << 16),   /* Scan groups in DAHDIScan mode */
+       OPTION_STOP              = (1 << 17),
+       OPTION_EXITONHANGUP      = (1 << 18),   /* Hang up when the spied-on channel hangs up. */
+       OPTION_UNIQUEID          = (1 << 19),   /* The chanprefix is a channel uniqueid or fully specified channel name. */
+       OPTION_LONG_QUEUE        = (1 << 20),   /* Allow usage of a long queue to store audio frames. */
+};
 
 enum {
        OPT_ARG_VOLUME = 0,
@@ -198,28 +401,34 @@ enum {
        OPT_ARG_RECORD,
        OPT_ARG_ENFORCED,
        OPT_ARG_NAME,
+       OPT_ARG_EXIT,
+       OPT_ARG_CYCLE,
        OPT_ARG_ARRAY_SIZE,
-} chanspy_opt_args;
+};
 
 AST_APP_OPTIONS(spy_opts, {
-       AST_APP_OPTION('q', OPTION_QUIET),
        AST_APP_OPTION('b', OPTION_BRIDGED),
        AST_APP_OPTION('B', OPTION_BARGE),
-       AST_APP_OPTION('w', OPTION_WHISPER),
-       AST_APP_OPTION('W', OPTION_PRIVATE),
-       AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
-       AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
-       AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
+       AST_APP_OPTION_ARG('c', OPTION_DTMF_CYCLE, OPT_ARG_CYCLE),
+       AST_APP_OPTION('d', OPTION_DTMF_SWITCH_MODES),
        AST_APP_OPTION_ARG('e', OPTION_ENFORCED, OPT_ARG_ENFORCED),
+       AST_APP_OPTION('E', OPTION_EXITONHANGUP),
+       AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
+       AST_APP_OPTION('l', OPTION_LONG_QUEUE),
+       AST_APP_OPTION_ARG('n', OPTION_NAME, OPT_ARG_NAME),
        AST_APP_OPTION('o', OPTION_READONLY),
-       AST_APP_OPTION('X', OPTION_EXIT),
+       AST_APP_OPTION('q', OPTION_QUIET),
+       AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
        AST_APP_OPTION('s', OPTION_NOTECH),
-       AST_APP_OPTION_ARG('n', OPTION_NAME, OPT_ARG_NAME),
-       AST_APP_OPTION('d', OPTION_DTMF_SWITCH_MODES),
+       AST_APP_OPTION('S', OPTION_STOP),
+       AST_APP_OPTION('u', OPTION_UNIQUEID),
+       AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
+       AST_APP_OPTION('w', OPTION_WHISPER),
+       AST_APP_OPTION('W', OPTION_PRIVATE),
+       AST_APP_OPTION_ARG('x', OPTION_DTMF_EXIT, OPT_ARG_EXIT),
+       AST_APP_OPTION('X', OPTION_EXIT),
 });
 
-int next_unique_id_to_use = 0;
-
 struct chanspy_translation_helper {
        /* spy data */
        struct ast_audiohook spy_audiohook;
@@ -227,6 +436,13 @@ struct chanspy_translation_helper {
        struct ast_audiohook bridge_whisper_audiohook;
        int fd;
        int volfactor;
+       struct ast_flags flags;
+};
+
+struct spy_dtmf_options {
+       char exit;
+       char cycle;
+       char volume;
 };
 
 static void *spy_alloc(struct ast_channel *chan, void *data)
@@ -243,7 +459,7 @@ static void spy_release(struct ast_channel *chan, void *data)
 static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
 {
        struct chanspy_translation_helper *csth = data;
-       struct ast_frame *f = NULL;
+       struct ast_frame *f, *cur;
 
        ast_audiohook_lock(&csth->spy_audiohook);
        if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
@@ -252,20 +468,30 @@ static int spy_generate(struct ast_channel *chan, void *data, int len, int sampl
                return -1;
        }
 
-       f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR);
+       if (ast_test_flag(&csth->flags, OPTION_READONLY)) {
+               /* Option 'o' was set, so don't mix channel audio */
+               f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_READ, ast_format_slin);
+       } else {
+               f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, ast_format_slin);
+       }
 
        ast_audiohook_unlock(&csth->spy_audiohook);
 
        if (!f)
                return 0;
 
-       if (ast_write(chan, f)) {
-               ast_frfree(f);
-               return -1;
-       }
+       for (cur = f; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
+               if (ast_write(chan, cur)) {
+                       ast_frfree(f);
+                       return -1;
+               }
 
-       if (csth->fd)
-               write(csth->fd, f->data.ptr, f->datalen);
+               if (csth->fd) {
+                       if (write(csth->fd, cur->data.ptr, cur->datalen) < 0) {
+                               ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+                       }
+               }
+       }
 
        ast_frfree(f);
 
@@ -278,27 +504,28 @@ static struct ast_generator spygen = {
        .generate = spy_generate,
 };
 
-static int start_spying(struct ast_channel *chan, const char *spychan_name, struct ast_audiohook *audiohook)
+static int start_spying(struct ast_autochan *autochan, const char *spychan_name, struct ast_audiohook *audiohook, struct ast_flags *flags)
 {
-       int res = 0;
-       struct ast_channel *peer = NULL;
-
-       ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, chan->name);
+       int res;
 
-       res = ast_audiohook_attach(chan, audiohook);
+       ast_autochan_channel_lock(autochan);
+       ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, ast_channel_name(autochan->chan));
 
-       if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) { 
-               ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
+       if (ast_test_flag(flags, OPTION_READONLY)) {
+               ast_set_flag(audiohook, AST_AUDIOHOOK_MUTE_WRITE);
+       } else {
+               ast_set_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
        }
+       if (ast_test_flag(flags, OPTION_LONG_QUEUE)) {
+               ast_debug(9, "Using a long queue to store audio frames in spy audiohook\n");
+       } else {
+               ast_set_flag(audiohook, AST_AUDIOHOOK_SMALL_QUEUE);
+       }
+       res = ast_audiohook_attach(autochan->chan, audiohook);
+       ast_autochan_channel_unlock(autochan);
        return res;
 }
 
-struct chanspy_ds {
-       struct ast_channel *chan;
-       char unique_id[20];
-       ast_mutex_t lock;
-};
-
 static void change_spy_mode(const char digit, struct ast_flags *flags)
 {
        if (digit == '4') {
@@ -313,70 +540,160 @@ static void change_spy_mode(const char digit, struct ast_flags *flags)
        }
 }
 
-static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chanspy_ds, 
-       int *volfactor, int fd, struct ast_flags *flags, char *exitcontext) 
+static int pack_channel_into_message(struct ast_channel *chan, const char *role,
+                                                                        struct ast_multi_channel_blob *payload)
+{
+       RAII_VAR(struct ast_channel_snapshot *, snapshot,
+                       ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan)),
+                       ao2_cleanup);
+
+       if (!snapshot) {
+               return -1;
+       }
+       ast_multi_channel_blob_add_channel(payload, role, snapshot);
+       return 0;
+}
+
+/*! \internal
+ * \brief Publish the chanspy message over Stasis-Core
+ * \param spyer The channel doing the spying
+ * \param spyee Who is being spied upon
+ * \start start If non-zero, the spying is starting. Otherwise, the spyer is
+ * finishing
+ */
+static void publish_chanspy_message(struct ast_channel *spyer,
+                                                                       struct ast_channel *spyee,
+                                                                       int start)
+{
+       RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+       RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+       struct stasis_message_type *type = start ? ast_channel_chanspy_start_type(): ast_channel_chanspy_stop_type();
+
+       if (!spyer) {
+               ast_log(AST_LOG_WARNING, "Attempt to publish ChanSpy message for NULL spyer channel\n");
+               return;
+       }
+       blob = ast_json_null();
+       if (!blob || !type) {
+               return;
+       }
+
+       payload = ast_multi_channel_blob_create(blob);
+       if (!payload) {
+               return;
+       }
+
+       if (pack_channel_into_message(spyer, "spyer_channel", payload)) {
+               return;
+       }
+
+       if (spyee) {
+               if (pack_channel_into_message(spyee, "spyee_channel", payload)) {
+                       return;
+               }
+       }
+
+       message = stasis_message_create(type, payload);
+       if (!message) {
+               return;
+       }
+       stasis_publish(ast_channel_topic(spyer), message);
+}
+
+static int attach_barge(struct ast_autochan *spyee_autochan,
+       struct ast_autochan **spyee_bridge_autochan, struct ast_audiohook *bridge_whisper_audiohook,
+       const char *spyer_name, const char *name, struct ast_flags *flags)
+{
+       int retval = 0;
+       struct ast_autochan *internal_bridge_autochan;
+       struct ast_channel *spyee_chan;
+       RAII_VAR(struct ast_channel *, bridged, NULL, ast_channel_cleanup);
+
+       ast_autochan_channel_lock(spyee_autochan);
+       spyee_chan = ast_channel_ref(spyee_autochan->chan);
+       ast_autochan_channel_unlock(spyee_autochan);
+       bridged = ast_channel_bridge_peer(spyee_chan);
+       ast_channel_unref(spyee_chan);
+       if (!bridged) {
+               return -1;
+       }
+
+       ast_audiohook_init(bridge_whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Chanspy", 0);
+
+       internal_bridge_autochan = ast_autochan_setup(bridged);
+       if (!internal_bridge_autochan) {
+               return -1;
+       }
+
+       if (start_spying(internal_bridge_autochan, spyer_name, bridge_whisper_audiohook, flags)) {
+               ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee '%s'. Barge mode disabled.\n", name);
+               retval = -1;
+       }
+
+       *spyee_bridge_autochan = internal_bridge_autochan;
+
+       return retval;
+}
+
+static int channel_spy(struct ast_channel *chan, struct ast_autochan *spyee_autochan,
+       int *volfactor, int fd, struct spy_dtmf_options *user_options, struct ast_flags *flags,
+       char *exitcontext)
 {
        struct chanspy_translation_helper csth;
-       int running = 0, res, x = 0;
+       int running = 0, bridge_connected = 0, res, x = 0;
        char inp[24] = {0};
        char *name;
        struct ast_frame *f;
        struct ast_silence_generator *silgen = NULL;
-       struct ast_channel *spyee = NULL, *spyee_bridge = NULL;
+       struct ast_autochan *spyee_bridge_autochan = NULL;
        const char *spyer_name;
 
        ast_channel_lock(chan);
-       spyer_name = ast_strdupa(chan->name);
-       ast_channel_unlock(chan);
-
-       ast_mutex_lock(&spyee_chanspy_ds->lock);
-       if (spyee_chanspy_ds->chan) {
-               spyee = spyee_chanspy_ds->chan;
-               ast_channel_lock(spyee);
-       }
-       ast_mutex_unlock(&spyee_chanspy_ds->lock);
-
-       if (!spyee)
+       if (ast_check_hangup(chan)) {
+               ast_channel_unlock(chan);
                return 0;
+       }
+       spyer_name = ast_strdupa(ast_channel_name(chan));
+       ast_channel_unlock(chan);
 
-       /* We now hold the channel lock on spyee */
-
-       if (ast_check_hangup(chan) || ast_check_hangup(spyee)) {
-               ast_channel_unlock(spyee);
+       ast_autochan_channel_lock(spyee_autochan);
+       if (ast_check_hangup(spyee_autochan->chan)
+               || ast_test_flag(ast_channel_flags(spyee_autochan->chan), AST_FLAG_ZOMBIE)) {
+               ast_autochan_channel_unlock(spyee_autochan);
                return 0;
        }
+       name = ast_strdupa(ast_channel_name(spyee_autochan->chan));
 
-       name = ast_strdupa(spyee->name);
        ast_verb(2, "Spying on channel %s\n", name);
+       publish_chanspy_message(chan, spyee_autochan->chan, 1);
+       ast_autochan_channel_unlock(spyee_autochan);
 
        memset(&csth, 0, sizeof(csth));
+       ast_copy_flags(&csth.flags, flags, AST_FLAGS_ALL);
 
-       ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy");
+       /* This is the audiohook which gives us the audio off the channel we are
+          spying on.
+       */
+       ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy", 0);
 
-       if (start_spying(spyee, spyer_name, &csth.spy_audiohook)) {
+       if (start_spying(spyee_autochan, spyer_name, &csth.spy_audiohook, flags)) {
                ast_audiohook_destroy(&csth.spy_audiohook);
-               ast_channel_unlock(spyee);
                return 0;
        }
 
-       ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy");
-       ast_audiohook_init(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Chanspy");
-       if (start_spying(spyee, spyer_name, &csth.whisper_audiohook)) {
-               ast_log(LOG_WARNING, "Unable to attach whisper audiohook to spyee %s. Whisper mode disabled!\n", spyee->name);
-       }
-       if ((spyee_bridge = ast_bridged_channel(spyee))) {
-               ast_channel_lock(spyee_bridge);
-               if (start_spying(ast_bridged_channel(spyee), spyer_name, &csth.bridge_whisper_audiohook)) {
-                       ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee %s. Barge mode disabled!\n", spyee->name);
+       if (ast_test_flag(flags, OPTION_WHISPER | OPTION_BARGE | OPTION_DTMF_SWITCH_MODES)) {
+               /* This audiohook will let us inject audio from our channel into the
+                  channel we are currently spying on.
+               */
+               ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy", 0);
+
+               if (start_spying(spyee_autochan, spyer_name, &csth.whisper_audiohook, flags)) {
+                       ast_log(LOG_WARNING, "Unable to attach whisper audiohook to spyee %s. Whisper mode disabled!\n", name);
                }
-               ast_channel_unlock(spyee_bridge);
        }
-       ast_channel_unlock(spyee);
-       spyee = NULL;
 
-       ast_channel_lock(chan);
-       ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
-       ast_channel_unlock(chan);
+       ast_channel_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
 
        csth.volfactor = *volfactor;
 
@@ -406,19 +723,35 @@ static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chansp
           has arrived, since the spied-on channel could have gone away while
           we were waiting
        */
-       while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
+       while (ast_waitfor(chan, -1) > -1 && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
                if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
                        running = -1;
+                       if (f) {
+                               ast_frfree(f);
+                       }
                        break;
                }
 
                if (ast_test_flag(flags, OPTION_BARGE) && f->frametype == AST_FRAME_VOICE) {
+                       /* This hook lets us inject audio into the channel that the spyee is currently
+                        * bridged with. If the spyee isn't bridged with anything yet, nothing will
+                        * be attached and we'll need to continue attempting to attach the barge
+                        * audio hook. */
+                       if (!bridge_connected && attach_barge(spyee_autochan, &spyee_bridge_autochan,
+                                       &csth.bridge_whisper_audiohook, spyer_name, name, flags) == 0) {
+                               bridge_connected = 1;
+                       }
+
                        ast_audiohook_lock(&csth.whisper_audiohook);
-                       ast_audiohook_lock(&csth.bridge_whisper_audiohook);
                        ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
-                       ast_audiohook_write_frame(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
                        ast_audiohook_unlock(&csth.whisper_audiohook);
-                       ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
+
+                       if (bridge_connected) {
+                               ast_audiohook_lock(&csth.bridge_whisper_audiohook);
+                               ast_audiohook_write_frame(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
+                               ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
+                       }
+
                        ast_frfree(f);
                        continue;
                } else if (ast_test_flag(flags, OPTION_WHISPER) && f->frametype == AST_FRAME_VOICE) {
@@ -428,8 +761,8 @@ static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chansp
                        ast_frfree(f);
                        continue;
                }
-               
-               res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0;
+
+               res = (f->frametype == AST_FRAME_DTMF) ? f->subclass.integer : 0;
                ast_frfree(f);
                if (!res)
                        continue;
@@ -462,10 +795,13 @@ static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chansp
                        }
                }
 
-               if (res == '*') {
+               if (res == user_options->cycle) {
                        running = 0;
                        break;
-               } else if (res == '#') {
+               } else if (res == user_options->exit) {
+                       running = -2;
+                       break;
+               } else if (res == user_options->volume) {
                        if (!ast_strlen_zero(inp)) {
                                running = atoi(inp);
                                break;
@@ -474,7 +810,7 @@ static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chansp
                        (*volfactor)++;
                        if (*volfactor > 4)
                                *volfactor = -4;
-                       ast_verb(3, "Setting spy volume on %s to %d\n", chan->name, *volfactor);
+                       ast_verb(3, "Setting spy volume on %s to %d\n", ast_channel_name(chan), *volfactor);
 
                        csth.volfactor = *volfactor;
                        csth.spy_audiohook.options.read_volume = csth.volfactor;
@@ -487,190 +823,114 @@ static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chansp
        else
                ast_deactivate_generator(chan);
 
-       ast_channel_lock(chan);
-       ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
-       ast_channel_unlock(chan);
+       ast_channel_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
 
-       ast_audiohook_lock(&csth.whisper_audiohook);
-       ast_audiohook_detach(&csth.whisper_audiohook);
-       ast_audiohook_unlock(&csth.whisper_audiohook);
-       ast_audiohook_destroy(&csth.whisper_audiohook);
-       ast_audiohook_lock(&csth.bridge_whisper_audiohook);
-       ast_audiohook_detach(&csth.bridge_whisper_audiohook);
-       ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
-       ast_audiohook_destroy(&csth.bridge_whisper_audiohook);
+       if (ast_test_flag(flags, OPTION_WHISPER | OPTION_BARGE | OPTION_DTMF_SWITCH_MODES)) {
+               ast_audiohook_lock(&csth.whisper_audiohook);
+               ast_audiohook_detach(&csth.whisper_audiohook);
+               ast_audiohook_unlock(&csth.whisper_audiohook);
+               ast_audiohook_destroy(&csth.whisper_audiohook);
+       }
+
+       if (ast_test_flag(flags, OPTION_BARGE | OPTION_DTMF_SWITCH_MODES)) {
+               ast_audiohook_lock(&csth.bridge_whisper_audiohook);
+               ast_audiohook_detach(&csth.bridge_whisper_audiohook);
+               ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
+               ast_audiohook_destroy(&csth.bridge_whisper_audiohook);
+       }
 
        ast_audiohook_lock(&csth.spy_audiohook);
        ast_audiohook_detach(&csth.spy_audiohook);
        ast_audiohook_unlock(&csth.spy_audiohook);
        ast_audiohook_destroy(&csth.spy_audiohook);
-       
-       ast_verb(2, "Done Spying on channel %s\n", name);
-
-       return running;
-}
 
-/*!
- * \note This relies on the embedded lock to be recursive, as it may be called
- * due to a call to chanspy_ds_free with the lock held there.
- */
-static void chanspy_ds_destroy(void *data)
-{
-       struct chanspy_ds *chanspy_ds = data;
+       if (spyee_bridge_autochan) {
+               ast_autochan_destroy(spyee_bridge_autochan);
+       }
 
-       /* Setting chan to be NULL is an atomic operation, but we don't want this
-        * value to change while this lock is held.  The lock is held elsewhere
-        * while it performs non-atomic operations with this channel pointer */
+       ast_verb(2, "Done Spying on channel %s\n", name);
+       publish_chanspy_message(chan, NULL, 0);
 
-       ast_mutex_lock(&chanspy_ds->lock);
-       chanspy_ds->chan = NULL;
-       ast_mutex_unlock(&chanspy_ds->lock);
+       return running;
 }
 
-static void chanspy_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+static struct ast_autochan *next_channel(struct ast_channel_iterator *iter,
+       struct ast_channel *chan)
 {
-       struct chanspy_ds *chanspy_ds = data;
-       
-       ast_mutex_lock(&chanspy_ds->lock);
-       chanspy_ds->chan = new_chan;
-       ast_mutex_unlock(&chanspy_ds->lock);
-}
-
-static const struct ast_datastore_info chanspy_ds_info = {
-       .type = "chanspy",
-       .destroy = chanspy_ds_destroy,
-       .chan_fixup = chanspy_ds_chan_fixup,
-};
+       struct ast_channel *next;
+       struct ast_autochan *autochan_store;
+       const size_t pseudo_len = strlen("DAHDI/pseudo");
 
-static struct chanspy_ds *chanspy_ds_free(struct chanspy_ds *chanspy_ds)
-{
-       if (!chanspy_ds)
+       if (!iter) {
                return NULL;
-
-       ast_mutex_lock(&chanspy_ds->lock);
-       if (chanspy_ds->chan) {
-               struct ast_datastore *datastore;
-               struct ast_channel *chan;
-
-               chan = chanspy_ds->chan;
-
-               ast_channel_lock(chan);
-               if ((datastore = ast_channel_datastore_find(chan, &chanspy_ds_info, chanspy_ds->unique_id))) {
-                       ast_channel_datastore_remove(chan, datastore);
-                       /* chanspy_ds->chan is NULL after this call */
-                       chanspy_ds_destroy(datastore->data);
-                       datastore->data = NULL;
-                       ast_channel_datastore_free(datastore);
-               }
-               ast_channel_unlock(chan);
        }
-       ast_mutex_unlock(&chanspy_ds->lock);
-
-       return NULL;
-}
 
-/*! \note Returns the channel in the chanspy_ds locked as well as the chanspy_ds locked */
-static struct chanspy_ds *setup_chanspy_ds(struct ast_channel *chan, struct chanspy_ds *chanspy_ds)
-{
-       struct ast_datastore *datastore = NULL;
+       for (; (next = ast_channel_iterator_next(iter)); ast_channel_unref(next)) {
+               if (!strncmp(ast_channel_name(next), "DAHDI/pseudo", pseudo_len)
+                       || next == chan) {
+                       continue;
+               }
 
-       ast_mutex_lock(&chanspy_ds->lock);
+               autochan_store = ast_autochan_setup(next);
+               ast_channel_unref(next);
 
-       if (!(datastore = ast_channel_datastore_alloc(&chanspy_ds_info, chanspy_ds->unique_id))) {
-               ast_mutex_unlock(&chanspy_ds->lock);
-               chanspy_ds = chanspy_ds_free(chanspy_ds);
-               ast_channel_unlock(chan);
-               return NULL;
+               return autochan_store;
        }
-       
-       chanspy_ds->chan = chan;
-       datastore->data = chanspy_ds;
-       ast_channel_datastore_add(chan, datastore);
-
-       return chanspy_ds;
+       return NULL;
 }
 
-static struct chanspy_ds *next_channel(struct ast_channel *chan,
-       const struct ast_channel *last, const char *spec,
-       const char *exten, const char *context, struct chanspy_ds *chanspy_ds)
+static int spy_sayname(struct ast_channel *chan, const char *mailbox, const char *context)
 {
-       struct ast_channel *next;
-       char channel_name[AST_CHANNEL_NAME];
-
-redo:
-       if (!ast_strlen_zero(spec))
-               next = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec));
-       else if (!ast_strlen_zero(exten))
-               next = ast_walk_channel_by_exten_locked(last, exten, context);
-       else
-               next = ast_channel_walk_locked(last);
+       char *mailbox_id;
 
-       if (!next)
-               return NULL;
-
-       snprintf(channel_name, AST_CHANNEL_NAME, "%s/pseudo", dahdi_chan_name);
-       if (!strncmp(next->name, channel_name, 10)) {
-               ast_channel_unlock(next);
-               goto redo;
-       } else if (next == chan) {
-               last = next;
-               ast_channel_unlock(next);
-               goto redo;
-       }
-
-       return setup_chanspy_ds(next, chanspy_ds);
+       mailbox_id = ast_alloca(strlen(mailbox) + strlen(context) + 2);
+       sprintf(mailbox_id, "%s@%s", mailbox, context); /* Safe */
+       return ast_app_sayname(chan, mailbox_id);
 }
 
 static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
-       int volfactor, const int fd, const char *mygroup, const char *myenforced,
-       const char *spec, const char *exten, const char *context, const char *mailbox,
-       const char *name_context)
+       int volfactor, const int fd, struct spy_dtmf_options *user_options,
+       const char *mygroup, const char *myenforced, const char *spec, const char *exten,
+       const char *context, const char *mailbox, const char *name_context)
 {
        char nameprefix[AST_NAME_STRLEN];
-       char peer_name[AST_NAME_STRLEN + 5];
        char exitcontext[AST_MAX_CONTEXT] = "";
        signed char zero_volume = 0;
        int waitms;
        int res;
-       char *ptr;
-       int num;
        int num_spyed_upon = 1;
-       struct chanspy_ds chanspy_ds;
+       struct ast_channel_iterator *iter = NULL;
 
        if (ast_test_flag(flags, OPTION_EXIT)) {
                const char *c;
                ast_channel_lock(chan);
                if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) {
                        ast_copy_string(exitcontext, c, sizeof(exitcontext));
-               } else if (!ast_strlen_zero(chan->macrocontext)) {
-                       ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
+               } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))) {
+                       ast_copy_string(exitcontext, ast_channel_macrocontext(chan), sizeof(exitcontext));
                } else {
-                       ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
+                       ast_copy_string(exitcontext, ast_channel_context(chan), sizeof(exitcontext));
                }
                ast_channel_unlock(chan);
        }
 
-       ast_mutex_init(&chanspy_ds.lock);
-
-       snprintf(chanspy_ds.unique_id, sizeof(chanspy_ds.unique_id), "%d", ast_atomic_fetchadd_int(&next_unique_id_to_use, +1));
-
-       if (chan->_state != AST_STATE_UP)
+       if (ast_channel_state(chan) != AST_STATE_UP)
                ast_answer(chan);
 
-       ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */
+       ast_channel_set_flag(chan, AST_FLAG_SPYING);
 
        waitms = 100;
 
        for (;;) {
-               struct chanspy_ds *peer_chanspy_ds = NULL, *next_chanspy_ds = NULL;
-               struct ast_channel *prev = NULL, *peer = NULL;
+               struct ast_autochan *autochan = NULL, *next_autochan = NULL;
+               struct ast_channel *prev = NULL;
 
                if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) {
-                       res = ast_streamfile(chan, "beep", chan->language);
+                       res = ast_streamfile(chan, "beep", ast_channel_language(chan));
                        if (!res)
                                res = ast_waitstream(chan, "");
                        else if (res < 0) {
-                               ast_clear_flag(chan, AST_FLAG_SPYING);
+                               ast_channel_clear_flag(chan, AST_FLAG_SPYING);
                                break;
                        }
                        if (!ast_strlen_zero(exitcontext)) {
@@ -684,82 +944,115 @@ static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
                        }
                }
 
+               /* Set up the iterator we'll be using during this call */
+               if (!ast_strlen_zero(spec)) {
+                       if (ast_test_flag(flags, OPTION_UNIQUEID)) {
+                               struct ast_channel *unique_chan;
+
+                               unique_chan = ast_channel_get_by_name(spec);
+                               if (!unique_chan) {
+                                       res = -1;
+                                       goto exit;
+                               }
+                               iter = ast_channel_iterator_by_name_new(ast_channel_name(unique_chan), 0);
+                               ast_channel_unref(unique_chan);
+                       } else {
+                               iter = ast_channel_iterator_by_name_new(spec, strlen(spec));
+                       }
+               } else if (!ast_strlen_zero(exten)) {
+                       iter = ast_channel_iterator_by_exten_new(exten, context);
+               } else {
+                       iter = ast_channel_iterator_all_new();
+               }
+
+               if (!iter) {
+                       res = -1;
+                       goto exit;
+               }
+
                res = ast_waitfordigit(chan, waitms);
                if (res < 0) {
-                       ast_clear_flag(chan, AST_FLAG_SPYING);
+                       iter = ast_channel_iterator_destroy(iter);
+                       ast_channel_clear_flag(chan, AST_FLAG_SPYING);
                        break;
                }
                if (!ast_strlen_zero(exitcontext)) {
                        char tmp[2];
                        tmp[0] = res;
                        tmp[1] = '\0';
-                       if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
+                       if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
+                               iter = ast_channel_iterator_destroy(iter);
                                goto exit;
-                       else
+                       } else {
                                ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
+                       }
                }
 
                /* reset for the next loop around, unless overridden later */
                waitms = 100;
                num_spyed_upon = 0;
 
-               for (peer_chanspy_ds = next_channel(chan, prev, spec, exten, context, &chanspy_ds);
-                    peer_chanspy_ds;
-                        chanspy_ds_free(peer_chanspy_ds), prev = peer,
-                    peer_chanspy_ds = next_chanspy_ds ? next_chanspy_ds : 
-                               next_channel(chan, prev, spec, exten, context, &chanspy_ds), next_chanspy_ds = NULL) {
-                       const char *group;
+               for (autochan = next_channel(iter, chan);
+                       autochan;
+                       prev = autochan->chan,
+                               ast_autochan_destroy(autochan),
+                               autochan = next_autochan ?: next_channel(iter, chan),
+                               next_autochan = NULL) {
                        int igrp = !mygroup;
-                       char *groups[NUM_SPYGROUPS];
-                       char *mygroups[NUM_SPYGROUPS];
-                       int num_groups = 0;
-                       char *dup_group;
-                       int num_mygroups = 0;
-                       char *dup_mygroup;
-                       int x;
-                       int y;
-                       char *s;
-                       char *buffer;
-                       char *end;
-                       char *ext;
-                       char *form_enforced;
                        int ienf = !myenforced;
 
-                       peer = peer_chanspy_ds->chan;
-
-                       ast_mutex_unlock(&peer_chanspy_ds->lock);
-
-                       if (peer == prev) {
-                               ast_channel_unlock(peer);
-                               chanspy_ds_free(peer_chanspy_ds);
+                       if (autochan->chan == prev) {
+                               ast_autochan_destroy(autochan);
                                break;
                        }
 
                        if (ast_check_hangup(chan)) {
-                               ast_channel_unlock(peer);
-                               chanspy_ds_free(peer_chanspy_ds);
+                               ast_autochan_destroy(autochan);
                                break;
                        }
 
-                       if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer)) {
-                               ast_channel_unlock(peer);
+                       ast_autochan_channel_lock(autochan);
+                       if (ast_test_flag(flags, OPTION_BRIDGED)
+                               && !ast_channel_is_bridged(autochan->chan)) {
+                               ast_autochan_channel_unlock(autochan);
                                continue;
                        }
 
-                       if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING)) {
-                               ast_channel_unlock(peer);
+                       if (ast_check_hangup(autochan->chan)
+                               || ast_test_flag(ast_channel_flags(autochan->chan), AST_FLAG_SPYING)) {
+                               ast_autochan_channel_unlock(autochan);
                                continue;
                        }
+                       ast_autochan_channel_unlock(autochan);
 
                        if (mygroup) {
-                               dup_mygroup = ast_strdupa(mygroup);
+                               int num_groups = 0;
+                               int num_mygroups = 0;
+                               char dup_group[512];
+                               char dup_mygroup[512];
+                               char *groups[NUM_SPYGROUPS];
+                               char *mygroups[NUM_SPYGROUPS];
+                               const char *group = NULL;
+                               int x;
+                               int y;
+                               ast_copy_string(dup_mygroup, mygroup, sizeof(dup_mygroup));
                                num_mygroups = ast_app_separate_args(dup_mygroup, ':', mygroups,
-                                       sizeof(mygroups) / sizeof(mygroups[0]));
+                                       ARRAY_LEN(mygroups));
+
+                               /* Before dahdi scan was part of chanspy, it would use the "GROUP" variable 
+                                * rather than "SPYGROUP", this check is done to preserve expected behavior */
+                               ast_autochan_channel_lock(autochan);
+                               if (ast_test_flag(flags, OPTION_DAHDI_SCAN)) {
+                                       group = pbx_builtin_getvar_helper(autochan->chan, "GROUP");
+                               } else {
+                                       group = pbx_builtin_getvar_helper(autochan->chan, "SPYGROUP");
+                               }
+                               ast_autochan_channel_unlock(autochan);
 
-                               if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) {
-                                       dup_group = ast_strdupa(group);
+                               if (!ast_strlen_zero(group)) {
+                                       ast_copy_string(dup_group, group, sizeof(dup_group));
                                        num_groups = ast_app_separate_args(dup_group, ':', groups,
-                                               sizeof(groups) / sizeof(groups[0]));
+                                               ARRAY_LEN(groups));
                                }
 
                                for (y = 0; y < num_mygroups; y++) {
@@ -773,139 +1066,152 @@ static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
                        }
 
                        if (!igrp) {
-                               ast_channel_unlock(peer);
                                continue;
                        }
-
                        if (myenforced) {
+                               char ext[AST_CHANNEL_NAME + 3];
+                               char buffer[512];
+                               char *end;
 
-                               /* We don't need to allocate more space than just the
-                               length of (peer->name) for ext as we will cut the
-                               channel name's ending before copying into ext */
-
-                               ext = alloca(strlen(peer->name));
-
-                               form_enforced = alloca(strlen(myenforced) + 3);
+                               snprintf(buffer, sizeof(buffer) - 1, ":%s:", myenforced);
 
-                               strcpy(form_enforced, ":");
-                               strcat(form_enforced, myenforced);
-                               strcat(form_enforced, ":");
-
-                               buffer = ast_strdupa(peer->name);
-                               
-                               if ((end = strchr(buffer, '-'))) {
+                               ast_autochan_channel_lock(autochan);
+                               ast_copy_string(ext + 1, ast_channel_name(autochan->chan), sizeof(ext) - 1);
+                               ast_autochan_channel_unlock(autochan);
+                               if ((end = strchr(ext, '-'))) {
                                        *end++ = ':';
                                        *end = '\0';
                                }
 
-                               strcpy(ext, ":");
-                               strcat(ext, buffer);
+                               ext[0] = ':';
 
-                               if (strcasestr(form_enforced, ext))
+                               if (strcasestr(buffer, ext)) {
                                        ienf = 1;
+                               }
                        }
 
-                       if (!ienf)
+                       if (!ienf) {
                                continue;
-
-                       strcpy(peer_name, "spy-");
-                       strncat(peer_name, peer->name, AST_NAME_STRLEN - 4 - 1);
-                       ptr = strchr(peer_name, '/');
-                       *ptr++ = '\0';
-                       ptr = strsep(&ptr, "-");
-
-                       for (s = peer_name; s < ptr; s++)
-                               *s = tolower(*s);
-                       /* We have to unlock the peer channel here to avoid a deadlock.
-                        * So, when we need to dereference it again, we have to lock the 
-                        * datastore and get the pointer from there to see if the channel 
-                        * is still valid. */
-                       ast_channel_unlock(peer);
+                       }
 
                        if (!ast_test_flag(flags, OPTION_QUIET)) {
+                               char peer_name[AST_NAME_STRLEN + 5];
+                               char *ptr, *s;
+
+                               strcpy(peer_name, "spy-");
+                               ast_autochan_channel_lock(autochan);
+                               strncat(peer_name, ast_channel_name(autochan->chan), AST_NAME_STRLEN - 4 - 1);
+                               ast_autochan_channel_unlock(autochan);
+                               if ((ptr = strchr(peer_name, '/'))) {
+                                       *ptr++ = '\0';
+                                       for (s = peer_name; s < ptr; s++) {
+                                               *s = tolower(*s);
+                                       }
+                                       if ((s = strchr(ptr, '-'))) {
+                                               *s = '\0';
+                                       }
+                               }
+
                                if (ast_test_flag(flags, OPTION_NAME)) {
                                        const char *local_context = S_OR(name_context, "default");
                                        const char *local_mailbox = S_OR(mailbox, ptr);
-                                       res = ast_app_sayname(chan, local_mailbox, local_context);
+
+                                       if (local_mailbox) {
+                                               res = spy_sayname(chan, local_mailbox, local_context);
+                                       } else {
+                                               res = -1;
+                                       }
                                }
                                if (!ast_test_flag(flags, OPTION_NAME) || res < 0) {
+                                       int num;
                                        if (!ast_test_flag(flags, OPTION_NOTECH)) {
-                                               if (ast_fileexists(peer_name, NULL, NULL) != -1) {
-                                                       res = ast_streamfile(chan, peer_name, chan->language);
+                                               if (ast_fileexists(peer_name, NULL, NULL) > 0) {
+                                                       res = ast_streamfile(chan, peer_name, ast_channel_language(chan));
                                                        if (!res) {
                                                                res = ast_waitstream(chan, "");
                                                        }
                                                        if (res) {
-                                                               chanspy_ds_free(peer_chanspy_ds);
+                                                               ast_autochan_destroy(autochan);
                                                                break;
                                                        }
                                                } else {
-                                                       res = ast_say_character_str(chan, peer_name, "", chan->language);
+                                                       res = ast_say_character_str(chan, peer_name, "", ast_channel_language(chan), AST_SAY_CASE_NONE);
                                                }
                                        }
-                                       if ((num = atoi(ptr)))
-                                               ast_say_digits(chan, atoi(ptr), "", chan->language);
+                                       if (ptr && (num = atoi(ptr))) {
+                                               ast_say_digits(chan, num, "", ast_channel_language(chan));
+                                       }
                                }
                        }
 
-                       res = channel_spy(chan, peer_chanspy_ds, &volfactor, fd, flags, exitcontext);
-                       num_spyed_upon++;       
+                       res = channel_spy(chan, autochan, &volfactor, fd, user_options, flags, exitcontext);
+                       num_spyed_upon++;
 
                        if (res == -1) {
-                               chanspy_ds_free(peer_chanspy_ds);
+                               ast_autochan_destroy(autochan);
+                               iter = ast_channel_iterator_destroy(iter);
                                goto exit;
                        } else if (res == -2) {
                                res = 0;
-                               chanspy_ds_free(peer_chanspy_ds);
+                               ast_autochan_destroy(autochan);
+                               iter = ast_channel_iterator_destroy(iter);
                                goto exit;
-                       } else if (res > 1 && spec) {
+                       } else if (res > 1 && spec && !ast_test_flag(flags, OPTION_UNIQUEID)) {
                                struct ast_channel *next;
 
                                snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
 
-                               if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) {
-                                       peer_chanspy_ds = chanspy_ds_free(peer_chanspy_ds);
-                                       next_chanspy_ds = setup_chanspy_ds(next, &chanspy_ds);
+                               if ((next = ast_channel_get_by_name_prefix(nameprefix, strlen(nameprefix)))) {
+                                       next_autochan = ast_autochan_setup(next);
+                                       next = ast_channel_unref(next);
                                } else {
                                        /* stay on this channel, if it is still valid */
-
-                                       ast_mutex_lock(&peer_chanspy_ds->lock);
-                                       if (peer_chanspy_ds->chan) {
-                                               ast_channel_lock(peer_chanspy_ds->chan);
-                                               next_chanspy_ds = peer_chanspy_ds;
-                                               peer_chanspy_ds = NULL;
+                                       ast_autochan_channel_lock(autochan);
+                                       if (!ast_check_hangup(autochan->chan)) {
+                                               next_autochan = ast_autochan_setup(autochan->chan);
                                        } else {
                                                /* the channel is gone */
-                                               ast_mutex_unlock(&peer_chanspy_ds->lock);
-                                               next_chanspy_ds = NULL;
+                                               next_autochan = NULL;
                                        }
+                                       ast_autochan_channel_unlock(autochan);
                                }
-
-                               peer = NULL;
+                       } else if (res == 0 && ast_test_flag(flags, OPTION_EXITONHANGUP)) {
+                               ast_autochan_destroy(autochan);
+                               iter = ast_channel_iterator_destroy(iter);
+                               goto exit;
                        }
                }
+
+               iter = ast_channel_iterator_destroy(iter);
+
                if (res == -1 || ast_check_hangup(chan))
                        break;
+               if (ast_test_flag(flags, OPTION_STOP) && !next_autochan) {
+                       break;
+               }
        }
 exit:
 
-       ast_clear_flag(chan, AST_FLAG_SPYING);
+       ast_channel_clear_flag(chan, AST_FLAG_SPYING);
 
        ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
 
-       ast_mutex_destroy(&chanspy_ds.lock);
-
        return res;
 }
 
-static int chanspy_exec(struct ast_channel *chan, void *data)
+static int chanspy_exec(struct ast_channel *chan, const char *data)
 {
        char *myenforced = NULL;
        char *mygroup = NULL;
        char *recbase = NULL;
        int fd = 0;
        struct ast_flags flags;
-       int oldwf = 0;
+       struct spy_dtmf_options user_options = {
+               .cycle = '*',
+               .volume = '#',
+               .exit = '\0',
+       };
+       RAII_VAR(struct ast_format *, oldwf, NULL, ao2_cleanup);
        int volfactor = 0;
        int res;
        char *mailbox = NULL;
@@ -915,14 +1221,15 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
                AST_APP_ARG(options);
        );
        char *opts[OPT_ARG_ARRAY_SIZE];
+       char *parse = ast_strdupa(data);
 
-       data = ast_strdupa(data);
-       AST_STANDARD_APP_ARGS(args, data);
+       AST_STANDARD_APP_ARGS(args, parse);
 
        if (args.spec && !strcmp(args.spec, "all"))
                args.spec = NULL;
 
        if (args.options) {
+               char tmp;
                ast_app_parse_options(spy_opts, &flags, opts, args.options);
                if (ast_test_flag(&flags, OPTION_GROUP))
                        mygroup = opts[OPT_ARG_GROUP];
@@ -931,10 +1238,28 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
                        !(recbase = opts[OPT_ARG_RECORD]))
                        recbase = "chanspy";
 
+               if (ast_test_flag(&flags, OPTION_DTMF_EXIT) && opts[OPT_ARG_EXIT]) {
+                       tmp = opts[OPT_ARG_EXIT][0];
+                       if (strchr("0123456789*#", tmp) && tmp != '\0') {
+                               user_options.exit = tmp;
+                       } else {
+                               ast_log(LOG_NOTICE, "Argument for option 'x' must be a valid DTMF digit.\n");
+                       }
+               }
+
+               if (ast_test_flag(&flags, OPTION_DTMF_CYCLE) && opts[OPT_ARG_CYCLE]) {
+                       tmp = opts[OPT_ARG_CYCLE][0];
+                       if (strchr("0123456789*#", tmp) && tmp != '\0') {
+                               user_options.cycle = tmp;
+                       } else {
+                               ast_log(LOG_NOTICE, "Argument for option 'c' must be a valid DTMF digit.\n");
+                       }
+               }
+
                if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
                        int vol;
 
-                       if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
+                       if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
                                ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
                        else
                                volfactor = vol;
@@ -945,7 +1270,7 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
 
                if (ast_test_flag(&flags, OPTION_ENFORCED))
                        myenforced = opts[OPT_ARG_ENFORCED];
-               
+
                if (ast_test_flag(&flags, OPTION_NAME)) {
                        if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
                                char *delimiter;
@@ -958,13 +1283,12 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
                                }
                        }
                }
-
-
-       } else
+       } else {
                ast_clear_flag(&flags, AST_FLAGS_ALL);
+       }
 
-       oldwf = chan->writeformat;
-       if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
+       oldwf = ao2_bump(ast_channel_writeformat(chan));
+       if (ast_set_write_format(chan, ast_format_slin) < 0) {
                ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
                return -1;
        }
@@ -979,7 +1303,7 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
                }
        }
 
-       res = common_exec(chan, &flags, volfactor, fd, mygroup, myenforced, args.spec, NULL, NULL, mailbox, name_context);
+       res = common_exec(chan, &flags, volfactor, fd, &user_options, mygroup, myenforced, args.spec, NULL, NULL, mailbox, name_context);
 
        if (fd)
                close(fd);
@@ -987,17 +1311,26 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
        if (oldwf && ast_set_write_format(chan, oldwf) < 0)
                ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
 
+       if (ast_test_flag(&flags, OPTION_EXITONHANGUP)) {
+               ast_verb(3, "Stopped spying due to the spied-on channel hanging up.\n");
+       }
+
        return res;
 }
 
-static int extenspy_exec(struct ast_channel *chan, void *data)
+static int extenspy_exec(struct ast_channel *chan, const char *data)
 {
        char *ptr, *exten = NULL;
        char *mygroup = NULL;
        char *recbase = NULL;
        int fd = 0;
        struct ast_flags flags;
-       int oldwf = 0;
+       struct spy_dtmf_options user_options = {
+               .cycle = '*',
+               .volume = '#',
+               .exit = '\0',
+       };
+       RAII_VAR(struct ast_format *, oldwf, NULL, ao2_cleanup);
        int volfactor = 0;
        int res;
        char *mailbox = NULL;
@@ -1006,21 +1339,21 @@ static int extenspy_exec(struct ast_channel *chan, void *data)
                AST_APP_ARG(context);
                AST_APP_ARG(options);
        );
+       char *parse = ast_strdupa(data);
 
-       data = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
 
-       AST_STANDARD_APP_ARGS(args, data);
        if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) {
                exten = args.context;
                *ptr++ = '\0';
                args.context = ptr;
        }
-
        if (ast_strlen_zero(args.context))
-               args.context = ast_strdupa(chan->context);
+               args.context = ast_strdupa(ast_channel_context(chan));
 
        if (args.options) {
                char *opts[OPT_ARG_ARRAY_SIZE];
+               char tmp;
 
                ast_app_parse_options(spy_opts, &flags, opts, args.options);
                if (ast_test_flag(&flags, OPTION_GROUP))
@@ -1030,10 +1363,28 @@ static int extenspy_exec(struct ast_channel *chan, void *data)
                        !(recbase = opts[OPT_ARG_RECORD]))
                        recbase = "chanspy";
 
+               if (ast_test_flag(&flags, OPTION_DTMF_EXIT) && opts[OPT_ARG_EXIT]) {
+                       tmp = opts[OPT_ARG_EXIT][0];
+                       if (strchr("0123456789*#", tmp) && tmp != '\0') {
+                               user_options.exit = tmp;
+                       } else {
+                               ast_log(LOG_NOTICE, "Argument for option 'x' must be a valid DTMF digit.\n");
+                       }
+               }
+
+               if (ast_test_flag(&flags, OPTION_DTMF_CYCLE) && opts[OPT_ARG_CYCLE]) {
+                       tmp = opts[OPT_ARG_CYCLE][0];
+                       if (strchr("0123456789*#", tmp) && tmp != '\0') {
+                               user_options.cycle = tmp;
+                       } else {
+                               ast_log(LOG_NOTICE, "Argument for option 'c' must be a valid DTMF digit.\n");
+                       }
+               }
+
                if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
                        int vol;
 
-                       if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
+                       if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
                                ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
                        else
                                volfactor = vol;
@@ -1042,7 +1393,6 @@ static int extenspy_exec(struct ast_channel *chan, void *data)
                if (ast_test_flag(&flags, OPTION_PRIVATE))
                        ast_set_flag(&flags, OPTION_WHISPER);
 
-               
                if (ast_test_flag(&flags, OPTION_NAME)) {
                        if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
                                char *delimiter;
@@ -1056,11 +1406,13 @@ static int extenspy_exec(struct ast_channel *chan, void *data)
                        }
                }
 
-       } else
+       } else {
+               /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
                ast_clear_flag(&flags, AST_FLAGS_ALL);
+       }
 
-       oldwf = chan->writeformat;
-       if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
+       oldwf = ao2_bump(ast_channel_writeformat(chan));
+       if (ast_set_write_format(chan, ast_format_slin) < 0) {
                ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
                return -1;
        }
@@ -1076,7 +1428,7 @@ static int extenspy_exec(struct ast_channel *chan, void *data)
        }
 
 
-       res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, NULL, exten, args.context, mailbox, name_context);
+       res = common_exec(chan, &flags, volfactor, fd, &user_options, mygroup, NULL, NULL, exten, args.context, mailbox, name_context);
 
        if (fd)
                close(fd);
@@ -1087,12 +1439,52 @@ static int extenspy_exec(struct ast_channel *chan, void *data)
        return res;
 }
 
+static int dahdiscan_exec(struct ast_channel *chan, const char *data)
+{
+       const char *spec = "DAHDI";
+       struct ast_flags flags = {0};
+       struct spy_dtmf_options user_options = {
+               .cycle = '#',
+               .volume = '\0',
+               .exit = '*',
+       };
+       struct ast_format *oldwf;
+       int res;
+       char *mygroup = NULL;
+
+       /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
+       ast_clear_flag(&flags, AST_FLAGS_ALL);
+
+       if (!ast_strlen_zero(data)) {
+               mygroup = ast_strdupa(data);
+       }
+       ast_set_flag(&flags, OPTION_DTMF_EXIT);
+       ast_set_flag(&flags, OPTION_DTMF_CYCLE);
+       ast_set_flag(&flags, OPTION_DAHDI_SCAN);
+
+       oldwf = ao2_bump(ast_channel_writeformat(chan));
+       if (ast_set_write_format(chan, ast_format_slin) < 0) {
+               ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
+               ao2_cleanup(oldwf);
+               return -1;
+       }
+
+       res = common_exec(chan, &flags, 0, 0, &user_options, mygroup, NULL, spec, NULL, NULL, NULL, NULL);
+
+       if (oldwf && ast_set_write_format(chan, oldwf) < 0)
+               ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
+       ao2_cleanup(oldwf);
+
+       return res;
+}
+
 static int unload_module(void)
 {
        int res = 0;
 
        res |= ast_unregister_application(app_chan);
        res |= ast_unregister_application(app_ext);
+       res |= ast_unregister_application(app_dahdiscan);
 
        return res;
 }
@@ -1101,8 +1493,9 @@ static int load_module(void)
 {
        int res = 0;
 
-       res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan);
-       res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext);
+       res |= ast_register_application_xml(app_chan, chanspy_exec);
+       res |= ast_register_application_xml(app_ext, extenspy_exec);
+       res |= ast_register_application_xml(app_dahdiscan, dahdiscan_exec);
 
        return res;
 }