Add BUGBUG note for ASTERISK-22009
authorRichard Mudgett <rmudgett@digium.com>
Wed, 3 Jul 2013 23:55:53 +0000 (23:55 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Wed, 3 Jul 2013 23:55:53 +0000 (23:55 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393631 65c4cc65-6c06-0410-ace0-fbb531ad65f3

12 files changed:
CHANGES
UPGRADE.txt
apps/app_agent_pool.c [new file with mode: 0644]
channels/chan_agent.c [deleted file]
configs/agents.conf.sample
configs/queues.conf.sample
include/asterisk/bridging.h
include/asterisk/config_options.h
include/asterisk/stasis_channels.h
main/bridging.c
main/config_options.c
main/stasis_channels.c

diff --git a/CHANGES b/CHANGES
index e6da222..1edbe92 100644 (file)
--- a/CHANGES
+++ b/CHANGES
 Applications
 ------------------
 
+AgentLogin
+------------------
+ * The application no longer does agent authentication.  The dialplan needs to
+   perform this function before running AgentLogin.  If the agent is already
+   logged in, dialplan will continue with the AGENT_STATUS channel variable
+   set to ALREADY_LOGGED_IN.
+
 AgentMonitorOutgoing
 ------------------
  * The 'c' option has been removed. It is not possible to modify the name of a
    channel involved in a CDR.
+ * Application removed.  It was a holdover from when AgentCallbackLogin was
+   removed.
 
 ForkCDR
 ------------------
@@ -244,8 +253,8 @@ AMI (Asterisk Manager Interface)
    of "CallerID" and "ConnectedID" to avoid confusion with similarly named
    parameters in the channel snapshot.
 
- * The "Agentlogin" and "Agentlogoff" events have been renamed "AgentLogin" and
-   "AgentLogoff" respectively.
+ * The AMI events "Agentlogin" and "Agentlogoff" have been renamed
+   "AgentLogin" and "AgentLogoff" respectively.
 
  * The "Channel" key used in the "AlarmClear", "Alarm", and "DNDState" has been
    renamed "DAHDIChannel" since it does not convey an Asterisk channel name.
@@ -423,6 +432,21 @@ chan_agent
    and pretending otherwise helps no one.
  * The AGENTUPDATECDR channel variable has also been removed, for the same
    reason as the updatecdr option.
+ * The driver is no longer a Data retrieval API data provider for the
+   AMI DataGet action.
+ * The endcall and enddtmf configuration options are removed.  Use the
+   dialplan function CHANNEL(dtmf-features) to set DTMF features on the agent
+   channel before calling AgentLogin.
+ * chan_agent is removed and replaced with AgentLogin and AgentRequest dialplan
+   applications.  Agents are connected with callers using the new AgentRequest
+   dialplan application.  The Agents:<agent-id> device state is available to
+   monitor the status of an agent.  See agents.conf.sample for valid
+   configuration options.
+
+chan_bridge
+------------------
+ * chan_bridge is removed and its functionality is incorporated into ConfBridge
+   itself.
 
 chan_local
 ------------------
index 7a5261b..fb7b65b 100644 (file)
@@ -24,6 +24,8 @@
 AgentMonitorOutgoing
  - The 'c' option has been removed. It is not possible to modify the name of a
    channel involved in a CDR.
+ - Application removed.  It was a holdover from when AgentCallbackLogin was
+   removed.
 
 NoCDR:
  - This application is deprecated. Please use the CDR_PROP function instead.
@@ -124,6 +126,15 @@ chan_agent:
    and pretending otherwise helps no one.
  - The AGENTUPDATECDR channel variable has also been removed, for the same
    reason as the updatecdr option.
+ - chan_agent is removed and replaced with AgentLogin and AgentRequest dialplan
+   applications.  Agents are connected with callers using the new AgentRequest
+   dialplan application.  The Agents:<agent-id> device state is available to
+   monitor the status of an agent.  See agents.conf.sample for valid
+   configuration options.
+
+chan_bridge
+ - chan_bridge is removed and its functionality is incorporated into ConfBridge
+   itself.
 
 chan_dahdi:
  - Analog port dialing and deferred DTMF dialing for PRI now distinguishes
diff --git a/apps/app_agent_pool.c b/apps/app_agent_pool.c
new file mode 100644 (file)
index 0000000..b3f67d8
--- /dev/null
@@ -0,0 +1,2486 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Call center agent pool.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ * \arg \ref Config_agent
+ */
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/config_options.h"
+#include "asterisk/features_config.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/stasis_channels.h"
+
+/*** DOCUMENTATION
+       <application name="AgentLogin" language="en_US">
+               <synopsis>
+                       Login an agent.
+               </synopsis>
+               <syntax argsep=",">
+                       <parameter name="AgentId" required="true" />
+                       <parameter name="options">
+                               <optionlist>
+                                       <option name="s">
+                                               <para>silent login - do not announce the login ok segment after
+                                               agent logged on.</para>
+                                       </option>
+                               </optionlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Login an agent to the system.  Any agent authentication is assumed to
+                       already be done by dialplan.  While logged in, the agent can receive calls
+                       and will hear a configurable <literal>beep</literal> sound when a new call
+                       comes in for the agent.  Login failures will continue in the dialplan
+                       with <variable>AGENT_STATUS</variable> set.</para>
+                       <para>Before logging in, you can setup on the real agent channel the
+                       CHANNEL(dtmf-features) an agent will have when talking to a caller
+                       and you can setup on the channel running this application the
+                       CONNECTEDLINE() information the agent will see while waiting for a
+                       caller.</para>
+                       <para><variable>AGENT_STATUS</variable> enumeration values:</para>
+                       <enumlist>
+                               <enum name = "INVALID"><para>The specified agent is invalid.</para></enum>
+                               <enum name = "ALREADY_LOGGED_IN"><para>The agent is already logged in.</para></enum>
+                       </enumlist>
+                       <note><para>The Agents:<replaceable>AgentId</replaceable> device state is
+                       available to monitor the status of the agent.</para></note>
+               </description>
+               <see-also>
+                       <ref type="application">Authenticate</ref>
+                       <ref type="application">Queue</ref>
+                       <ref type="application">AddQueueMember</ref>
+                       <ref type="application">RemoveQueueMember</ref>
+                       <ref type="application">PauseQueueMember</ref>
+                       <ref type="application">UnpauseQueueMember</ref>
+                       <ref type="function">AGENT</ref>
+                       <ref type="function">CHANNEL(dtmf-features)</ref>
+                       <ref type="function">CONNECTEDLINE()</ref>
+                       <ref type="filename">agents.conf</ref>
+                       <ref type="filename">queues.conf</ref>
+               </see-also>
+       </application>
+       <application name="AgentRequest" language="en_US">
+               <synopsis>
+                       Request an agent to connect with the channel.
+               </synopsis>
+               <syntax argsep=",">
+                       <parameter name="AgentId" required="true" />
+               </syntax>
+               <description>
+                       <para>Request an agent to connect with the channel.  Failure to find and
+                       alert an agent will continue in the dialplan with <variable>AGENT_STATUS</variable> set.</para>
+                       <para><variable>AGENT_STATUS</variable> enumeration values:</para>
+                       <enumlist>
+                               <enum name = "INVALID"><para>The specified agent is invalid.</para></enum>
+                               <enum name = "NOT_LOGGED_IN"><para>The agent is not available.</para></enum>
+                               <enum name = "BUSY"><para>The agent is on another call.</para></enum>
+                               <enum name = "ERROR"><para>Alerting the agent failed.</para></enum>
+                       </enumlist>
+               </description>
+               <see-also>
+                       <ref type="application">AgentLogin</ref>
+               </see-also>
+       </application>
+       <function name="AGENT" language="en_US">
+               <synopsis>
+                       Gets information about an Agent
+               </synopsis>
+               <syntax argsep=":">
+                       <parameter name="AgentId" required="true" />
+                       <parameter name="item">
+                               <para>The valid items to retrieve are:</para>
+                               <enumlist>
+                                       <enum name="status">
+                                               <para>(default) The status of the agent (LOGGEDIN | LOGGEDOUT)</para>
+                                       </enum>
+                                       <enum name="password">
+                                               <para>Deprecated.  The dialplan handles any agent authentication.</para>
+                                       </enum>
+                                       <enum name="name">
+                                               <para>The name of the agent</para>
+                                       </enum>
+                                       <enum name="mohclass">
+                                               <para>MusicOnHold class</para>
+                                       </enum>
+                                       <enum name="channel">
+                                               <para>The name of the active channel for the Agent (AgentLogin)</para>
+                                       </enum>
+                                       <enum name="fullchannel">
+                                               <para>The untruncated name of the active channel for the Agent (AgentLogin)</para>
+                                       </enum>
+                               </enumlist>
+                       </parameter>
+               </syntax>
+               <description></description>
+       </function>
+       <manager name="Agents" language="en_US">
+               <synopsis>
+                       Lists agents and their status.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+               </syntax>
+               <description>
+                       <para>Will list info about all defined agents.</para>
+               </description>
+       </manager>
+       <manager name="AgentLogoff" language="en_US">
+               <synopsis>
+                       Sets an agent as no longer logged in.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="Agent" required="true">
+                               <para>Agent ID of the agent to log off.</para>
+                       </parameter>
+                       <parameter name="Soft">
+                               <para>Set to <literal>true</literal> to not hangup existing calls.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Sets an agent as no longer logged in.</para>
+               </description>
+       </manager>
+       <configInfo name="app_agent_pool" language="en_US">
+               <synopsis>Agent pool applications</synopsis>
+               <description>
+                       <note><para>Option changes take effect on agent login or after an agent
+                       disconnects from a call.</para></note>
+               </description>
+               <configFile name="agents.conf">
+                       <configObject name="global">
+                               <synopsis>Unused, but reserved.</synopsis>
+                       </configObject>
+                       <configObject name="agent-id">
+                               <synopsis>Configure an agent for the pool.</synopsis>
+                               <description>
+                                       <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                               </description>
+                               <configOption name="ackcall">
+                                       <synopsis>Enable to require the agent to acknowledge a call.</synopsis>
+                                       <description>
+                                               <para>Enable to require the agent to give a DTMF acknowledgement
+                                               when the agent receives a call.</para>
+                                               <note><para>The option is overridden by <variable>AGENTACKCALL</variable> on agent login.</para></note>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="acceptdtmf">
+                                       <synopsis>DTMF key sequence the agent uses to acknowledge a call.</synopsis>
+                                       <description>
+                                               <note><para>The option is overridden by <variable>AGENTACCEPTDTMF</variable> on agent login.</para></note>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="autologoff">
+                                       <synopsis>Time the agent has to acknowledge a call before being logged off.</synopsis>
+                                       <description>
+                                               <para>Set how many seconds a call for the agent has to wait for the
+                                               agent to acknowledge the call before the agent is automatically
+                                               logged off.  If set to zero then the call will wait forever for
+                                               the agent to acknowledge.</para>
+                                               <note><para>The option is overridden by <variable>AGENTAUTOLOGOFF</variable> on agent login.</para></note>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="wrapuptime">
+                                       <synopsis>Minimum time the agent has between calls.</synopsis>
+                                       <description>
+                                               <para>Set the minimum amount of time in milliseconds after
+                                               disconnecting a call before the agent can receive a new call.</para>
+                                               <note><para>The option is overridden by <variable>AGENTWRAPUPTIME</variable> on agent login.</para></note>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="musiconhold">
+                                       <synopsis>Music on hold class the agent listens to between calls.</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="recordagentcalls">
+                                       <synopsis>Enable to automatically record calls the agent takes.</synopsis>
+                                       <description>
+                                               <para>Enable recording calls the agent takes automatically by
+                                               invoking the automixmon DTMF feature when the agent connects
+                                               to a caller.  See <filename>features.conf.sample</filename> for information about
+                                               the automixmon feature.</para>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="custom_beep">
+                                       <synopsis>Sound file played to alert the agent when a call is present.</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="fullname">
+                                       <synopsis>A friendly name for the agent used in log messages.</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+                                       </description>
+                               </configOption>
+                       </configObject>
+               </configFile>
+       </configInfo>
+ ***/
+
+/* ------------------------------------------------------------------- */
+
+#define AST_MAX_BUF    256
+
+/*! Maximum wait time (in ms) for the custom_beep file to play announcing the caller. */
+#define CALLER_SAFETY_TIMEOUT_TIME     (2 * 60 * 1000)
+
+/*! Number of seconds to wait for local channel optimizations to complete. */
+#define LOGIN_WAIT_TIMEOUT_TIME                5
+
+static const char app_agent_login[] = "AgentLogin";
+static const char app_agent_request[] = "AgentRequest";
+
+/*! Agent config parameters. */
+struct agent_cfg {
+       AST_DECLARE_STRING_FIELDS(
+               /*! Identification of the agent.  (agents config container key) */
+               AST_STRING_FIELD(username);
+               /*! Name of agent for logging and querying purposes */
+               AST_STRING_FIELD(full_name);
+
+               /*!
+                * \brief DTMF string for an agent to accept a call.
+                *
+                * \note The channel variable AGENTACCEPTDTMF overrides on login.
+                */
+               AST_STRING_FIELD(dtmf_accept);
+               /*! Beep sound file to use.  Alert the agent a call is waiting. */
+               AST_STRING_FIELD(beep_sound);
+               /*! MOH class to use while agent waiting for call. */
+               AST_STRING_FIELD(moh);
+       );
+       /*!
+        * \brief Number of seconds for agent to ack a call before being logged off.
+        *
+        * \note The channel variable AGENTAUTOLOGOFF overrides on login.
+        * \note If zero then timer is disabled.
+        */
+       unsigned int auto_logoff;
+       /*!
+        * \brief Time after a call in ms before the agent can get a new call.
+        *
+        * \note The channel variable AGENTWRAPUPTIME overrides on login.
+        */
+       unsigned int wrapup_time;
+       /*!
+        * \brief TRUE if agent needs to ack a call to accept it.
+        *
+        * \note The channel variable AGENTACKCALL overrides on login.
+        */
+       int ack_call;
+       /*! TRUE if agent calls are automatically recorded. */
+       int record_agent_calls;
+};
+
+/*!
+ * \internal
+ * \brief Agent config ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int agent_cfg_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+       const struct agent_cfg *cfg_left = obj_left;
+       const struct agent_cfg *cfg_right = obj_right;
+       const char *right_key = obj_right;
+       int cmp;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               right_key = cfg_right->username;
+               /* Fall through */
+       case OBJ_KEY:
+               cmp = strcmp(cfg_left->username, right_key);
+               break;
+       case OBJ_PARTIAL_KEY:
+               cmp = strncmp(cfg_left->username, right_key, strlen(right_key));
+               break;
+       }
+       return cmp;
+}
+
+static void agent_cfg_destructor(void *vdoomed)
+{
+       struct agent_cfg *doomed = vdoomed;
+
+       ast_string_field_free_memory(doomed);
+}
+
+static void *agent_cfg_alloc(const char *name)
+{
+       struct agent_cfg *cfg;
+
+       cfg = ao2_alloc_options(sizeof(*cfg), agent_cfg_destructor,
+               AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (!cfg || ast_string_field_init(cfg, 64)) {
+               return NULL;
+       }
+       ast_string_field_set(cfg, username, name);
+       return cfg;
+}
+
+static void *agent_cfg_find(struct ao2_container *agents, const char *username)
+{
+       return ao2_find(agents, username, OBJ_KEY);
+}
+
+/*! Agents configuration */
+struct agents_cfg {
+       /*! Master configured agents container. */
+       struct ao2_container *agents;
+};
+
+static struct aco_type agent_type = {
+       .type = ACO_ITEM,
+       .name = "agent-id",
+       .category_match = ACO_BLACKLIST,
+       .category = "^(general|agents)$",
+       .item_alloc = agent_cfg_alloc,
+       .item_find = agent_cfg_find,
+       .item_offset = offsetof(struct agents_cfg, agents),
+};
+
+static struct aco_type *agent_types[] = ACO_TYPES(&agent_type);
+
+/* The general category is reserved, but unused */
+static struct aco_type general_type = {
+       .type = ACO_GLOBAL,
+       .name = "global",
+       .category_match = ACO_WHITELIST,
+       .category = "^general$",
+};
+
+static struct aco_file agents_conf = {
+       .filename = "agents.conf",
+       .types = ACO_TYPES(&general_type, &agent_type),
+};
+
+static AO2_GLOBAL_OBJ_STATIC(cfg_handle);
+
+static void agents_cfg_destructor(void *vdoomed)
+{
+       struct agents_cfg *doomed = vdoomed;
+
+       ao2_cleanup(doomed->agents);
+       doomed->agents = NULL;
+}
+
+/*!
+ * \internal
+ * \brief Create struct agents_cfg object.
+ * \since 12.0.0
+ *
+ * \note A lock is not needed for the object or any secondary
+ * created cfg objects.  These objects are immutable after the
+ * config is loaded and applied.
+ *
+ * \retval New struct agents_cfg object.
+ * \retval NULL on error.
+ */
+static void *agents_cfg_alloc(void)
+{
+       struct agents_cfg *cfg;
+
+       cfg = ao2_alloc_options(sizeof(*cfg), agents_cfg_destructor,
+               AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (!cfg) {
+               return NULL;
+       }
+       cfg->agents = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
+               AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, agent_cfg_sort_cmp, NULL);
+       if (!cfg->agents) {
+               ao2_ref(cfg, -1);
+               cfg = NULL;
+       }
+       return cfg;
+}
+
+static void agents_post_apply_config(void);
+
+CONFIG_INFO_STANDARD(cfg_info, cfg_handle, agents_cfg_alloc,
+       .files = ACO_FILES(&agents_conf),
+       .post_apply_config = agents_post_apply_config,
+);
+
+static void destroy_config(void)
+{
+       ao2_global_obj_release(cfg_handle);
+       aco_info_destroy(&cfg_info);
+}
+
+static int load_config(void)
+{
+       if (aco_info_init(&cfg_info)) {
+               return -1;
+       }
+
+       /* Agent options */
+       aco_option_register(&cfg_info, "ackcall", ACO_EXACT, agent_types, "no", OPT_BOOL_T, 1, FLDSET(struct agent_cfg, ack_call));
+       aco_option_register(&cfg_info, "acceptdtmf", ACO_EXACT, agent_types, "#", OPT_STRINGFIELD_T, 1, STRFLDSET(struct agent_cfg, dtmf_accept));
+       aco_option_register(&cfg_info, "autologoff", ACO_EXACT, agent_types, "0", OPT_UINT_T, 0, FLDSET(struct agent_cfg, auto_logoff));
+       aco_option_register(&cfg_info, "wrapuptime", ACO_EXACT, agent_types, "0", OPT_UINT_T, 0, FLDSET(struct agent_cfg, wrapup_time));
+       aco_option_register(&cfg_info, "musiconhold", ACO_EXACT, agent_types, "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, moh));
+       aco_option_register(&cfg_info, "recordagentcalls", ACO_EXACT, agent_types, "no", OPT_BOOL_T, 1, FLDSET(struct agent_cfg, record_agent_calls));
+       aco_option_register(&cfg_info, "custom_beep", ACO_EXACT, agent_types, "beep", OPT_STRINGFIELD_T, 1, STRFLDSET(struct agent_cfg, beep_sound));
+       aco_option_register(&cfg_info, "fullname", ACO_EXACT, agent_types, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, full_name));
+
+       if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
+               goto error;
+       }
+
+       return 0;
+
+error:
+       destroy_config();
+       return -1;
+}
+
+enum agent_state {
+       /*! The agent is defined but an agent is not present. */
+       AGENT_STATE_LOGGED_OUT,
+       /*! Forced initial login wait to allow any local channel optimizations to happen. */
+       AGENT_STATE_PROBATION_WAIT,
+       /*! The agent is ready for a call. */
+       AGENT_STATE_READY_FOR_CALL,
+       /*! The agent has a call waiting to connect. */
+       AGENT_STATE_CALL_PRESENT,
+       /*! The agent needs to ack the call. */
+       AGENT_STATE_CALL_WAIT_ACK,
+       /*! The agent is connected with a call. */
+       AGENT_STATE_ON_CALL,
+       /*! The agent is resting between calls. */
+       AGENT_STATE_CALL_WRAPUP,
+       /*! The agent is being kicked out. */
+       AGENT_STATE_LOGGING_OUT,
+};
+
+/*! Agent config option override flags. */
+enum agent_override_flags {
+       AGENT_FLAG_ACK_CALL = (1 << 0),
+       AGENT_FLAG_DTMF_ACCEPT = (1 << 1),
+       AGENT_FLAG_AUTO_LOGOFF = (1 << 2),
+       AGENT_FLAG_WRAPUP_TIME = (1 << 3),
+};
+
+/*! \brief Structure representing an agent. */
+struct agent_pvt {
+       AST_DECLARE_STRING_FIELDS(
+               /*! Identification of the agent.  (agents container key) */
+               AST_STRING_FIELD(username);
+               /*! Login override DTMF string for an agent to accept a call. */
+               AST_STRING_FIELD(override_dtmf_accept);
+       );
+       /*! Connected line information to send when reentering the holding bridge. */
+       struct ast_party_connected_line waiting_colp;
+       /*! Flags show if settings were overridden by channel vars. */
+       unsigned int flags;
+       /*! Login override number of seconds for agent to ack a call before being logged off. */
+       unsigned int override_auto_logoff;
+       /*! Login override time after a call in ms before the agent can get a new call. */
+       unsigned int override_wrapup_time;
+       /*! Login override if agent needs to ack a call to accept it. */
+       unsigned int override_ack_call:1;
+
+       /*! TRUE if the agent is requested to logoff when the current call ends. */
+       unsigned int deferred_logoff:1;
+
+       /*! Mark and sweep config update to determine if an agent is dead. */
+       unsigned int the_mark:1;
+       /*!
+        * \brief TRUE if the agent is no longer configured and is being destroyed.
+        *
+        * \note Agents cannot log in if they are dead.
+        */
+       unsigned int dead:1;
+
+       /*! Agent control state variable. */
+       enum agent_state state;
+       /*! Custom device state of agent. */
+       enum ast_device_state devstate;
+
+       /*! When agent first logged in */
+       time_t login_start;
+       /*! When agent login probation started. */
+       time_t probation_start;
+       /*! When call started */
+       time_t call_start;
+       /*! When ack timer started */
+       struct timeval ack_time;
+       /*! When last disconnected */
+       struct timeval last_disconnect;
+
+       /*! Caller is waiting in this bridge for agent to join. (Holds ref) */
+       struct ast_bridge *caller_bridge;
+       /*! Agent is logged in with this channel. (Holds ref) (NULL if not logged in.) */
+       struct ast_channel *logged;
+       /*! Active config values from config file. (Holds ref) */
+       struct agent_cfg *cfg;
+};
+
+/*! Container of defined agents. */
+static struct ao2_container *agents;
+
+/*!
+ * \brief Lock the agent.
+ *
+ * \param agent Agent to lock
+ *
+ * \return Nothing
+ */
+#define agent_lock(agent)      _agent_lock(agent, __FILE__, __PRETTY_FUNCTION__, __LINE__, #agent)
+static inline void _agent_lock(struct agent_pvt *agent, const char *file, const char *function, int line, const char *var)
+{
+       __ao2_lock(agent, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Unlock the agent.
+ *
+ * \param agent Agent to unlock
+ *
+ * \return Nothing
+ */
+#define agent_unlock(agent)    _agent_unlock(agent, __FILE__, __PRETTY_FUNCTION__, __LINE__, #agent)
+static inline void _agent_unlock(struct agent_pvt *agent, const char *file, const char *function, int line, const char *var)
+{
+       __ao2_unlock(agent, file, function, line, var);
+}
+
+/*!
+ * \internal
+ * \brief Obtain the agent logged in channel lock if it exists.
+ * \since 12.0.0
+ *
+ * \param agent Pointer to the LOCKED agent_pvt.
+ *
+ * \note Assumes the agent lock is already obtained.
+ *
+ * \return Nothing
+ */
+static struct ast_channel *agent_lock_logged(struct agent_pvt *agent)
+{
+       struct ast_channel *logged;
+
+       for (;;) {
+               if (!agent->logged) { /* No owner. Nothing to do. */
+                       return NULL;
+               }
+
+               /* If we don't ref the logged, it could be killed when we unlock the agent. */
+               logged = ast_channel_ref(agent->logged);
+
+               /* Locking logged requires us to lock channel, then agent. */
+               agent_unlock(agent);
+               ast_channel_lock(logged);
+               agent_lock(agent);
+
+               /* Check if logged changed during agent unlock period */
+               if (logged != agent->logged) {
+                       /* Channel changed. Unref and do another pass. */
+                       ast_channel_unlock(logged);
+                       ast_channel_unref(logged);
+               } else {
+                       /* Channel stayed the same. Return it. */
+                       return logged;
+               }
+       }
+}
+
+/*!
+ * \internal
+ * \brief Get the Agent:agent_id device state.
+ * \since 12.0.0
+ *
+ * \param agent_id Username of the agent.
+ *
+ * \details
+ * Search the agents container for the agent and return the
+ * current state.
+ *
+ * \return Device state of the agent.
+ */
+static enum ast_device_state agent_pvt_devstate_get(const char *agent_id)
+{
+       RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, agent_id, OBJ_KEY), ao2_cleanup);
+
+       if (agent) {
+               return agent->devstate;
+       }
+       return AST_DEVICE_INVALID;
+}
+
+/*!
+ * \internal
+ * \brief Request an agent device state be updated.
+ * \since 12.0.0
+ *
+ * \param agent_id Which agent needs the device state updated.
+ *
+ * \return Nothing
+ */
+static void agent_devstate_changed(const char *agent_id)
+{
+       ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "Agent:%s", agent_id);
+}
+
+static void agent_pvt_destructor(void *vdoomed)
+{
+       struct agent_pvt *doomed = vdoomed;
+
+       /* Make sure device state reflects agent destruction. */
+       if (!ast_strlen_zero(doomed->username)) {
+               ast_debug(1, "Agent %s: Destroyed.\n", doomed->username);
+               agent_devstate_changed(doomed->username);
+       }
+
+       ast_party_connected_line_free(&doomed->waiting_colp);
+       if (doomed->caller_bridge) {
+               ast_bridge_destroy(doomed->caller_bridge);
+               doomed->caller_bridge = NULL;
+       }
+       if (doomed->logged) {
+               doomed->logged = ast_channel_unref(doomed->logged);
+       }
+       ao2_cleanup(doomed->cfg);
+       doomed->cfg = NULL;
+       ast_string_field_free_memory(doomed);
+}
+
+static struct agent_pvt *agent_pvt_new(struct agent_cfg *cfg)
+{
+       struct agent_pvt *agent;
+
+       agent = ao2_alloc(sizeof(*agent), agent_pvt_destructor);
+       if (!agent) {
+               return NULL;
+       }
+       if (ast_string_field_init(agent, 32)) {
+               ao2_ref(agent, -1);
+               return NULL;
+       }
+       ast_string_field_set(agent, username, cfg->username);
+       ast_party_connected_line_init(&agent->waiting_colp);
+       ao2_ref(cfg, +1);
+       agent->cfg = cfg;
+       agent->devstate = AST_DEVICE_UNAVAILABLE;
+       return agent;
+}
+
+/*!
+ * \internal
+ * \brief Agents ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int agent_pvt_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+       const struct agent_pvt *agent_left = obj_left;
+       const struct agent_pvt *agent_right = obj_right;
+       const char *right_key = obj_right;
+       int cmp;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               right_key = agent_right->username;
+               /* Fall through */
+       case OBJ_KEY:
+               cmp = strcmp(agent_left->username, right_key);
+               break;
+       case OBJ_PARTIAL_KEY:
+               cmp = strncmp(agent_left->username, right_key, strlen(right_key));
+               break;
+       }
+       return cmp;
+}
+
+/*!
+ * \internal
+ * \brief ao2_find() callback function.
+ * \since 12.0.0
+ *
+ * Usage:
+ * found = ao2_find(agents, agent, OBJ_POINTER);
+ * found = ao2_find(agents, "agent-id", OBJ_KEY);
+ * found = ao2_find(agents, agent->logged, 0);
+ */
+static int agent_pvt_cmp(void *obj, void *arg, int flags)
+{
+       const struct agent_pvt *agent = obj;
+       int cmp;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       case OBJ_POINTER:
+       case OBJ_KEY:
+       case OBJ_PARTIAL_KEY:
+               cmp = CMP_MATCH;
+               break;
+       default:
+               if (agent->logged == arg) {
+                       cmp = CMP_MATCH;
+               } else {
+                       cmp = 0;
+               }
+               break;
+       }
+       return cmp;
+}
+
+static int agent_mark(void *obj, void *arg, int flags)
+{
+       struct agent_pvt *agent = obj;
+
+       agent_lock(agent);
+       agent->the_mark = 1;
+       agent_unlock(agent);
+       return 0;
+}
+
+static void agents_mark(void)
+{
+       ao2_callback(agents, 0, agent_mark, NULL);
+}
+
+static int agent_sweep(void *obj, void *arg, int flags)
+{
+       struct agent_pvt *agent = obj;
+       int cmp = 0;
+
+       agent_lock(agent);
+       if (agent->the_mark) {
+               agent->the_mark = 0;
+               agent->dead = 1;
+               /* Unlink dead agents immediately. */
+               cmp = CMP_MATCH;
+       }
+       agent_unlock(agent);
+       return cmp;
+}
+
+static void agents_sweep(void)
+{
+       struct ao2_iterator *iter;
+       struct agent_pvt *agent;
+       struct ast_channel *logged;
+
+       iter = ao2_callback(agents, OBJ_MULTIPLE | OBJ_UNLINK, agent_sweep, NULL);
+       if (!iter) {
+               return;
+       }
+       for (; (agent = ao2_iterator_next(iter)); ao2_ref(agent, -1)) {
+               agent_lock(agent);
+               if (agent->logged) {
+                       logged = ast_channel_ref(agent->logged);
+               } else {
+                       logged = NULL;
+               }
+               agent_unlock(agent);
+               if (!logged) {
+                       continue;
+               }
+               ast_log(LOG_NOTICE,
+                       "Forced logoff of agent %s(%s).  Agent no longer configured.\n",
+                       agent->username, ast_channel_name(logged));
+               ast_softhangup(logged, AST_SOFTHANGUP_EXPLICIT);
+               ast_channel_unref(logged);
+       }
+       ao2_iterator_destroy(iter);
+}
+
+static void agents_post_apply_config(void)
+{
+       struct ao2_iterator iter;
+       struct agent_cfg *cfg;
+       RAII_VAR(struct agents_cfg *, cfgs, ao2_global_obj_ref(cfg_handle), ao2_cleanup);
+
+       ast_assert(cfgs != NULL);
+
+       agents_mark();
+       iter = ao2_iterator_init(cfgs->agents, 0);
+       for (; (cfg = ao2_iterator_next(&iter)); ao2_ref(cfg, -1)) {
+               RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, cfg->username, OBJ_KEY), ao2_cleanup);
+
+               if (agent) {
+                       agent_lock(agent);
+                       agent->the_mark = 0;
+                       if (!agent->logged) {
+                               struct agent_cfg *cfg_old;
+
+                               /* Replace the config of agents not logged in. */
+                               cfg_old = agent->cfg;
+                               ao2_ref(cfg, +1);
+                               agent->cfg = cfg;
+                               ao2_cleanup(cfg_old);
+                       }
+                       agent_unlock(agent);
+                       continue;
+               }
+               agent = agent_pvt_new(cfg);
+               if (!agent) {
+                       continue;
+               }
+               ao2_link(agents, agent);
+               ast_debug(1, "Agent %s: Created.\n", agent->username);
+               agent_devstate_changed(agent->username);
+       }
+       ao2_iterator_destroy(&iter);
+       agents_sweep();
+}
+
+static int agent_logoff_request(const char *agent_id, int soft)
+{
+       struct ast_channel *logged;
+       RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, agent_id, OBJ_KEY), ao2_cleanup);
+
+       if (!agent) {
+               return -1;
+       }
+
+       agent_lock(agent);
+       logged = agent_lock_logged(agent);
+       if (logged) {
+               if (soft) {
+                       agent->deferred_logoff = 1;
+               } else {
+                       ast_softhangup(logged, AST_SOFTHANGUP_EXPLICIT);
+               }
+               ast_channel_unlock(logged);
+               ast_channel_unref(logged);
+       }
+       agent_unlock(agent);
+       return 0;
+}
+
+/*! Agent holding bridge instance holder. */
+static AO2_GLOBAL_OBJ_STATIC(agent_holding);
+
+/*! Agent holding bridge deferred creation lock. */
+AST_MUTEX_DEFINE_STATIC(agent_holding_lock);
+
+/*!
+ * \internal
+ * \brief Connect the agent with the waiting caller.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Agent channel connecting to the caller.
+ * \param agent Which agent is connecting to the caller.
+ *
+ * \note The agent is locked on entry and not locked on exit.
+ *
+ * \return Nothing
+ */
+static void agent_connect_caller(struct ast_bridge_channel *bridge_channel, struct agent_pvt *agent)
+{
+       struct ast_bridge *caller_bridge;
+       int record_agent_calls;
+       int res;
+
+       record_agent_calls = agent->cfg->record_agent_calls;
+       caller_bridge = agent->caller_bridge;
+       agent->caller_bridge = NULL;
+       agent->state = AGENT_STATE_ON_CALL;
+       time(&agent->call_start);
+       agent_unlock(agent);
+
+       if (!caller_bridge) {
+               /* Reset agent. */
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               return;
+       }
+       res = ast_bridge_move(caller_bridge, bridge_channel->bridge, bridge_channel->chan,
+               NULL, 0);
+       if (res) {
+               /* Reset agent. */
+               ast_bridge_destroy(caller_bridge);
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               return;
+       }
+       ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_ANSWER, NULL, 0);
+
+       if (record_agent_calls) {
+               struct ast_bridge_features_automixmonitor options = {
+                       .start_stop = AUTO_MONITOR_START,
+                       };
+
+               /*
+                * The agent is in the new bridge so we can invoke the
+                * mixmonitor hook to only start recording.
+                */
+               ast_bridge_features_do(AST_BRIDGE_BUILTIN_AUTOMIXMON, caller_bridge,
+                       bridge_channel, &options);
+       }
+}
+
+static int bridge_agent_hold_ack(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct agent_pvt *agent = hook_pvt;
+
+       agent_lock(agent);
+       switch (agent->state) {
+       case AGENT_STATE_CALL_WAIT_ACK:
+               /* Connect to caller now. */
+               ast_debug(1, "Agent %s: Acked call.\n", agent->username);
+               agent_connect_caller(bridge_channel, agent);/* Will unlock agent. */
+               return 0;
+       default:
+               break;
+       }
+       agent_unlock(agent);
+       return 0;
+}
+
+static int bridge_agent_hold_heartbeat(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct agent_pvt *agent = hook_pvt;
+       int probation_timedout = 0;
+       int ack_timedout = 0;
+       int wrapup_timedout = 0;
+       int deferred_logoff;
+       unsigned int wrapup_time;
+       unsigned int auto_logoff;
+
+       agent_lock(agent);
+       deferred_logoff = agent->deferred_logoff;
+       if (deferred_logoff) {
+               agent->state = AGENT_STATE_LOGGING_OUT;
+       }
+
+       switch (agent->state) {
+       case AGENT_STATE_PROBATION_WAIT:
+               probation_timedout =
+                       LOGIN_WAIT_TIMEOUT_TIME <= (time(NULL) - agent->probation_start);
+               if (probation_timedout) {
+                       /* Now ready for a caller. */
+                       agent->state = AGENT_STATE_READY_FOR_CALL;
+                       agent->devstate = AST_DEVICE_NOT_INUSE;
+               }
+               break;
+       case AGENT_STATE_CALL_WAIT_ACK:
+               /* Check ack call time. */
+               auto_logoff = agent->cfg->auto_logoff;
+               if (ast_test_flag(agent, AGENT_FLAG_AUTO_LOGOFF)) {
+                       auto_logoff = agent->override_auto_logoff;
+               }
+               if (auto_logoff) {
+                       auto_logoff *= 1000;
+                       ack_timedout = ast_tvdiff_ms(ast_tvnow(), agent->ack_time) > auto_logoff;
+                       if (ack_timedout) {
+                               agent->state = AGENT_STATE_LOGGING_OUT;
+                       }
+               }
+               break;
+       case AGENT_STATE_CALL_WRAPUP:
+               /* Check wrapup time. */
+               wrapup_time = agent->cfg->wrapup_time;
+               if (ast_test_flag(agent, AGENT_FLAG_WRAPUP_TIME)) {
+                       wrapup_time = agent->override_wrapup_time;
+               }
+               wrapup_timedout = ast_tvdiff_ms(ast_tvnow(), agent->last_disconnect) > wrapup_time;
+               if (wrapup_timedout) {
+                       agent->state = AGENT_STATE_READY_FOR_CALL;
+                       agent->devstate = AST_DEVICE_NOT_INUSE;
+               }
+               break;
+       default:
+               break;
+       }
+       agent_unlock(agent);
+
+       if (deferred_logoff) {
+               ast_debug(1, "Agent %s: Deferred logoff.\n", agent->username);
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+       } else if (probation_timedout) {
+               ast_debug(1, "Agent %s: Login complete.\n", agent->username);
+               agent_devstate_changed(agent->username);
+       } else if (ack_timedout) {
+               ast_debug(1, "Agent %s: Ack call timeout.\n", agent->username);
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+       } else if (wrapup_timedout) {
+               ast_debug(1, "Agent %s: Wrapup timeout. Ready for new call.\n", agent->username);
+               agent_devstate_changed(agent->username);
+       }
+
+       return 0;
+}
+
+static void agent_after_bridge_cb(struct ast_channel *chan, void *data);
+static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason, void *data);
+
+/*!
+ * \internal
+ * \brief ast_bridge agent_hold push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_agent_hold_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+       int res = 0;
+       unsigned int wrapup_time;
+       char dtmf[AST_FEATURE_MAX_LEN];
+       struct ast_channel *chan;
+       const char *moh_class;
+       RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
+
+       chan = bridge_channel->chan;
+
+       agent = ao2_find(agents, swap ? swap->chan : chan, 0);
+       if (!agent) {
+               /* Could not find the agent. */
+               return -1;
+       }
+
+       /* Setup agent entertainment */
+       agent_lock(agent);
+       moh_class = ast_strdupa(agent->cfg->moh);
+       agent_unlock(agent);
+       res |= ast_channel_add_bridge_role(chan, "holding_participant");
+       res |= ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
+       res |= ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", moh_class);
+
+       /* Add DTMF acknowledge hook. */
+       dtmf[0] = '\0';
+       agent_lock(agent);
+       if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
+               ? agent->override_ack_call : agent->cfg->ack_call) {
+               const char *dtmf_accept;
+
+               dtmf_accept = ast_test_flag(agent, AGENT_FLAG_DTMF_ACCEPT)
+                       ? agent->override_dtmf_accept : agent->cfg->dtmf_accept;
+               ast_copy_string(dtmf, dtmf_accept, sizeof(dtmf));
+       }
+       agent_unlock(agent);
+       if (!ast_strlen_zero(dtmf)) {
+               ao2_ref(agent, +1);
+               if (ast_bridge_dtmf_hook(bridge_channel->features, dtmf, bridge_agent_hold_ack,
+                       agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
+                       ao2_ref(agent, -1);
+                       res = -1;
+               }
+       }
+
+       /* Add heartbeat interval hook. */
+       ao2_ref(agent, +1);
+       if (ast_bridge_interval_hook(bridge_channel->features, 1000,
+               bridge_agent_hold_heartbeat, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
+               ao2_ref(agent, -1);
+               res = -1;
+       }
+
+       res |= ast_bridge_base_v_table.push(self, bridge_channel, swap);
+       if (res) {
+               ast_channel_remove_bridge_role(chan, "holding_participant");
+               return -1;
+       }
+
+       if (swap) {
+               res = ast_after_bridge_callback_set(chan, agent_after_bridge_cb,
+                       agent_after_bridge_cb_failed, chan);
+               if (res) {
+                       ast_channel_remove_bridge_role(chan, "holding_participant");
+                       return -1;
+               }
+
+               agent_lock(agent);
+               ast_channel_unref(agent->logged);
+               agent->logged = ast_channel_ref(chan);
+               agent_unlock(agent);
+
+               /*
+                * Kick the channel out so it can come back in fully controlled.
+                * Otherwise, the after bridge callback will linger and the
+                * agent will have some slightly different behavior in corner
+                * cases.
+                */
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               return 0;
+       }
+
+       agent_lock(agent);
+       switch (agent->state) {
+       case AGENT_STATE_LOGGED_OUT:
+               /*!
+                * \todo XXX the login probation time should be only if it is needed.
+                *
+                * Need to determine if there are any local channels that can
+                * optimize and wait until they actually do before leaving the
+                * AGENT_STATE_PROBATION_WAIT state.  For now, the blind
+                * timer of LOGIN_WAIT_TIMEOUT_TIME will do.
+                */
+               /*
+                * Start the login probation timer.
+                *
+                * We cannot handle an agent local channel optimization when the
+                * agent is on a call.  The optimization may kick the agent
+                * channel we know about out of the call without our being able
+                * to switch to the replacement channel.  Get any agent local
+                * channel optimization out of the way while the agent is in the
+                * holding bridge.
+                */
+               time(&agent->probation_start);
+               agent->state = AGENT_STATE_PROBATION_WAIT;
+               agent_unlock(agent);
+               break;
+       case AGENT_STATE_PROBATION_WAIT:
+               /* Restart the probation timer. */
+               time(&agent->probation_start);
+               agent_unlock(agent);
+               break;
+       case AGENT_STATE_READY_FOR_CALL:
+               /*
+                * Likely someone manally kicked us out of the holding bridge
+                * and we came right back in.
+                */
+               agent_unlock(agent);
+               break;
+       default:
+               /* Unexpected agent state. */
+               ast_assert(0);
+               /* Fall through */
+       case AGENT_STATE_CALL_PRESENT:
+       case AGENT_STATE_CALL_WAIT_ACK:
+               agent->state = AGENT_STATE_READY_FOR_CALL;
+               agent->devstate = AST_DEVICE_NOT_INUSE;
+               agent_unlock(agent);
+               ast_debug(1, "Agent %s: Call abort recovery complete.\n", agent->username);
+               agent_devstate_changed(agent->username);
+               break;
+       case AGENT_STATE_ON_CALL:
+       case AGENT_STATE_CALL_WRAPUP:
+               wrapup_time = agent->cfg->wrapup_time;
+               if (ast_test_flag(agent, AGENT_FLAG_WRAPUP_TIME)) {
+                       wrapup_time = agent->override_wrapup_time;
+               }
+               if (wrapup_time) {
+                       agent->state = AGENT_STATE_CALL_WRAPUP;
+               } else {
+                       agent->state = AGENT_STATE_READY_FOR_CALL;
+                       agent->devstate = AST_DEVICE_NOT_INUSE;
+               }
+               agent_unlock(agent);
+               if (!wrapup_time) {
+                       /* No wrapup time. */
+                       ast_debug(1, "Agent %s: Ready for new call.\n", agent->username);
+                       agent_devstate_changed(agent->username);
+               }
+               break;
+       }
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge agent_hold pull method.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \details
+ * Remove any channel hooks controlled by the bridge.  Release
+ * any resources held by bridge_channel->bridge_pvt and release
+ * bridge_channel->bridge_pvt.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_agent_hold_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
+{
+       ast_channel_remove_bridge_role(bridge_channel->chan, "holding_participant");
+       ast_bridge_base_v_table.pull(self, bridge_channel);
+}
+
+/*!
+ * \brief The bridge is being dissolved.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \details
+ * The bridge is being dissolved.  Remove any external
+ * references to the bridge so it can be destroyed.
+ *
+ * \note On entry, self must NOT be locked.
+ *
+ * \return Nothing
+ */
+static void bridge_agent_hold_dissolving(struct ast_bridge *self)
+{
+       ao2_global_obj_replace_unref(agent_holding, NULL);
+       ast_bridge_base_v_table.dissolving(self);
+}
+
+static struct ast_bridge_methods bridge_agent_hold_v_table;
+
+static struct ast_bridge *bridge_agent_hold_new(void)
+{
+       struct ast_bridge *bridge;
+
+       bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &bridge_agent_hold_v_table);
+       bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
+               AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+                       | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+       bridge = ast_bridge_register(bridge);
+       return bridge;
+}
+
+static void bridging_init_agent_hold(void)
+{
+       /* Setup bridge agent_hold subclass v_table. */
+       bridge_agent_hold_v_table = ast_bridge_base_v_table;
+       bridge_agent_hold_v_table.name = "agent_hold";
+       bridge_agent_hold_v_table.dissolving = bridge_agent_hold_dissolving;
+       bridge_agent_hold_v_table.push = bridge_agent_hold_push;
+       bridge_agent_hold_v_table.pull = bridge_agent_hold_pull;
+}
+
+static int bridge_agent_hold_deferred_create(void)
+{
+       RAII_VAR(struct ast_bridge *, holding, ao2_global_obj_ref(agent_holding), ao2_cleanup);
+
+       if (!holding) {
+               ast_mutex_lock(&agent_holding_lock);
+               holding = ao2_global_obj_ref(agent_holding);
+               if (!holding) {
+                       holding = bridge_agent_hold_new();
+                       ao2_global_obj_replace_unref(agent_holding, holding);
+               }
+               ast_mutex_unlock(&agent_holding_lock);
+               if (!holding) {
+                       ast_log(LOG_ERROR, "Could not create agent holding bridge.\n");
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+static void send_agent_login(struct ast_channel *chan, const char *agent)
+{
+       RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+
+       ast_assert(agent != NULL);
+
+       blob = ast_json_pack("{s: s}",
+               "agent", agent);
+       if (!blob) {
+               return;
+       }
+
+       ast_channel_publish_blob(chan, ast_channel_agent_login_type(), blob);
+}
+
+static void send_agent_logoff(struct ast_channel *chan, const char *agent, long logintime)
+{
+       RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+
+       ast_assert(agent != NULL);
+
+       blob = ast_json_pack("{s: s, s: i}",
+               "agent", agent,
+               "logintime", logintime);
+       if (!blob) {
+               return;
+       }
+
+       ast_channel_publish_blob(chan, ast_channel_agent_logoff_type(), blob);
+}
+
+/*!
+ * \internal
+ * \brief Logout the agent.
+ * \since 12.0.0
+ *
+ * \param agent Which agent logging out.
+ *
+ * \note On entry agent is already locked.  On exit it is no longer locked.
+ *
+ * \return Nothing
+ */
+static void agent_logout(struct agent_pvt *agent)
+{
+       struct ast_channel *logged;
+       struct ast_bridge *caller_bridge;
+       long time_logged_in;
+
+       time_logged_in = time(NULL) - agent->login_start;
+       logged = agent->logged;
+       agent->logged = NULL;
+       caller_bridge = agent->caller_bridge;
+       caller_bridge = NULL;
+       agent->state = AGENT_STATE_LOGGED_OUT;
+       agent->devstate = AST_DEVICE_UNAVAILABLE;
+       ast_clear_flag(agent, AST_FLAGS_ALL);
+       agent_unlock(agent);
+       agent_devstate_changed(agent->username);
+
+       if (caller_bridge) {
+               ast_bridge_destroy(caller_bridge);
+       }
+
+       send_agent_logoff(logged, agent->username, time_logged_in);
+       ast_verb(2, "Agent '%s' logged out.  Logged in for %ld seconds.\n",
+               agent->username, time_logged_in);
+       ast_channel_unref(logged);
+}
+
+/*!
+ * \internal
+ * \brief Agent driver loop.
+ * \since 12.0.0
+ *
+ * \param agent Which agent.
+ * \param logged The logged in channel.
+ *
+ * \return Nothing
+ */
+static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
+{
+       struct ast_bridge_features features;
+
+       if (!ast_bridge_features_init(&features)) {
+               for (;;) {
+                       struct agents_cfg *cfgs;
+                       struct agent_cfg *cfg_new;
+                       struct agent_cfg *cfg_old;
+                       struct ast_bridge *holding;
+                       struct ast_bridge *caller_bridge;
+
+                       holding = ao2_global_obj_ref(agent_holding);
+                       if (!holding) {
+                               break;
+                       }
+
+                       ast_bridge_join(holding, logged, NULL, &features, NULL, 1);
+                       if (logged != agent->logged) {
+                               break;
+                       }
+
+                       if (agent->dead) {
+                               break;
+                       }
+
+                       /* Update the agent's config before rejoining the holding bridge. */
+                       cfgs = ao2_global_obj_ref(cfg_handle);
+                       if (!cfgs) {
+                               break;
+                       }
+                       cfg_new = ao2_find(cfgs->agents, agent->username, OBJ_KEY);
+                       ao2_ref(cfgs, -1);
+                       if (!cfg_new) {
+                               break;
+                       }
+                       agent_lock(agent);
+                       cfg_old = agent->cfg;
+                       agent->cfg = cfg_new;
+
+                       agent->last_disconnect = ast_tvnow();
+
+                       /* Clear out any caller bridge before rejoining the holding bridge. */
+                       caller_bridge = agent->caller_bridge;
+                       agent->caller_bridge = NULL;
+                       agent_unlock(agent);
+                       ao2_ref(cfg_old, -1);
+                       if (caller_bridge) {
+                               ast_bridge_destroy(caller_bridge);
+                       }
+
+                       if (agent->state == AGENT_STATE_LOGGING_OUT
+                               || agent->deferred_logoff
+                               || ast_check_hangup_locked(logged)) {
+                               break;
+                       }
+
+                       /*
+                        * It is safe to access agent->waiting_colp without a lock.  It
+                        * is only setup on agent login and not changed.
+                        */
+                       ast_channel_update_connected_line(logged, &agent->waiting_colp, NULL);
+               }
+               ast_bridge_features_cleanup(&features);
+       }
+
+       agent_lock(agent);
+       if (logged != agent->logged) {
+               /*
+                * We are no longer the agent channel because of local channel
+                * optimization.
+                */
+               agent_unlock(agent);
+               ast_debug(1, "Agent %s: Channel %s is no longer the agent.\n",
+                       agent->username, ast_channel_name(logged));
+               return;
+       }
+       agent_logout(agent);
+}
+
+static void agent_after_bridge_cb(struct ast_channel *chan, void *data)
+{
+       struct agent_pvt *agent;
+
+       agent = ao2_find(agents, chan, 0);
+       if (!agent) {
+               return;
+       }
+
+       ast_debug(1, "Agent %s: New agent channel %s.\n",
+               agent->username, ast_channel_name(chan));
+       agent_run(agent, chan);
+       ao2_ref(agent, -1);
+}
+
+static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason, void *data)
+{
+       struct ast_channel *chan = data;
+       struct agent_pvt *agent;
+
+       agent = ao2_find(agents, chan, 0);
+       if (!agent) {
+               return;
+       }
+       ast_log(LOG_WARNING, "Agent %s: Forced logout.  Lost control of %s because: %s\n",
+               agent->username, ast_channel_name(chan),
+               ast_after_bridge_cb_reason_string(reason));
+       agent_lock(agent);
+       agent_logout(agent);
+       ao2_ref(agent, -1);
+}
+
+/*!
+ * \internal
+ * \brief Get the lock on the agent bridge_channel and return it.
+ * \since 12.0.0
+ *
+ * \param agent Whose bridge_chanel to get.
+ *
+ * \retval bridge_channel on success (Reffed and locked).
+ * \retval NULL on error.
+ */
+static struct ast_bridge_channel *agent_bridge_channel_get_lock(struct agent_pvt *agent)
+{
+       struct ast_channel *logged;
+       struct ast_bridge_channel *bc;
+
+       for (;;) {
+               agent_lock(agent);
+               logged = agent->logged;
+               if (!logged) {
+                       agent_unlock(agent);
+                       return NULL;
+               }
+               ast_channel_ref(logged);
+               agent_unlock(agent);
+
+               ast_channel_lock(logged);
+               bc = ast_channel_get_bridge_channel(logged);
+               ast_channel_unlock(logged);
+               ast_channel_unref(logged);
+               if (!bc) {
+                       if (agent->logged != logged) {
+                               continue;
+                       }
+                       return NULL;
+               }
+
+               ast_bridge_channel_lock(bc);
+               if (bc->chan != logged || agent->logged != logged) {
+                       ast_bridge_channel_unlock(bc);
+                       ao2_ref(bc, -1);
+                       continue;
+               }
+               return bc;
+       }
+}
+
+static void caller_abort_agent(struct agent_pvt *agent)
+{
+       struct ast_bridge_channel *logged;
+
+       logged = agent_bridge_channel_get_lock(agent);
+       if (!logged) {
+               struct ast_bridge *caller_bridge;
+
+               ast_debug(1, "Agent '%s' no longer logged in.\n", agent->username);
+
+               agent_lock(agent);
+               caller_bridge = agent->caller_bridge;
+               agent->caller_bridge = NULL;
+               agent_unlock(agent);
+               if (caller_bridge) {
+                       ast_bridge_destroy(caller_bridge);
+               }
+               return;
+       }
+
+       /* Kick the agent out of the holding bridge to reset it. */
+       ast_bridge_change_state_nolock(logged, AST_BRIDGE_CHANNEL_STATE_END);
+       ast_bridge_channel_unlock(logged);
+}
+
+static int caller_safety_timeout(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct agent_pvt *agent = hook_pvt;
+
+       if (agent->state == AGENT_STATE_CALL_PRESENT) {
+               ast_verb(3, "Agent '%s' did not respond.  Safety timeout.\n", agent->username);
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               caller_abort_agent(agent);
+       }
+
+       return -1;
+}
+
+static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *payload, size_t payload_size)
+{
+       const char *agent_id = payload;
+       const char *playfile;
+       RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
+
+       agent = ao2_find(agents, agent_id, OBJ_KEY);
+       if (!agent) {
+               ast_debug(1, "Agent '%s' does not exist.  Where did it go?\n", agent_id);
+               return;
+       }
+
+       /* Alert the agent. */
+       agent_lock(agent);
+       playfile = ast_strdupa(agent->cfg->beep_sound);
+       agent_unlock(agent);
+       ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE);
+
+       agent_lock(agent);
+       switch (agent->state) {
+       case AGENT_STATE_CALL_PRESENT:
+               if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
+                       ? agent->override_ack_call : agent->cfg->ack_call) {
+                       agent->state = AGENT_STATE_CALL_WAIT_ACK;
+                       agent->ack_time = ast_tvnow();
+                       break;
+               }
+
+               /* Connect to caller now. */
+               ast_debug(1, "Agent %s: Immediately connecting to call.\n", agent->username);
+               agent_connect_caller(bridge_channel, agent);/* Will unlock agent. */
+               return;
+       default:
+               break;
+       }
+       agent_unlock(agent);
+}
+
+static int send_alert_to_agent(struct ast_bridge_channel *bridge_channel, const char *agent_id)
+{
+       return ast_bridge_channel_queue_callback(bridge_channel, agent_alert, agent_id,
+               strlen(agent_id) + 1);
+}
+
+static int send_colp_to_agent(struct ast_bridge_channel *bridge_channel, struct ast_party_connected_line *connected)
+{
+       struct ast_set_party_connected_line update = {
+               .id.name = 1,
+               .id.number = 1,
+               .id.subaddress = 1,
+       };
+       unsigned char data[1024];       /* This should be large enough */
+       size_t datalen;
+
+       datalen = ast_connected_line_build_data(data, sizeof(data), connected, &update);
+       if (datalen == (size_t) -1) {
+               return 0;
+       }
+
+       return ast_bridge_channel_queue_control_data(bridge_channel,
+               AST_CONTROL_CONNECTED_LINE, data, datalen);
+}
+
+/*!
+ * \brief Dialplan AgentRequest application to locate an agent to talk with.
+ *
+ * \param chan Channel wanting to talk with an agent.
+ * \param data Application parameters
+ *
+ * \retval 0 To continue in dialplan.
+ * \retval -1 To hangup.
+ */
+static int agent_request_exec(struct ast_channel *chan, const char *data)
+{
+       struct ast_bridge *caller_bridge;
+       struct ast_bridge_channel *logged;
+       char *parse;
+       int res;
+       struct ast_bridge_features caller_features;
+       struct ast_party_connected_line connected;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(agent_id);
+               AST_APP_ARG(other);             /* Any remaining unused arguments */
+       );
+
+       RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
+
+       if (bridge_agent_hold_deferred_create()) {
+               return -1;
+       }
+
+       parse = ast_strdupa(data ?: "");
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.agent_id)) {
+               ast_log(LOG_WARNING, "AgentRequest requires an AgentId\n");
+               return -1;
+       }
+
+       /* Find the agent. */
+       agent = ao2_find(agents, args.agent_id, OBJ_KEY);
+       if (!agent) {
+               ast_verb(3, "Agent '%s' does not exist.\n", args.agent_id);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "INVALID");
+               return 0;
+       }
+
+       if (ast_bridge_features_init(&caller_features)) {
+               return -1;
+       }
+
+       /* Add safety timeout hook. */
+       ao2_ref(agent, +1);
+       if (ast_bridge_interval_hook(&caller_features, CALLER_SAFETY_TIMEOUT_TIME,
+               caller_safety_timeout, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
+               ao2_ref(agent, -1);
+               ast_bridge_features_cleanup(&caller_features);
+               return -1;
+       }
+
+       caller_bridge = ast_bridge_basic_new();
+       if (!caller_bridge) {
+               ast_bridge_features_cleanup(&caller_features);
+               return -1;
+       }
+
+       /* Get COLP for agent. */
+       ast_party_connected_line_init(&connected);
+       ast_channel_lock(chan);
+       ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
+       ast_channel_unlock(chan);
+
+       agent_lock(agent);
+       switch (agent->state) {
+       case AGENT_STATE_LOGGED_OUT:
+       case AGENT_STATE_LOGGING_OUT:
+               agent_unlock(agent);
+               ast_party_connected_line_free(&connected);
+               ast_bridge_destroy(caller_bridge);
+               ast_bridge_features_cleanup(&caller_features);
+               ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN");
+               return 0;
+       case AGENT_STATE_READY_FOR_CALL:
+               ao2_ref(caller_bridge, +1);
+               agent->caller_bridge = caller_bridge;
+               agent->state = AGENT_STATE_CALL_PRESENT;
+               agent->devstate = AST_DEVICE_INUSE;
+               break;
+       default:
+               agent_unlock(agent);
+               ast_party_connected_line_free(&connected);
+               ast_bridge_destroy(caller_bridge);
+               ast_bridge_features_cleanup(&caller_features);
+               ast_verb(3, "Agent '%s' is busy.\n", agent->username);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "BUSY");
+               return 0;
+       }
+       agent_unlock(agent);
+       agent_devstate_changed(agent->username);
+
+       logged = agent_bridge_channel_get_lock(agent);
+       if (!logged) {
+               ast_party_connected_line_free(&connected);
+               ast_bridge_destroy(caller_bridge);
+               ast_bridge_features_cleanup(&caller_features);
+               ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN");
+               caller_abort_agent(agent);
+               return 0;
+       }
+
+       send_colp_to_agent(logged, &connected);
+       ast_party_connected_line_free(&connected);
+
+       res = send_alert_to_agent(logged, agent->username);
+       ast_bridge_channel_unlock(logged);
+       ao2_ref(logged, -1);
+       if (res) {
+               ast_bridge_destroy(caller_bridge);
+               ast_bridge_features_cleanup(&caller_features);
+               ast_verb(3, "Agent '%s': Failed to alert the agent.\n", agent->username);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ERROR");
+               caller_abort_agent(agent);
+               return 0;
+       }
+
+       ast_indicate(chan, AST_CONTROL_RINGING);
+       ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL, 1);
+       ast_bridge_features_cleanup(&caller_features);
+
+       return -1;
+}
+
+/*!
+ * \internal
+ * \brief Get agent config values from the login channel.
+ * \since 12.0.0
+ *
+ * \param agent What to setup channel config values on.
+ * \param chan Channel logging in as an agent.
+ *
+ * \return Nothing
+ */
+static void agent_login_channel_config(struct agent_pvt *agent, struct ast_channel *chan)
+{
+       struct ast_flags opts = { 0 };
+       struct ast_party_connected_line connected;
+       unsigned int override_ack_call = 0;
+       unsigned int override_auto_logoff = 0;
+       unsigned int override_wrapup_time = 0;
+       const char *override_dtmf_accept = NULL;
+       const char *var;
+
+       ast_party_connected_line_init(&connected);
+
+       /* Get config values from channel. */
+       ast_channel_lock(chan);
+       ast_party_connected_line_copy(&connected, ast_channel_connected(chan));
+
+       var = pbx_builtin_getvar_helper(chan, "AGENTACKCALL");
+       if (!ast_strlen_zero(var)) {
+               override_ack_call = ast_true(var) ? 1 : 0;
+               ast_set_flag(&opts, AGENT_FLAG_ACK_CALL);
+       }
+
+       var = pbx_builtin_getvar_helper(chan, "AGENTACCEPTDTMF");
+       if (!ast_strlen_zero(var)) {
+               override_dtmf_accept = ast_strdupa(var);
+               ast_set_flag(&opts, AGENT_FLAG_DTMF_ACCEPT);
+       }
+
+       var = pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF");
+       if (!ast_strlen_zero(var)) {
+               if (sscanf(var, "%u", &override_auto_logoff) == 1) {
+                       ast_set_flag(&opts, AGENT_FLAG_AUTO_LOGOFF);
+               }
+       }
+
+       var = pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME");
+       if (!ast_strlen_zero(var)) {
+               if (sscanf(var, "%u", &override_wrapup_time) == 1) {
+                       ast_set_flag(&opts, AGENT_FLAG_WRAPUP_TIME);
+               }
+       }
+       ast_channel_unlock(chan);
+
+       /* Set config values on agent. */
+       agent_lock(agent);
+       ast_party_connected_line_free(&agent->waiting_colp);
+       agent->waiting_colp = connected;
+
+       ast_string_field_set(agent, override_dtmf_accept, override_dtmf_accept);
+       ast_copy_flags(agent, &opts, AST_FLAGS_ALL);
+       agent->override_auto_logoff = override_auto_logoff;
+       agent->override_wrapup_time = override_wrapup_time;
+       agent->override_ack_call = override_ack_call;
+       agent_unlock(agent);
+}
+
+enum AGENT_LOGIN_OPT_FLAGS {
+       OPT_SILENT = (1 << 0),
+};
+AST_APP_OPTIONS(agent_login_opts, BEGIN_OPTIONS
+       AST_APP_OPTION('s', OPT_SILENT),
+END_OPTIONS);
+
+/*!
+ * \brief Dialplan AgentLogin application to log in an agent.
+ *
+ * \param chan Channel attempting to login as an agent.
+ * \param data Application parameters
+ *
+ * \retval 0 To continue in dialplan.
+ * \retval -1 To hangup.
+ */
+static int agent_login_exec(struct ast_channel *chan, const char *data)
+{
+       char *parse;
+       struct ast_flags opts;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(agent_id);
+               AST_APP_ARG(options);
+               AST_APP_ARG(other);             /* Any remaining unused arguments */
+       );
+
+       RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
+
+       if (bridge_agent_hold_deferred_create()) {
+               return -1;
+       }
+
+       if (ast_channel_state(chan) != AST_STATE_UP && ast_answer(chan)) {
+               return -1;
+       }
+
+       parse = ast_strdupa(data ?: "");
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.agent_id)) {
+               ast_log(LOG_WARNING, "AgentLogin requires an AgentId\n");
+               return -1;
+       }
+
+       if (ast_app_parse_options(agent_login_opts, &opts, NULL, args.options)) {
+               /* General invalid option syntax. */
+               return -1;
+       }
+
+       /* Find the agent. */
+       agent = ao2_find(agents, args.agent_id, OBJ_KEY);
+       if (!agent) {
+               ast_verb(3, "Agent '%s' does not exist.\n", args.agent_id);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "INVALID");
+               return 0;
+       }
+
+       /* Has someone already logged in as this agent already? */
+       agent_lock(agent);
+       if (agent->logged) {
+               agent_unlock(agent);
+               ast_verb(3, "Agent '%s' already logged in.\n", agent->username);
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ALREADY_LOGGED_IN");
+               return 0;
+       }
+       agent->logged = ast_channel_ref(chan);
+       agent->last_disconnect = ast_tvnow();
+       time(&agent->login_start);
+       agent_unlock(agent);
+
+       agent_login_channel_config(agent, chan);
+
+       if (!ast_test_flag(&opts, OPT_SILENT)
+               && !ast_streamfile(chan, "agent-loginok", ast_channel_language(chan))) {
+               ast_waitstream(chan, "");
+       }
+
+       ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", agent->username,
+               ast_getformatname(ast_channel_readformat(chan)),
+               ast_getformatname(ast_channel_writeformat(chan)));
+       send_agent_login(chan, agent->username);
+
+       agent_run(agent, chan);
+       return -1;
+}
+
+static int agent_function_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       char *parse;
+       struct agent_pvt *agent;
+       struct ast_channel *logged;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(agentid);
+               AST_APP_ARG(item);
+       );
+
+       buf[0] = '\0';
+
+       parse = ast_strdupa(data ?: "");
+       AST_NONSTANDARD_APP_ARGS(args, parse, ':');
+
+       if (ast_strlen_zero(args.agentid)) {
+               ast_log(LOG_WARNING, "The AGENT function requires an argument - agentid!\n");
+               return -1;
+       }
+       if (!args.item) {
+               args.item = "status";
+       }
+
+       agent = ao2_find(agents, args.agentid, OBJ_KEY);
+       if (!agent) {
+               ast_log(LOG_WARNING, "Agent '%s' not found!\n", args.agentid);
+               return -1;
+       }
+
+       agent_lock(agent);
+       if (!strcasecmp(args.item, "status")) {
+               const char *status;
+
+               if (agent->logged) {
+                       status = "LOGGEDIN";
+               } else {
+                       status = "LOGGEDOUT";
+               }
+               ast_copy_string(buf, status, len);
+       } else if (!strcasecmp(args.item, "name")) {
+               ast_copy_string(buf, agent->cfg->full_name, len);
+       } else if (!strcasecmp(args.item, "mohclass")) {
+               ast_copy_string(buf, agent->cfg->moh, len);
+       } else if (!strcasecmp(args.item, "channel")) {
+               logged = agent_lock_logged(agent);
+               if (logged) {
+                       char *pos;
+
+                       ast_copy_string(buf, ast_channel_name(logged), len);
+                       ast_channel_unlock(logged);
+                       ast_channel_unref(logged);
+
+                       pos = strrchr(buf, '-');
+                       if (pos) {
+                               *pos = '\0';
+                       }
+               }
+       } else if (!strcasecmp(args.item, "fullchannel")) {
+               logged = agent_lock_logged(agent);
+               if (logged) {
+                       ast_copy_string(buf, ast_channel_name(logged), len);
+                       ast_channel_unlock(logged);
+                       ast_channel_unref(logged);
+               }
+       }
+       agent_unlock(agent);
+       ao2_ref(agent, -1);
+
+       return 0;
+}
+
+static struct ast_custom_function agent_function = {
+       .name = "AGENT",
+       .read = agent_function_read,
+};
+
+struct agent_complete {
+       /*! Nth match to return. */
+       int state;
+       /*! Which match currently on. */
+       int which;
+};
+
+static int complete_agent_search(void *obj, void *arg, void *data, int flags)
+{
+       struct agent_complete *search = data;
+
+       if (++search->which > search->state) {
+               return CMP_MATCH;
+       }
+       return 0;
+}
+
+static char *complete_agent(const char *word, int state)
+{
+       char *ret;
+       struct agent_pvt *agent;
+       struct agent_complete search = {
+               .state = state,
+       };
+
+       agent = ao2_callback_data(agents, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+               complete_agent_search, (char *) word, &search);
+       if (!agent) {
+               return NULL;
+       }
+       ret = ast_strdup(agent->username);
+       ao2_ref(agent, -1);
+       return ret;
+}
+
+static int complete_agent_logoff_search(void *obj, void *arg, void *data, int flags)
+{
+       struct agent_pvt *agent = obj;
+       struct agent_complete *search = data;
+
+       if (!agent->logged) {
+               return 0;
+       }
+       if (++search->which > search->state) {
+               return CMP_MATCH;
+       }
+       return 0;
+}
+
+static char *complete_agent_logoff(const char *word, int state)
+{
+       char *ret;
+       struct agent_pvt *agent;
+       struct agent_complete search = {
+               .state = state,
+       };
+
+       agent = ao2_callback_data(agents, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+               complete_agent_logoff_search, (char *) word, &search);
+       if (!agent) {
+               return NULL;
+       }
+       ret = ast_strdup(agent->username);
+       ao2_ref(agent, -1);
+       return ret;
+}
+
+static void agent_show_requested(struct ast_cli_args *a, int online_only)
+{
+#define FORMAT_HDR "%-8s %-20s %-11s %-30s %s\n"
+#define FORMAT_ROW "%-8s %-20s %-11s %-30s %s\n"
+
+       struct ao2_iterator iter;
+       struct agent_pvt *agent;
+       struct ast_str *out = ast_str_alloca(512);
+       unsigned int agents_total = 0;
+       unsigned int agents_logged_in = 0;
+       unsigned int agents_talking = 0;
+
+       ast_cli(a->fd, FORMAT_HDR, "Agent-ID", "Name", "State", "Channel", "Talking with");
+       iter = ao2_iterator_init(agents, 0);
+       for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
+               struct ast_channel *logged;
+
+               ++agents_total;
+
+               agent_lock(agent);
+               logged = agent_lock_logged(agent);
+               if (logged) {
+                       const char *talking_with;
+
+                       ++agents_logged_in;
+
+                       talking_with = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
+                       if (!ast_strlen_zero(talking_with)) {
+                               ++agents_talking;
+                       } else {
+                               talking_with = "";
+                       }
+                       ast_str_set(&out, 0, FORMAT_ROW, agent->username, agent->cfg->full_name,
+                               ast_devstate_str(agent->devstate), ast_channel_name(logged), talking_with);
+                       ast_channel_unlock(logged);
+                       ast_channel_unref(logged);
+               } else {
+                       ast_str_set(&out, 0, FORMAT_ROW, agent->username, agent->cfg->full_name,
+                               ast_devstate_str(agent->devstate), "", "");
+               }
+               agent_unlock(agent);
+
+               if (!online_only || logged) {
+                       ast_cli(a->fd, "%s", ast_str_buffer(out));
+               }
+       }
+       ao2_iterator_destroy(&iter);
+
+       ast_cli(a->fd, "\nDefined agents: %u, Logged in: %u, Talking: %u\n",
+               agents_total, agents_logged_in, agents_talking);
+
+#undef FORMAT_HDR
+#undef FORMAT_ROW
+}
+
+static char *agent_handle_show_online(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "agent show online";
+               e->usage =
+                       "Usage: agent show online\n"
+                       "       Provides summary information for logged in agents.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
+       }
+
+       agent_show_requested(a, 1);
+
+       return CLI_SUCCESS;
+}
+
+static char *agent_handle_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "agent show all";
+               e->usage =
+                       "Usage: agent show all\n"
+                       "       Provides summary information for all agents.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
+       }
+
+       agent_show_requested(a, 0);
+
+       return CLI_SUCCESS;
+}
+
+static char *agent_handle_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct agent_pvt *agent;
+       struct ast_channel *logged;
+       struct ast_str *out = ast_str_alloca(4096);
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "agent show";
+               e->usage =
+                       "Usage: agent show <agent-id>\n"
+                       "       Show information about the <agent-id> agent\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 2) {
+                       return complete_agent(a->word, a->n);
+               }
+               return NULL;
+       }
+
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
+       }
+
+       agent = ao2_find(agents, a->argv[2], OBJ_KEY);
+       if (!agent) {
+               ast_cli(a->fd, "Agent '%s' not found\n", a->argv[2]);
+               return CLI_SUCCESS;
+       }
+
+       agent_lock(agent);
+       logged = agent_lock_logged(agent);
+       ast_str_set(&out, 0, "Id: %s\n", agent->username);
+       ast_str_append(&out, 0, "Name: %s\n", agent->cfg->full_name);
+       ast_str_append(&out, 0, "Beep: %s\n", agent->cfg->beep_sound);
+       ast_str_append(&out, 0, "MOH: %s\n", agent->cfg->moh);
+       ast_str_append(&out, 0, "RecordCalls: %s\n", AST_CLI_YESNO(agent->cfg->record_agent_calls));
+       ast_str_append(&out, 0, "State: %s\n", ast_devstate_str(agent->devstate));
+       if (logged) {
+               const char *talking_with;
+
+               ast_str_append(&out, 0, "LoggedInChannel: %s\n", ast_channel_name(logged));
+               ast_str_append(&out, 0, "LoggedInTime: %ld\n", (long) agent->login_start);
+               talking_with = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
+               if (!ast_strlen_zero(talking_with)) {
+                       ast_str_append(&out, 0, "TalkingWith: %s\n", talking_with);
+                       ast_str_append(&out, 0, "CallStarted: %ld\n", (long) agent->call_start);
+               }
+               ast_channel_unlock(logged);
+               ast_channel_unref(logged);
+       }
+       agent_unlock(agent);
+       ao2_ref(agent, -1);
+
+       ast_cli(a->fd, "%s", ast_str_buffer(out));
+
+       return CLI_SUCCESS;
+}
+
+static char *agent_handle_logoff_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "agent logoff";
+               e->usage =
+                       "Usage: agent logoff <agent-id> [soft]\n"
+                       "       Sets an agent as no longer logged in.\n"
+                       "       If 'soft' is specified, do not hangup existing calls.\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 2) {
+                       return complete_agent_logoff(a->word, a->n);
+               } else if (a->pos == 3 && a->n == 0
+                       && (ast_strlen_zero(a->word)
+                               || !strncasecmp("soft", a->word, strlen(a->word)))) {
+                       return ast_strdup("soft");
+               }
+               return NULL;
+       }
+
+       if (a->argc < 3 || 4 < a->argc) {
+               return CLI_SHOWUSAGE;
+       }
+       if (a->argc == 4 && strcasecmp(a->argv[3], "soft")) {
+               return CLI_SHOWUSAGE;
+       }
+
+       if (!agent_logoff_request(a->argv[2], a->argc == 4)) {
+               ast_cli(a->fd, "Logging out %s\n", a->argv[2]);
+       }
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_agents[] = {
+       AST_CLI_DEFINE(agent_handle_show_online, "Show status of online agents"),
+       AST_CLI_DEFINE(agent_handle_show_all, "Show status of all agents"),
+       AST_CLI_DEFINE(agent_handle_show_specific, "Show information about an agent"),
+       AST_CLI_DEFINE(agent_handle_logoff_cmd, "Sets an agent offline"),
+};
+
+static int action_agents(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       char id_text[AST_MAX_BUF];
+       struct ao2_iterator iter;
+       struct agent_pvt *agent;
+       struct ast_str *out = ast_str_alloca(4096);
+
+       if (!ast_strlen_zero(id)) {
+               snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+       } else {
+               id_text[0] = '\0';
+       }
+       astman_send_ack(s, m, "Agents will follow");
+
+       iter = ao2_iterator_init(agents, 0);
+       for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
+               struct ast_party_id party_id;
+               struct ast_channel *logged;
+               const char *login_chan;
+               const char *talking_to;
+               const char *talking_to_chan;
+               const char *status;
+               time_t login_start;
+
+               agent_lock(agent);
+               logged = agent_lock_logged(agent);
+
+               /*
+                * Status Values:
+                * AGENT_LOGGEDOFF - Agent isn't logged in
+                * AGENT_IDLE      - Agent is logged in, and waiting for call
+                * AGENT_ONCALL    - Agent is logged in, and on a call
+                * AGENT_UNKNOWN   - Don't know anything about agent. Shouldn't ever get this.
+                */
+
+               if (logged) {
+                       login_chan = ast_channel_name(logged);
+                       login_start = agent->login_start;
+                       talking_to_chan = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
+                       if (!ast_strlen_zero(talking_to_chan)) {
+                               party_id = ast_channel_connected_effective_id(logged);
+                               talking_to = S_COR(party_id.number.valid, party_id.number.str, "n/a");
+                               status = "AGENT_ONCALL";
+                       } else {
+                               talking_to = "n/a";
+                               talking_to_chan = "n/a";
+                               status = "AGENT_IDLE";
+                       }
+               } else {
+                       login_chan = "n/a";
+                       login_start = 0;
+                       talking_to = "n/a";
+                       talking_to_chan = "n/a";
+                       status = "AGENT_LOGGEDOFF";
+               }
+
+               ast_str_set(&out, 0, "Agent: %s\r\n", agent->username);
+               ast_str_append(&out, 0, "Name: %s\r\n", S_OR(agent->cfg->full_name, "None"));
+               ast_str_append(&out, 0, "Status: %s\r\n", status);
+               ast_str_append(&out, 0, "LoggedInChan: %s\r\n", login_chan);
+               ast_str_append(&out, 0, "LoggedInTime: %ld\r\n", (long) login_start);
+               ast_str_append(&out, 0, "TalkingTo: %s\r\n", talking_to);
+               ast_str_append(&out, 0, "TalkingToChan: %s\r\n", talking_to_chan);
+
+               if (logged) {
+                       ast_channel_unlock(logged);
+                       ast_channel_unref(logged);
+               }
+               agent_unlock(agent);
+
+               astman_append(s, "Event: Agents\r\n"
+                       "%s%s\r\n",
+                       ast_str_buffer(out), id_text);
+       }
+       ao2_iterator_destroy(&iter);
+
+       astman_append(s, "Event: AgentsComplete\r\n"
+               "%s"
+               "\r\n", id_text);
+       return 0;
+}
+
+static int action_agent_logoff(struct mansession *s, const struct message *m)
+{
+       const char *agent = astman_get_header(m, "Agent");
+       const char *soft_s = astman_get_header(m, "Soft"); /* "true" is don't hangup */
+
+       if (ast_strlen_zero(agent)) {
+               astman_send_error(s, m, "No agent specified");
+               return 0;
+       }
+
+       if (!agent_logoff_request(agent, ast_true(soft_s))) {
+               astman_send_ack(s, m, "Agent logged out");
+       } else {
+               astman_send_error(s, m, "No such agent");
+       }
+
+       return 0;
+}
+
+static int unload_module(void)
+{
+       struct ast_bridge *holding;
+
+       /* Unregister dialplan applications */
+       ast_unregister_application(app_agent_login);
+       ast_unregister_application(app_agent_request);
+
+       /* Unregister dialplan functions */
+       ast_custom_function_unregister(&agent_function);
+
+       /* Unregister manager command */
+       ast_manager_unregister("Agents");
+       ast_manager_unregister("AgentLogoff");
+
+       /* Unregister CLI commands */
+       ast_cli_unregister_multiple(cli_agents, ARRAY_LEN(cli_agents));
+
+       ast_devstate_prov_del("Agent");
+
+       /* Destroy agent holding bridge. */
+       holding = ao2_global_obj_replace(agent_holding, NULL);
+       if (holding) {
+               ast_bridge_destroy(holding);
+       }
+
+       destroy_config();
+       ao2_ref(agents, -1);
+       agents = NULL;
+       return 0;
+}
+
+static int load_module(void)
+{
+       int res = 0;
+
+       agents = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
+               AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, agent_pvt_sort_cmp, agent_pvt_cmp);
+       if (!agents) {
+               return AST_MODULE_LOAD_FAILURE;
+       }
+       if (load_config()) {
+               ast_log(LOG_ERROR, "Unable to load config. Not loading module.\n");
+               ao2_ref(agents, -1);
+               agents = NULL;
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       /* Init agent holding bridge v_table. */
+       bridging_init_agent_hold();
+
+       /* Setup to provide Agent:agent-id device state. */
+       res |= ast_devstate_prov_add("Agent", agent_pvt_devstate_get);
+
+       /* CLI Commands */
+       res |= ast_cli_register_multiple(cli_agents, ARRAY_LEN(cli_agents));
+
+       /* Manager commands */
+       res |= ast_manager_register_xml("Agents", EVENT_FLAG_AGENT, action_agents);
+       res |= ast_manager_register_xml("AgentLogoff", EVENT_FLAG_AGENT, action_agent_logoff);
+
+       /* Dialplan Functions */
+       res |= ast_custom_function_register(&agent_function);
+
+       /* Dialplan applications */
+       res |= ast_register_application_xml(app_agent_login, agent_login_exec);
+       res |= ast_register_application_xml(app_agent_request, agent_request_exec);
+
+       if (res) {
+               unload_module();
+               return AST_MODULE_LOAD_FAILURE;
+       }
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload(void)
+{
+       if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
+               /* Just keep the config we already have in place. */
+               return -1;
+       }
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call center agent pool applications",
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload,
+       .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
+);
diff --git a/channels/chan_agent.c b/channels/chan_agent.c
deleted file mode 100644 (file)
index 57f0914..0000000
+++ /dev/null
@@ -1,2568 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2012, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-
-/*! \file
- * 
- * \brief Implementation of Agents (proxy channel)
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * This file is the implementation of Agents modules.
- * It is a dynamic module that is loaded by Asterisk. 
- * \par See also
- * \arg \ref Config_agent
- *
- * \ingroup channel_drivers
- */
-/*** MODULEINFO
-        <depend>res_monitor</depend>
-       <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <sys/socket.h>
-#include <fcntl.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/signal.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/sched.h"
-#include "asterisk/io.h"
-#include "asterisk/acl.h"
-#include "asterisk/callerid.h"
-#include "asterisk/file.h"
-#include "asterisk/cli.h"
-#include "asterisk/app.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/manager.h"
-#include "asterisk/features.h"
-#include "asterisk/utils.h"
-#include "asterisk/causes.h"
-#include "asterisk/astdb.h"
-#include "asterisk/devicestate.h"
-#include "asterisk/monitor.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/event.h"
-#include "asterisk/data.h"
-
-/*** DOCUMENTATION
-       <application name="AgentLogin" language="en_US">
-               <synopsis>
-                       Call agent login.
-               </synopsis>
-               <syntax>
-                       <parameter name="AgentNo" />
-                       <parameter name="options">
-                               <optionlist>
-                                       <option name="s">
-                                               <para>silent login - do not announce the login ok segment after
-                                               agent logged on/off</para>
-                                       </option>
-                               </optionlist>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>Asks the agent to login to the system. Always returns <literal>-1</literal>.
-                       While logged in, the agent can receive calls and will hear a <literal>beep</literal>
-                       when a new call comes in. The agent can dump the call by pressing the star key.</para>
-               </description>
-               <see-also>
-                       <ref type="application">Queue</ref>
-                       <ref type="application">AddQueueMember</ref>
-                       <ref type="application">RemoveQueueMember</ref>
-                       <ref type="application">PauseQueueMember</ref>
-                       <ref type="application">UnpauseQueueMember</ref>
-                       <ref type="function">AGENT</ref>
-                       <ref type="filename">agents.conf</ref>
-                       <ref type="filename">queues.conf</ref>
-               </see-also>
-       </application>
-       <application name="AgentMonitorOutgoing" language="en_US">
-               <synopsis>
-                       Record agent's outgoing call.
-               </synopsis>
-               <syntax>
-                       <parameter name="options">
-                               <optionlist>
-                                       <option name="d">
-                                               <para>make the app return <literal>-1</literal> if there is an error condition.</para>
-                                       </option>
-                                       <option name="n">
-                                               <para>don't generate the warnings when there is no callerid or the
-                                               agentid is not known. It's handy if you want to have one context
-                                               for agent and non-agent calls.</para>
-                                       </option>
-                               </optionlist>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>Tries to figure out the id of the agent who is placing outgoing call based on
-                       comparison of the callerid of the current interface and the global variable
-                       placed by the AgentCallbackLogin application. That's why it should be used only
-                       with the AgentCallbackLogin app. Uses the monitoring functions in chan_agent
-                       instead of Monitor application. That has to be configured in the
-                       <filename>agents.conf</filename> file.</para>
-                       <para>Normally the app returns <literal>0</literal> unless the options are passed.</para>
-               </description>
-               <see-also>
-                       <ref type="filename">agents.conf</ref>
-               </see-also>
-       </application>
-       <function name="AGENT" language="en_US">
-               <synopsis>
-                       Gets information about an Agent
-               </synopsis>
-               <syntax argsep=":">
-                       <parameter name="agentid" required="true" />
-                       <parameter name="item">
-                               <para>The valid items to retrieve are:</para>
-                               <enumlist>
-                                       <enum name="status">
-                                               <para>(default) The status of the agent (LOGGEDIN | LOGGEDOUT)</para>
-                                       </enum>
-                                       <enum name="password">
-                                               <para>The password of the agent</para>
-                                       </enum>
-                                       <enum name="name">
-                                               <para>The name of the agent</para>
-                                       </enum>
-                                       <enum name="mohclass">
-                                               <para>MusicOnHold class</para>
-                                       </enum>
-                                       <enum name="channel">
-                                               <para>The name of the active channel for the Agent (AgentLogin)</para>
-                                       </enum>
-                                       <enum name="fullchannel">
-                                               <para>The untruncated name of the active channel for the Agent (AgentLogin)</para>
-                                       </enum>
-                               </enumlist>
-                       </parameter>
-               </syntax>
-               <description></description>
-       </function>
-       <manager name="Agents" language="en_US">
-               <synopsis>
-                       Lists agents and their status.
-               </synopsis>
-               <syntax>
-                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-               </syntax>
-               <description>
-                       <para>Will list info about all possible agents.</para>
-               </description>
-       </manager>
-       <manager name="AgentLogoff" language="en_US">
-               <synopsis>
-                       Sets an agent as no longer logged in.
-               </synopsis>
-               <syntax>
-                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-                       <parameter name="Agent" required="true">
-                               <para>Agent ID of the agent to log off.</para>
-                       </parameter>
-                       <parameter name="Soft">
-                               <para>Set to <literal>true</literal> to not hangup existing calls.</para>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>Sets an agent as no longer logged in.</para>
-               </description>
-       </manager>
- ***/
-
-static const char tdesc[] = "Call Agent Proxy Channel";
-static const char config[] = "agents.conf";
-
-static const char app[] = "AgentLogin";
-static const char app3[] = "AgentMonitorOutgoing";
-
-static char moh[80] = "default";
-
-#define AST_MAX_AGENT  80                          /*!< Agent ID or Password max length */
-#define AST_MAX_BUF    256
-#define AST_MAX_FILENAME_LEN   256
-
-static const char pa_family[] = "Agents";          /*!< Persistent Agents astdb family */
-#define PA_MAX_LEN 2048                             /*!< The maximum length of each persistent member agent database entry */
-
-#define DEFAULT_ACCEPTDTMF '#'
-#define DEFAULT_ENDDTMF '*'
-
-static ast_group_t group;
-static int autologoff;
-static int wrapuptime;
-static int ackcall;
-static int endcall;
-static int autologoffunavail = 0;
-static char acceptdtmf = DEFAULT_ACCEPTDTMF;
-static char enddtmf = DEFAULT_ENDDTMF;
-
-static int maxlogintries = 3;
-static char agentgoodbye[AST_MAX_FILENAME_LEN] = "vm-goodbye";
-
-static int recordagentcalls = 0;
-static char recordformat[AST_MAX_BUF] = "";
-static char recordformatext[AST_MAX_BUF] = "";
-static char urlprefix[AST_MAX_BUF] = "";
-static char savecallsin[AST_MAX_BUF] = "";
-static char beep[AST_MAX_BUF] = "beep";
-
-#define GETAGENTBYCALLERID     "AGENTBYCALLERID"
-
-enum {
-       AGENT_FLAG_ACKCALL = (1 << 0),
-       AGENT_FLAG_AUTOLOGOFF = (1 << 1),
-       AGENT_FLAG_WRAPUPTIME = (1 << 2),
-       AGENT_FLAG_ACCEPTDTMF = (1 << 3),
-       AGENT_FLAG_ENDDTMF = (1 << 4),
-};
-
-/*! \brief Structure representing an agent.  */
-struct agent_pvt {
-       ast_mutex_t lock;              /*!< Channel private lock */
-       int dead;                      /*!< Poised for destruction? */
-       int pending;                   /*!< Not a real agent -- just pending a match */
-       int abouttograb;               /*!< About to grab */
-       int autologoff;                /*!< Auto timeout time */
-       int ackcall;                   /*!< ackcall */
-       int deferlogoff;               /*!< Defer logoff to hangup */
-       char acceptdtmf;
-       char enddtmf;
-       time_t loginstart;             /*!< When agent first logged in (0 when logged off) */
-       time_t start;                  /*!< When call started */
-       struct timeval lastdisc;       /*!< When last disconnected */
-       int wrapuptime;                /*!< Wrapup time in ms */
-       ast_group_t group;             /*!< Group memberships */
-       int acknowledged;              /*!< Acknowledged */
-       char moh[80];                  /*!< Which music on hold */
-       char agent[AST_MAX_AGENT];     /*!< Agent ID */
-       char password[AST_MAX_AGENT];  /*!< Password for Agent login */
-       char name[AST_MAX_AGENT];
-       int app_lock_flag;
-       ast_cond_t app_complete_cond;
-       ast_cond_t login_wait_cond;
-       int app_sleep_cond;            /*!< Non-zero if the login app should sleep. */
-       struct ast_channel *owner;     /*!< Agent */
-       struct ast_channel *chan;      /*!< Channel we use */
-       unsigned int flags;            /*!< Flags show if settings were applied with channel vars */
-       AST_LIST_ENTRY(agent_pvt) list;/*!< Next Agent in the linked list. */
-};
-
-#define DATA_EXPORT_AGENT(MEMBER)                              \
-       MEMBER(agent_pvt, autologoff, AST_DATA_INTEGER)         \
-       MEMBER(agent_pvt, ackcall, AST_DATA_BOOLEAN)            \
-       MEMBER(agent_pvt, deferlogoff, AST_DATA_BOOLEAN)        \
-       MEMBER(agent_pvt, wrapuptime, AST_DATA_MILLISECONDS)    \
-       MEMBER(agent_pvt, acknowledged, AST_DATA_BOOLEAN)       \
-       MEMBER(agent_pvt, name, AST_DATA_STRING)                \
-       MEMBER(agent_pvt, password, AST_DATA_PASSWORD)          \
-       MEMBER(agent_pvt, acceptdtmf, AST_DATA_CHARACTER)
-
-AST_DATA_STRUCTURE(agent_pvt, DATA_EXPORT_AGENT);
-
-static AST_LIST_HEAD_STATIC(agents, agent_pvt);        /*!< Holds the list of agents (loaded form agents.conf). */
-
-#define CHECK_FORMATS(ast, p) do { \
-       if (p->chan) {\
-               if (!(ast_format_cap_identical(ast_channel_nativeformats(ast), ast_channel_nativeformats(p->chan)))) { \
-                       char tmp1[256], tmp2[256]; \
-                       ast_debug(1, "Native formats changing from '%s' to '%s'\n", ast_getformatname_multiple(tmp1, sizeof(tmp1), ast_channel_nativeformats(ast)), ast_getformatname_multiple(tmp2, sizeof(tmp2), ast_channel_nativeformats(p->chan))); \
-                       /* Native formats changed, reset things */ \
-                       ast_format_cap_copy(ast_channel_nativeformats(ast), ast_channel_nativeformats(p->chan)); \
-                       ast_debug(1, "Resetting read to '%s' and write to '%s'\n", ast_getformatname(ast_channel_readformat(ast)), ast_getformatname(ast_channel_writeformat(ast)));\
-                       ast_set_read_format(ast, ast_channel_readformat(ast)); \
-                       ast_set_write_format(ast, ast_channel_writeformat(ast)); \
-               } \
-               if ((ast_format_cmp(ast_channel_readformat(p->chan), ast_channel_rawreadformat(ast)) != AST_FORMAT_CMP_EQUAL) && !ast_channel_generator(p->chan))  \
-                       ast_set_read_format(p->chan, ast_channel_rawreadformat(ast)); \
-               if ((ast_format_cmp(ast_channel_writeformat(p->chan), ast_channel_rawwriteformat(ast)) != AST_FORMAT_CMP_EQUAL) && !ast_channel_generator(p->chan)) \
-                       ast_set_write_format(p->chan, ast_channel_rawwriteformat(ast)); \
-       } \
-} while(0)
-
-/*! \brief Cleanup moves all the relevant FD's from the 2nd to the first, but retains things
-   properly for a timingfd XXX This might need more work if agents were logged in as agents or other
-   totally impractical combinations XXX */
-
-#define CLEANUP(ast, p) do { \
-       int x; \
-       if (p->chan) { \
-               for (x = 0; x < AST_MAX_FDS; x++) { \
-                       if (x != AST_TIMING_FD) { \
-                               ast_channel_set_fd(ast, x, ast_channel_fd(p->chan, x)); \
-                       } \
-               } \
-               ast_channel_set_fd(ast, AST_AGENT_FD, ast_channel_fd(p->chan, AST_TIMING_FD)); \
-       } \
-} while(0)
-
-/*--- Forward declarations */
-static struct ast_channel *agent_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
-static int agent_devicestate(const char *data);
-static int agent_digit_begin(struct ast_channel *ast, char digit);
-static int agent_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
-static int agent_call(struct ast_channel *ast, const char *dest, int timeout);
-static int agent_hangup(struct ast_channel *ast);
-static int agent_answer(struct ast_channel *ast);
-static struct ast_frame *agent_read(struct ast_channel *ast);
-static int agent_write(struct ast_channel *ast, struct ast_frame *f);
-static int agent_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
-static int agent_sendtext(struct ast_channel *ast, const char *text);
-static int agent_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
-static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
-static struct ast_channel *agent_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
-static char *complete_agent_logoff_cmd(const char *line, const char *word, int pos, int state);
-static struct ast_channel* agent_get_base_channel(struct ast_channel *chan);
-static int agent_logoff(const char *agent, int soft);
-
-/* BUGBUG This channel driver is totally hosed until it is rewritten. */
-/*! \brief Channel interface description for PBX integration */
-static struct ast_channel_tech agent_tech = {
-       .type = "Agent",
-       .description = tdesc,
-       .requester = agent_request,
-       .devicestate = agent_devicestate,
-       .send_digit_begin = agent_digit_begin,
-       .send_digit_end = agent_digit_end,
-       .call = agent_call,
-       .hangup = agent_hangup,
-       .answer = agent_answer,
-       .read = agent_read,
-       .write = agent_write,
-       .write_video = agent_write,
-       .send_html = agent_sendhtml,
-       .send_text = agent_sendtext,
-       .exception = agent_read,
-       .indicate = agent_indicate,
-       .fixup = agent_fixup,
-       .bridged_channel = agent_bridgedchannel,
-       .get_base_channel = agent_get_base_channel,
-};
-
-/*!
- * \brief Locks the owning channel for a LOCKED pvt while obeying locking order. The pvt
- * must enter this function locked and will be returned locked, but this function will
- * unlock the pvt for a short time, so it can't be used while expecting the pvt to remain
- * static. If function returns a non NULL channel, it will need to be unlocked and
- * unrefed once it is no longer needed.
- *
- * \param pvt Pointer to the LOCKED agent_pvt for which the owner is needed
- *     locked channel which owns the pvt at the time of completion. NULL if not available.
- */
-static struct ast_channel *agent_lock_owner(struct agent_pvt *pvt)
-{
-       struct ast_channel *owner;
-
-       for (;;) {
-               if (!pvt->owner) { /* No owner. Nothing to do. */
-                       return NULL;
-               }
-
-               /* If we don't ref the owner, it could be killed when we unlock the pvt. */
-               owner = ast_channel_ref(pvt->owner);
-
-               /* Locking order requires us to lock channel, then pvt. */
-               ast_mutex_unlock(&pvt->lock);
-               ast_channel_lock(owner);
-               ast_mutex_lock(&pvt->lock);
-
-               /* Check if owner changed during pvt unlock period */
-               if (owner != pvt->owner) { /* Channel changed. Unref and do another pass. */
-                       ast_channel_unlock(owner);
-                       owner = ast_channel_unref(owner);
-               } else { /* Channel stayed the same. Return it. */
-                       return owner;
-               }
-       }
-}
-
-/*!
- * \internal
- * \brief Destroy an agent pvt struct.
- *
- * \param doomed Agent pvt to destroy.
- *
- * \return Nothing
- */
-static void agent_pvt_destroy(struct agent_pvt *doomed)
-{
-       ast_mutex_destroy(&doomed->lock);
-       ast_cond_destroy(&doomed->app_complete_cond);
-       ast_cond_destroy(&doomed->login_wait_cond);
-       ast_free(doomed);
-}
-
-/*!
- * Adds an agent to the global list of agents.
- *
- * \param agent A string with the username, password and real name of an agent. As defined in agents.conf. Example: "13,169,John Smith"
- * \param pending If it is pending or not.
- * @return The just created agent.
- * \sa agent_pvt, agents.
- */
-static struct agent_pvt *add_agent(const char *agent, int pending)
-{
-       char *parse;
-       AST_DECLARE_APP_ARGS(args,
-               AST_APP_ARG(agt);
-               AST_APP_ARG(password);
-               AST_APP_ARG(name);
-       );
-       char *password = NULL;
-       char *name = NULL;
-       char *agt = NULL;
-       struct agent_pvt *p;
-
-       parse = ast_strdupa(agent);
-
-       /* Extract username (agt), password and name from agent (args). */
-       AST_STANDARD_APP_ARGS(args, parse);
-
-       if(args.argc == 0) {
-               ast_log(LOG_WARNING, "A blank agent line!\n");
-               return NULL;
-       }
-
-       if(ast_strlen_zero(args.agt) ) {
-               ast_log(LOG_WARNING, "An agent line with no agentid!\n");
-               return NULL;
-       } else
-               agt = args.agt;
-
-       if(!ast_strlen_zero(args.password)) {
-               password = args.password;
-               while (*password && *password < 33) password++;
-       }
-       if(!ast_strlen_zero(args.name)) {
-               name = args.name;
-               while (*name && *name < 33) name++;
-       }
-
-       if (!pending) {
-               /* Are we searching for the agent here ? To see if it exists already ? */
-               AST_LIST_TRAVERSE(&agents, p, list) {
-                       if (!strcmp(p->agent, agt)) {
-                               break;
-                       }
-               }
-       } else {
-               p = NULL;
-       }
-       if (!p) {
-               // Build the agent.
-               if (!(p = ast_calloc(1, sizeof(*p))))
-                       return NULL;
-               ast_copy_string(p->agent, agt, sizeof(p->agent));
-               ast_mutex_init(&p->lock);
-               ast_cond_init(&p->app_complete_cond, NULL);
-               ast_cond_init(&p->login_wait_cond, NULL);
-               p->app_lock_flag = 0;
-               p->app_sleep_cond = 1;
-               p->group = group;
-               p->pending = pending;
-               AST_LIST_INSERT_TAIL(&agents, p, list);
-       }
-       
-       ast_copy_string(p->password, password ? password : "", sizeof(p->password));
-       ast_copy_string(p->name, name ? name : "", sizeof(p->name));
-       ast_copy_string(p->moh, moh, sizeof(p->moh));
-       if (!ast_test_flag(p, AGENT_FLAG_ACKCALL)) {
-               p->ackcall = ackcall;
-       }
-       if (!ast_test_flag(p, AGENT_FLAG_AUTOLOGOFF)) {
-               p->autologoff = autologoff;
-       }
-       if (!ast_test_flag(p, AGENT_FLAG_ACCEPTDTMF)) {
-               p->acceptdtmf = acceptdtmf;
-       }
-       if (!ast_test_flag(p, AGENT_FLAG_ENDDTMF)) {
-               p->enddtmf = enddtmf;
-       }
-
-       /* If someone reduces the wrapuptime and reloads, we want it
-        * to change the wrapuptime immediately on all calls */
-       if (!ast_test_flag(p, AGENT_FLAG_WRAPUPTIME) && p->wrapuptime > wrapuptime) {
-               struct timeval now = ast_tvnow();
-               /* XXX check what is this exactly */
-
-               /* We won't be pedantic and check the tv_usec val */
-               if (p->lastdisc.tv_sec > (now.tv_sec + wrapuptime/1000)) {
-                       p->lastdisc.tv_sec = now.tv_sec + wrapuptime/1000;
-                       p->lastdisc.tv_usec = now.tv_usec;
-               }
-       }
-       p->wrapuptime = wrapuptime;
-
-       if (pending)
-               p->dead = 1;
-       else
-               p->dead = 0;
-       return p;
-}
-
-/*!
- * Deletes an agent after doing some clean up.
- * Further documentation: How safe is this function ? What state should the agent be to be cleaned.
- *
- * \warning XXX This function seems to be very unsafe.
- * Potential for double free and use after free among other
- * problems.
- *
- * \param p Agent to be deleted.
- * \returns Always 0.
- */
-static int agent_cleanup(struct agent_pvt *p)
-{
-       struct ast_channel *chan;
-
-       ast_mutex_lock(&p->lock);
-       chan = p->owner;
-       p->owner = NULL;
-       /* Release ownership of the agent to other threads (presumably running the login app). */
-       p->app_sleep_cond = 1;
-       p->app_lock_flag = 0;
-       ast_cond_signal(&p->app_complete_cond);
-       if (chan) {
-               ast_channel_tech_pvt_set(chan, NULL);
-               chan = ast_channel_release(chan);
-       }
-       if (p->dead) {
-               ast_mutex_unlock(&p->lock);
-               agent_pvt_destroy(p);
-       } else {
-               ast_mutex_unlock(&p->lock);
-       }
-       return 0;
-}
-
-static int agent_answer(struct ast_channel *ast)
-{
-       ast_log(LOG_WARNING, "Huh?  Agent is being asked to answer?\n");
-       return -1;
-}
-
-static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p, int needlock)
-{
-       char tmp[AST_MAX_BUF], tmp2[AST_MAX_BUF], *pointer;
-       char filename[AST_MAX_BUF];
-       int res = -1;
-       if (!p)
-               return -1;
-       if (!ast_channel_monitor(ast)) {
-               snprintf(filename, sizeof(filename), "agent-%s-%s",p->agent, ast_channel_uniqueid(ast));
-               /* substitute . for - */
-               if ((pointer = strchr(filename, '.')))
-                       *pointer = '-';
-               snprintf(tmp, sizeof(tmp), "%s%s", savecallsin, filename);
-               ast_monitor_start(ast, recordformat, tmp, needlock, X_REC_IN | X_REC_OUT);
-               ast_monitor_setjoinfiles(ast, 1);
-               snprintf(tmp2, sizeof(tmp2), "%s%s.%s", urlprefix, filename, recordformatext);
-#if 0
-               ast_verbose("name is %s, link is %s\n",tmp, tmp2);
-#endif
-               ast_cdr_setuserfield(ast_channel_name(ast), tmp2);
-               res = 0;
-       } else
-               ast_log(LOG_ERROR, "Recording already started on that call.\n");
-       return res;
-}
-
-static int agent_start_monitoring(struct ast_channel *ast, int needlock)
-{
-       return __agent_start_monitoring(ast, ast_channel_tech_pvt(ast), needlock);
-}
-
-static struct ast_frame *agent_read(struct ast_channel *ast)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       struct ast_frame *f = NULL;
-       static struct ast_frame answer_frame = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
-       int cur_time = time(NULL);
-       struct ast_channel *owner;
-
-       ast_mutex_lock(&p->lock);
-       owner = agent_lock_owner(p);
-
-       CHECK_FORMATS(ast, p);
-       if (!p->start) {
-               p->start = cur_time;
-       }
-       if (p->chan) {
-               ast_copy_flags(ast_channel_flags(p->chan), ast_channel_flags(ast), AST_FLAG_EXCEPTION);
-               ast_channel_fdno_set(p->chan, (ast_channel_fdno(ast) == AST_AGENT_FD) ? AST_TIMING_FD : ast_channel_fdno(ast));
-               f = ast_read(p->chan);
-               ast_channel_fdno_set(ast, -1);
-       } else
-               f = &ast_null_frame;
-       if (f) {
-               /* if acknowledgement is not required, and the channel is up, we may have missed
-                       an AST_CONTROL_ANSWER (if there was one), so mark the call acknowledged anyway */
-               if (!p->ackcall && !p->acknowledged && p->chan && (ast_channel_state(p->chan) == AST_STATE_UP)) {
-                       p->acknowledged = 1;
-               }
-
-               if (!p->acknowledged) {
-                       int howlong = cur_time - p->start;
-                       if (p->autologoff && (howlong >= p->autologoff)) {
-                               ast_log(LOG_NOTICE, "Agent '%s' didn't answer/confirm within %d seconds (waited %d)\n", p->name, p->autologoff, howlong);
-                               if (owner || p->chan) {
-                                       if (owner) {
-                                               ast_softhangup(owner, AST_SOFTHANGUP_EXPLICIT);
-                                               ast_channel_unlock(owner);
-                                               owner = ast_channel_unref(owner);
-                                       }
-
-                                       while (p->chan && ast_channel_trylock(p->chan)) {
-                                               DEADLOCK_AVOIDANCE(&p->lock);
-                                       }
-                                       if (p->chan) {
-                                               ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
-                                               ast_channel_unlock(p->chan);
-                                       }
-                               }
-                       }
-               }
-               switch (f->frametype) {
-               case AST_FRAME_CONTROL:
-                       if (f->subclass.integer == AST_CONTROL_ANSWER) {
-                               if (p->ackcall) {
-                                       ast_verb(3, "%s answered, waiting for '%c' to acknowledge\n", ast_channel_name(p->chan), p->acceptdtmf);
-                                       /* Don't pass answer along */
-                                       ast_frfree(f);
-                                       f = &ast_null_frame;
-                               } else {
-                                       p->acknowledged = 1;
-                                       /* Use the builtin answer frame for the 
-                                          recording start check below. */
-                                       ast_frfree(f);
-                                       f = &answer_frame;
-                               }
-                       }
-                       break;
-               case AST_FRAME_DTMF_BEGIN:
-                       /*ignore DTMF begin's as it can cause issues with queue announce files*/
-                       if((!p->acknowledged && f->subclass.integer == p->acceptdtmf) || (f->subclass.integer == p->enddtmf && endcall)){
-                               ast_frfree(f);
-                               f = &ast_null_frame;
-                       }
-                       break;
-               case AST_FRAME_DTMF_END:
-                       if (!p->acknowledged && (f->subclass.integer == p->acceptdtmf)) {
-                               if (p->chan) {
-                                       ast_verb(3, "%s acknowledged\n", ast_channel_name(p->chan));
-                               }
-                               p->acknowledged = 1;
-                               ast_frfree(f);
-                               f = &answer_frame;
-                       } else if (f->subclass.integer == p->enddtmf && endcall) {
-                               /* terminates call */
-                               ast_frfree(f);
-                               f = NULL;
-                       }
-                       break;
-               case AST_FRAME_VOICE:
-               case AST_FRAME_VIDEO:
-                       /* don't pass voice or video until the call is acknowledged */
-                       if (!p->acknowledged) {
-                               ast_frfree(f);
-                               f = &ast_null_frame;
-                       }
-               default:
-                       /* pass everything else on through */
-                       break;
-               }
-       }
-
-       if (owner) {
-               ast_channel_unlock(owner);
-               owner = ast_channel_unref(owner);
-       }
-
-       CLEANUP(ast,p);
-       if (p->chan && !ast_channel_internal_bridged_channel(p->chan)) {
-               if (strcasecmp(ast_channel_tech(p->chan)->type, "Local")) {
-                       ast_channel_internal_bridged_channel_set(p->chan, ast);
-                       ast_debug(1, "Bridge on '%s' being set to '%s' (3)\n", ast_channel_name(p->chan), ast_channel_name(ast_channel_internal_bridged_channel(p->chan)));
-               }
-       }
-       ast_mutex_unlock(&p->lock);
-       if (recordagentcalls && f == &answer_frame)
-               agent_start_monitoring(ast,0);
-       return f;
-}
-
-static int agent_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       ast_mutex_lock(&p->lock);
-       if (p->chan) 
-               res = ast_channel_sendhtml(p->chan, subclass, data, datalen);
-       ast_mutex_unlock(&p->lock);
-       return res;
-}
-
-static int agent_sendtext(struct ast_channel *ast, const char *text)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       ast_mutex_lock(&p->lock);
-       if (p->chan) 
-               res = ast_sendtext(p->chan, text);
-       ast_mutex_unlock(&p->lock);
-       return res;
-}
-
-static int agent_write(struct ast_channel *ast, struct ast_frame *f)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       CHECK_FORMATS(ast, p);
-       ast_mutex_lock(&p->lock);
-       if (!p->chan) 
-               res = 0;
-       else {
-               if ((f->frametype != AST_FRAME_VOICE) ||
-                   (f->frametype != AST_FRAME_VIDEO) ||
-                   (ast_format_cmp(&f->subclass.format, ast_channel_writeformat(p->chan)) != AST_FORMAT_CMP_NOT_EQUAL)) {
-                       res = ast_write(p->chan, f);
-               } else {
-                       ast_debug(1, "Dropping one incompatible %s frame on '%s' to '%s'\n", 
-                               f->frametype == AST_FRAME_VOICE ? "audio" : "video",
-                               ast_channel_name(ast), ast_channel_name(p->chan));
-                       res = 0;
-               }
-       }
-       CLEANUP(ast, p);
-       ast_mutex_unlock(&p->lock);
-       return res;
-}
-
-static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(newchan);
-       ast_mutex_lock(&p->lock);
-       if (p->owner != oldchan) {
-               ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, p->owner);
-               ast_mutex_unlock(&p->lock);
-               return -1;
-       }
-       p->owner = newchan;
-       ast_mutex_unlock(&p->lock);
-       return 0;
-}
-
-static int agent_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-
-       ast_mutex_lock(&p->lock);
-       if (p->chan && !ast_check_hangup(p->chan)) {
-               ast_channel_unlock(ast);
-               ast_channel_lock(p->chan);
-               res = ast_channel_tech(p->chan)->indicate
-                       ? ast_channel_tech(p->chan)->indicate(p->chan, condition, data, datalen)
-                       : -1;
-               ast_channel_unlock(p->chan);
-               ast_mutex_unlock(&p->lock);
-               ast_channel_lock(ast);
-       } else {
-               ast_mutex_unlock(&p->lock);
-               res = 0;
-       }
-       return res;
-}
-
-static int agent_digit_begin(struct ast_channel *ast, char digit)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       ast_mutex_lock(&p->lock);
-       if (p->chan) {
-               ast_senddigit_begin(p->chan, digit);
-       }
-       ast_mutex_unlock(&p->lock);
-       return 0;
-}
-
-static int agent_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       ast_mutex_lock(&p->lock);
-       if (p->chan) {
-               ast_senddigit_end(p->chan, digit, duration);
-       }
-       ast_mutex_unlock(&p->lock);
-       return 0;
-}
-
-static int agent_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       int res;
-       int newstate=0;
-
-       ast_mutex_lock(&p->lock);
-       p->acknowledged = 0;
-
-       if (p->pending) {
-               ast_log(LOG_DEBUG, "Pretending to dial on pending agent\n");
-               ast_mutex_unlock(&p->lock);
-               ast_setstate(ast, AST_STATE_DIALING);
-               return 0;
-       }
-
-       ast_assert(p->chan != NULL);
-       ast_verb(3, "agent_call, call to agent '%s' call on '%s'\n", p->agent, ast_channel_name(p->chan));
-       ast_debug(3, "Playing beep, lang '%s'\n", ast_channel_language(p->chan));
-
-       ast_mutex_unlock(&p->lock);
-
-       res = ast_streamfile(p->chan, beep, ast_channel_language(p->chan));
-       ast_debug(3, "Played beep, result '%d'\n", res);
-       if (!res) {
-               res = ast_waitstream(p->chan, "");
-               ast_debug(3, "Waited for stream, result '%d'\n", res);
-       }
-       
-       ast_mutex_lock(&p->lock);
-
-       if (!res) {
-               struct ast_format tmpfmt;
-               res = ast_set_read_format_from_cap(p->chan, ast_channel_nativeformats(p->chan));
-               ast_debug(3, "Set read format, result '%d'\n", res);
-               if (res)
-                       ast_log(LOG_WARNING, "Unable to set read format to %s\n", ast_getformatname(&tmpfmt));
-       }
-
-       if (!res) {
-               struct ast_format tmpfmt;
-               res = ast_set_write_format_from_cap(p->chan, ast_channel_nativeformats(p->chan));
-               ast_debug(3, "Set write format, result '%d'\n", res);
-               if (res)
-                       ast_log(LOG_WARNING, "Unable to set write format to %s\n", ast_getformatname(&tmpfmt));
-       }
-       if(!res) {
-               /* Call is immediately up, or might need ack */
-               if (p->ackcall) {
-                       newstate = AST_STATE_RINGING;
-               } else {
-                       newstate = AST_STATE_UP;
-                       if (recordagentcalls)
-                               agent_start_monitoring(ast, 0);
-                       p->acknowledged = 1;
-               }
-       }
-       CLEANUP(ast, p);
-       ast_mutex_unlock(&p->lock);
-       if (newstate)
-               ast_setstate(ast, newstate);
-       return res ? -1 : 0;
-}
-
-/*! \brief return the channel or base channel if one exists.  This function assumes the channel it is called on is already locked */
-struct ast_channel* agent_get_base_channel(struct ast_channel *chan)
-{
-       struct agent_pvt *p;
-       struct ast_channel *base = chan;
-
-       /* chan is locked by the calling function */
-       if (!chan || !ast_channel_tech_pvt(chan)) {
-               ast_log(LOG_ERROR, "whoa, you need a channel (0x%ld) with a tech_pvt (0x%ld) to get a base channel.\n", (long)chan, (chan)?(long)ast_channel_tech_pvt(chan):(long)NULL);
-               return NULL;
-       }
-       p = ast_channel_tech_pvt(chan);
-       if (p->chan) 
-               base = p->chan;
-       return base;
-}
-
-static int agent_hangup(struct ast_channel *ast)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(ast);
-       struct ast_channel *indicate_chan = NULL;
-       char *tmp_moh; /* moh buffer for indicating after unlocking p */
-
-       if (p->pending) {
-               AST_LIST_LOCK(&agents);
-               AST_LIST_REMOVE(&agents, p, list);
-               AST_LIST_UNLOCK(&agents);
-       }
-
-       ast_mutex_lock(&p->lock);
-       p->owner = NULL;
-       ast_channel_tech_pvt_set(ast, NULL);
-       p->acknowledged = 0;
-
-       /* if they really are hung up then set start to 0 so the test
-        * later if we're called on an already downed channel
-        * doesn't cause an agent to be logged out like when
-        * agent_request() is followed immediately by agent_hangup()
-        * as in apps/app_chanisavail.c:chanavail_exec()
-        */
-
-       ast_debug(1, "Hangup called for state %s\n", ast_state2str(ast_channel_state(ast)));
-       p->start = 0;
-       if (p->chan) {
-               ast_channel_internal_bridged_channel_set(p->chan, NULL);
-               /* If they're dead, go ahead and hang up on the agent now */
-               if (p->dead) {
-                       ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
-               } else if (p->loginstart) {
-                       indicate_chan = ast_channel_ref(p->chan);
-                       tmp_moh = ast_strdupa(p->moh);
-               }
-       }
-       ast_mutex_unlock(&p->lock);
-
-       if (indicate_chan) {
-               ast_indicate_data(indicate_chan, AST_CONTROL_HOLD,
-                       S_OR(tmp_moh, NULL),
-                       !ast_strlen_zero(tmp_moh) ? strlen(tmp_moh) + 1 : 0);
-               indicate_chan = ast_channel_unref(indicate_chan);
-       }
-
-       ast_mutex_lock(&p->lock);
-       if (p->abouttograb) {
-               /* Let the "about to grab" thread know this isn't valid anymore, and let it
-                  kill it later */
-               p->abouttograb = 0;
-       } else if (p->dead) {
-               ast_mutex_unlock(&p->lock);
-               agent_pvt_destroy(p);
-               return 0;
-       } else {
-               /* Store last disconnect time */
-               p->lastdisc = ast_tvadd(ast_tvnow(), ast_samp2tv(p->wrapuptime, 1000));
-       }
-
-       /* Release ownership of the agent to other threads (presumably running the login app). */
-       p->app_sleep_cond = 1;
-       p->app_lock_flag = 0;
-       ast_cond_signal(&p->app_complete_cond);
-
-       ast_mutex_unlock(&p->lock);
-       return 0;
-}
-
-static int agent_cont_sleep(void *data)
-{
-       struct agent_pvt *p;
-       int res;
-
-       p = (struct agent_pvt *) data;
-
-       ast_mutex_lock(&p->lock);
-       res = p->app_sleep_cond;
-       if (res && p->lastdisc.tv_sec) {
-               if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0) {
-                       res = 0;
-               }
-       }
-       ast_mutex_unlock(&p->lock);
-
-       if (!res) {
-               ast_debug(5, "agent_cont_sleep() returning %d\n", res);
-       }
-
-       return res;
-}
-
-static int agent_ack_sleep(struct agent_pvt *p)
-{
-       int digit;
-       int to = 1000;
-       struct ast_frame *f;
-       struct timeval start = ast_tvnow();
-       int ms;
-
-       /* Wait a second and look for something */
-       while ((ms = ast_remaining_ms(start, to))) {
-               ms = ast_waitfor(p->chan, ms);
-               if (ms < 0) {
-                       return -1;
-               }
-               if (ms == 0) {
-                       return 0;
-               }
-               f = ast_read(p->chan);
-               if (!f) {
-                       return -1;
-               }
-               if (f->frametype == AST_FRAME_DTMF) {
-                       digit = f->subclass.integer;
-               } else {
-                       digit = 0;
-               }
-               ast_frfree(f);
-               ast_mutex_lock(&p->lock);
-               if (!p->app_sleep_cond) {
-                       ast_mutex_unlock(&p->lock);
-                       return 0;
-               }
-               if (digit == p->acceptdtmf) {
-                       ast_mutex_unlock(&p->lock);
-                       return 1;
-               }
-               if (p->lastdisc.tv_sec) {
-                       if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0) {
-                               ast_mutex_unlock(&p->lock);
-                               return 0;
-                       }
-               }
-               ast_mutex_unlock(&p->lock);
-       }
-       return 0;
-}
-
-static struct ast_channel *agent_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
-{
-       struct agent_pvt *p = ast_channel_tech_pvt(bridge);
-       struct ast_channel *ret = NULL;
-
-       if (p) {
-               if (chan == p->chan)
-                       ret = ast_channel_internal_bridged_channel(bridge);
-               else if (chan == ast_channel_internal_bridged_channel(bridge))
-                       ret = p->chan;
-       }
-
-       ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning '%s'\n", ast_channel_name(chan), ast_channel_name(bridge), ret ? ast_channel_name(ret) : "<none>");
-       return ret;
-}
-
-/*! \brief Create new agent channel */
-static struct ast_channel *agent_new(struct agent_pvt *p, int state, const char *linkedid, struct ast_callid *callid)
-{
-       struct ast_channel *tmp;
-#if 0
-       if (!p->chan) {
-               ast_log(LOG_WARNING, "No channel? :(\n");
-               return NULL;
-       }
-#endif 
-       if (p->pending)
-               tmp = ast_channel_alloc(0, state, 0, 0, "", p->chan ? ast_channel_exten(p->chan):"", p->chan ? ast_channel_context(p->chan):"", linkedid, 0, "Agent/P%s-%d", p->agent, (int) ast_random() & 0xffff);
-       else
-               tmp = ast_channel_alloc(0, state, 0, 0, "", p->chan ? ast_channel_exten(p->chan):"", p->chan ? ast_channel_context(p->chan):"", linkedid, 0, "Agent/%s", p->agent);
-       if (!tmp) {
-               ast_log(LOG_WARNING, "Unable to allocate agent channel structure\n");
-               return NULL;
-       }
-
-       if (callid) {
-               ast_channel_callid_set(tmp, callid);
-       }
-
-       ast_channel_tech_set(tmp, &agent_tech);
-       if (p->chan) {
-               ast_format_cap_copy(ast_channel_nativeformats(tmp), ast_channel_nativeformats(p->chan));
-               ast_format_copy(ast_channel_writeformat(tmp), ast_channel_writeformat(p->chan));
-               ast_format_copy(ast_channel_rawwriteformat(tmp), ast_channel_writeformat(p->chan));
-               ast_format_copy(ast_channel_readformat(tmp), ast_channel_readformat(p->chan));
-               ast_format_copy(ast_channel_rawreadformat(tmp), ast_channel_readformat(p->chan));
-               ast_channel_language_set(tmp, ast_channel_language(p->chan));
-               ast_channel_context_set(tmp, ast_channel_context(p->chan));
-               ast_channel_exten_set(tmp, ast_channel_exten(p->chan));
-               /* XXX Is this really all we copy form the originating channel?? */
-       } else {
-               ast_format_set(ast_channel_writeformat(tmp), AST_FORMAT_SLINEAR, 0);
-               ast_format_set(ast_channel_rawwriteformat(tmp), AST_FORMAT_SLINEAR, 0);
-               ast_format_set(ast_channel_readformat(tmp), AST_FORMAT_SLINEAR, 0);
-               ast_format_set(ast_channel_rawreadformat(tmp), AST_FORMAT_SLINEAR, 0);
-               ast_format_cap_add(ast_channel_nativeformats(tmp), ast_channel_writeformat(tmp));
-       }
-       /* Safe, agentlock already held */
-       ast_channel_tech_pvt_set(tmp, p);
-       p->owner = tmp;
-       ast_channel_priority_set(tmp, 1);
-       return tmp;
-}
-
-
-/*!
- * Read configuration data. The file named agents.conf.
- *
- * \returns Always 0, or so it seems.
- */
-static int read_agent_config(int reload)
-{
-       struct ast_config *cfg;
-       struct ast_config *ucfg;
-       struct ast_variable *v;
-       struct agent_pvt *p;
-       const char *catname;
-       const char *hasagent;
-       int genhasagent;
-       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
-
-       group = 0;
-       autologoff = 0;
-       wrapuptime = 0;
-       ackcall = 0;
-       endcall = 1;
-       cfg = ast_config_load(config, config_flags);
-       if (!cfg) {
-               ast_log(LOG_NOTICE, "No agent configuration found -- agent support disabled\n");
-               return 0;
-       } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
-               return -1;
-       } else if (cfg == CONFIG_STATUS_FILEINVALID) {
-               ast_log(LOG_ERROR, "%s contains a parsing error.  Aborting\n", config);
-               return 0;
-       }
-       if ((ucfg = ast_config_load("users.conf", config_flags))) {
-               if (ucfg == CONFIG_STATUS_FILEUNCHANGED) {
-                       ucfg = NULL;
-               } else if (ucfg == CONFIG_STATUS_FILEINVALID) {
-                       ast_log(LOG_ERROR, "users.conf contains a parsing error.  Aborting\n");
-                       return 0;
-               }
-       }
-
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               p->dead = 1;
-       }
-       strcpy(moh, "default");
-       /* set the default recording values */
-       recordagentcalls = 0;
-       strcpy(recordformat, "wav");
-       strcpy(recordformatext, "wav");
-       urlprefix[0] = '\0';
-       savecallsin[0] = '\0';
-
-       /* Read in the [agents] section */
-       v = ast_variable_browse(cfg, "agents");
-       while(v) {
-               /* Create the interface list */
-               if (!strcasecmp(v->name, "agent")) {
-                       add_agent(v->value, 0);
-               } else if (!strcasecmp(v->name, "group")) {
-                       group = ast_get_group(v->value);
-               } else if (!strcasecmp(v->name, "autologoff")) {
-                       autologoff = atoi(v->value);
-                       if (autologoff < 0)
-                               autologoff = 0;
-               } else if (!strcasecmp(v->name, "ackcall")) {
-                       if (ast_true(v->value) || !strcasecmp(v->value, "always")) {
-                               ackcall = 1;
-                       }
-               } else if (!strcasecmp(v->name, "endcall")) {
-                       endcall = ast_true(v->value);
-               } else if (!strcasecmp(v->name, "acceptdtmf")) {
-                       acceptdtmf = *(v->value);
-                       ast_log(LOG_NOTICE, "Set acceptdtmf to %c\n", acceptdtmf);
-               } else if (!strcasecmp(v->name, "enddtmf")) {
-                       enddtmf = *(v->value);
-               } else if (!strcasecmp(v->name, "wrapuptime")) {
-                       wrapuptime = atoi(v->value);
-                       if (wrapuptime < 0)
-                               wrapuptime = 0;
-               } else if (!strcasecmp(v->name, "maxlogintries") && !ast_strlen_zero(v->value)) {
-                       maxlogintries = atoi(v->value);
-                       if (maxlogintries < 0)
-                               maxlogintries = 0;
-               } else if (!strcasecmp(v->name, "goodbye") && !ast_strlen_zero(v->value)) {
-                       strcpy(agentgoodbye,v->value);
-               } else if (!strcasecmp(v->name, "musiconhold")) {
-                       ast_copy_string(moh, v->value, sizeof(moh));
-               } else if (!strcasecmp(v->name, "autologoffunavail")) {
-                       if (ast_true(v->value))
-                               autologoffunavail = 1;
-                       else
-                               autologoffunavail = 0;
-               } else if (!strcasecmp(v->name, "recordagentcalls")) {
-                       recordagentcalls = ast_true(v->value);
-               } else if (!strcasecmp(v->name, "recordformat")) {
-                       ast_copy_string(recordformat, v->value, sizeof(recordformat));
-                       if (!strcasecmp(v->value, "wav49"))
-                               strcpy(recordformatext, "WAV");
-                       else
-                               ast_copy_string(recordformatext, v->value, sizeof(recordformatext));
-               } else if (!strcasecmp(v->name, "urlprefix")) {
-                       ast_copy_string(urlprefix, v->value, sizeof(urlprefix));
-                       if (urlprefix[strlen(urlprefix) - 1] != '/')
-                               strncat(urlprefix, "/", sizeof(urlprefix) - strlen(urlprefix) - 1);
-               } else if (!strcasecmp(v->name, "savecallsin")) {
-                       if (v->value[0] == '/')
-                               ast_copy_string(savecallsin, v->value, sizeof(savecallsin));
-                       else
-                               snprintf(savecallsin, sizeof(savecallsin) - 2, "/%s", v->value);
-                       if (savecallsin[strlen(savecallsin) - 1] != '/')
-                               strncat(savecallsin, "/", sizeof(savecallsin) - strlen(savecallsin) - 1);
-               } else if (!strcasecmp(v->name, "custom_beep")) {
-                       ast_copy_string(beep, v->value, sizeof(beep));
-               }
-               v = v->next;
-       }
-       if (ucfg) {
-               genhasagent = ast_true(ast_variable_retrieve(ucfg, "general", "hasagent"));
-               catname = ast_category_browse(ucfg, NULL);
-               while(catname) {
-                       if (strcasecmp(catname, "general")) {
-                               hasagent = ast_variable_retrieve(ucfg, catname, "hasagent");
-                               if (ast_true(hasagent) || (!hasagent && genhasagent)) {
-                                       char tmp[256];
-                                       const char *fullname = ast_variable_retrieve(ucfg, catname, "fullname");
-                                       const char *secret = ast_variable_retrieve(ucfg, catname, "secret");
-                                       if (!fullname)
-                                               fullname = "";
-                                       if (!secret)
-                                               secret = "";
-                                       snprintf(tmp, sizeof(tmp), "%s,%s,%s", catname, secret,fullname);
-                                       add_agent(tmp, 0);
-                               }
-                       }
-                       catname = ast_category_browse(ucfg, catname);
-               }
-               ast_config_destroy(ucfg);
-       }
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&agents, p, list) {
-               if (p->dead) {
-                       AST_LIST_REMOVE_CURRENT(list);
-                       /* Destroy if  appropriate */
-                       if (!p->owner) {
-                               if (!p->chan) {
-                                       agent_pvt_destroy(p);
-                               } else {
-                                       /* Cause them to hang up */
-                                       ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
-                               }
-                       }
-               }
-       }
-       AST_LIST_TRAVERSE_SAFE_END;
-       AST_LIST_UNLOCK(&agents);
-       ast_config_destroy(cfg);
-       return 1;
-}
-
-static int check_availability(struct agent_pvt *newlyavailable, int needlock)
-{
-       struct ast_channel *chan=NULL, *parent=NULL;
-       struct agent_pvt *p;
-       int res;
-
-       ast_debug(1, "Checking availability of '%s'\n", newlyavailable->agent);
-       if (needlock)
-               AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               if (p == newlyavailable) {
-                       continue;
-               }
-               ast_mutex_lock(&p->lock);
-               if (!p->abouttograb && p->pending && ((p->group && (newlyavailable->group & p->group)) || !strcmp(p->agent, newlyavailable->agent))) {
-                       ast_debug(1, "Call '%s' looks like a winner for agent '%s'\n", ast_channel_name(p->owner), newlyavailable->agent);
-                       /* We found a pending call, time to merge */
-                       chan = agent_new(newlyavailable, AST_STATE_DOWN, p->owner ? ast_channel_linkedid(p->owner) : NULL, NULL);
-                       parent = p->owner;
-                       p->abouttograb = 1;
-                       ast_mutex_unlock(&p->lock);
-                       break;
-               }
-               ast_mutex_unlock(&p->lock);
-       }
-       if (needlock)
-               AST_LIST_UNLOCK(&agents);
-       if (parent && chan)  {
-               if (newlyavailable->ackcall) {
-                       /* Don't do beep here */
-                       res = 0;
-               } else {
-                       ast_debug(3, "Playing beep, lang '%s'\n", ast_channel_language(newlyavailable->chan));
-                       res = ast_streamfile(newlyavailable->chan, beep, ast_channel_language(newlyavailable->chan));
-                       ast_debug(3, "Played beep, result '%d'\n", res);
-                       if (!res) {
-                               res = ast_waitstream(newlyavailable->chan, "");
-                               ast_debug(1, "Waited for stream, result '%d'\n", res);
-                       }
-               }
-               if (!res) {
-                       /* Note -- parent may have disappeared */
-                       if (p->abouttograb) {
-                               newlyavailable->acknowledged = 1;
-                               /* Safe -- agent lock already held */
-                               ast_setstate(parent, AST_STATE_UP);
-                               ast_setstate(chan, AST_STATE_UP);
-                               ast_channel_context_set(parent, ast_channel_context(chan));
-                               ast_channel_masquerade(parent, chan);
-                               ast_hangup(chan);
-                               p->abouttograb = 0;
-                       } else {
-                               ast_debug(1, "Sneaky, parent disappeared in the mean time...\n");
-                               agent_cleanup(newlyavailable);
-                       }
-               } else {
-                       ast_debug(1, "Ugh...  Agent hung up at exactly the wrong time\n");
-                       agent_cleanup(newlyavailable);
-               }
-       }
-       return 0;
-}
-
-static int check_beep(struct agent_pvt *newlyavailable, int needlock)
-{
-       struct agent_pvt *p;
-       int res=0;
-
-       ast_debug(1, "Checking beep availability of '%s'\n", newlyavailable->agent);
-       if (needlock)
-               AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               if (p == newlyavailable) {
-                       continue;
-               }
-               ast_mutex_lock(&p->lock);
-               if (!p->abouttograb && p->pending && ((p->group && (newlyavailable->group & p->group)) || !strcmp(p->agent, newlyavailable->agent))) {
-                       ast_debug(1, "Call '%s' looks like a would-be winner for agent '%s'\n", ast_channel_name(p->owner), newlyavailable->agent);
-                       ast_mutex_unlock(&p->lock);
-                       break;
-               }
-               ast_mutex_unlock(&p->lock);
-       }
-       if (needlock)
-               AST_LIST_UNLOCK(&agents);
-       if (p) {
-               ast_mutex_unlock(&newlyavailable->lock);
-               ast_debug(3, "Playing beep, lang '%s'\n", ast_channel_language(newlyavailable->chan));
-               res = ast_streamfile(newlyavailable->chan, beep, ast_channel_language(newlyavailable->chan));
-               ast_debug(1, "Played beep, result '%d'\n", res);
-               if (!res) {
-                       res = ast_waitstream(newlyavailable->chan, "");
-                       ast_debug(1, "Waited for stream, result '%d'\n", res);
-               }
-               ast_mutex_lock(&newlyavailable->lock);
-       }
-       return res;
-}
-
-/*! \brief Part of the Asterisk PBX interface */
-static struct ast_channel *agent_request(const char *type, struct ast_format_cap *cap, const struct ast_channel* requestor, const char *data, int *cause)
-{
-       struct agent_pvt *p;
-       struct ast_channel *chan = NULL;
-       const char *s;
-       ast_group_t groupmatch;
-       int groupoff;
-       int waitforagent=0;
-       int hasagent = 0;
-       struct timeval now;
-       struct ast_callid *callid = ast_read_threadstorage_callid();
-
-       s = data;
-       if ((s[0] == '@') && (sscanf(s + 1, "%30d", &groupoff) == 1)) {
-               groupmatch = (1 << groupoff);
-       } else if ((s[0] == ':') && (sscanf(s + 1, "%30d", &groupoff) == 1)) {
-               groupmatch = (1 << groupoff);
-               waitforagent = 1;
-       } else 
-               groupmatch = 0;
-
-       /* Check actual logged in agents first */
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               ast_mutex_lock(&p->lock);
-               if (!p->pending && ((groupmatch && (p->group & groupmatch)) || !strcmp(data, p->agent))) {
-                       if (p->chan) {
-                               hasagent++;
-                       }
-                       now = ast_tvnow();
-                       if (p->loginstart
-                               && (!p->lastdisc.tv_sec || ast_tvdiff_ms(now, p->lastdisc) > 0)) {
-                               p->lastdisc = ast_tv(0, 0);
-                               /* Agent must be registered, but not have any active call, and not be in a waiting state */
-                               if (!p->owner && p->chan) {
-                                       /* Fixed agent */
-                                       chan = agent_new(p, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL, callid);
-                               }
-                               if (chan) {
-                                       ast_mutex_unlock(&p->lock);
-                                       break;
-                               }
-                       }
-               }
-               ast_mutex_unlock(&p->lock);
-       }
-
-       if (!chan && waitforagent) {
-               /* No agent available -- but we're requesting to wait for one.
-                  Allocate a place holder */
-               if (hasagent) {
-                       ast_debug(1, "Creating place holder for '%s'\n", s);
-                       p = add_agent(data, 1);
-                       if (p) {
-                               p->group = groupmatch;
-                               chan = agent_new(p, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL, callid);
-                               if (!chan) {
-                                       AST_LIST_REMOVE(&agents, p, list);
-                                       agent_pvt_destroy(p);
-                               }
-                       }
-               } else {
-                       ast_debug(1, "Not creating place holder for '%s' since nobody logged in\n", s);
-               }
-       }
-       *cause = hasagent ? AST_CAUSE_BUSY : AST_CAUSE_UNREGISTERED;
-       AST_LIST_UNLOCK(&agents);
-
-       if (callid) {
-               callid = ast_callid_unref(callid);
-       }
-
-       if (chan) {
-               ast_mutex_lock(&p->lock);
-               if (p->pending) {
-                       ast_mutex_unlock(&p->lock);
-                       return chan;
-               }
-
-               if (!p->chan) {
-                       ast_debug(1, "Agent disconnected before we could connect the call\n");
-                       ast_mutex_unlock(&p->lock);
-                       ast_hangup(chan);
-                       *cause = AST_CAUSE_UNREGISTERED;
-                       return NULL;
-               }
-
-               /* we need to take control of the channel from the login app
-                * thread */
-               p->app_sleep_cond = 0;
-               p->app_lock_flag = 1;
-               ast_queue_frame(p->chan, &ast_null_frame);
-               ast_cond_wait(&p->login_wait_cond, &p->lock);
-
-               if (!p->chan) {
-                       ast_debug(1, "Agent disconnected while we were connecting the call\n");
-                       ast_mutex_unlock(&p->lock);
-                       ast_hangup(chan);
-                       *cause = AST_CAUSE_UNREGISTERED;
-                       return NULL;
-               }
-
-               ast_indicate(p->chan, AST_CONTROL_UNHOLD);
-               ast_mutex_unlock(&p->lock);
-       }
-
-       return chan;
-}
-
-static force_inline int powerof(unsigned int d)
-{
-       int x = ffs(d);
-
-       if (x)
-               return x - 1;
-
-       return 0;
-}
-
-/*!
- * Lists agents and their status to the Manager API.
- * It is registered on load_module() and it gets called by the manager backend.
- * This function locks both the pvt and the channel that owns it for a while, but
- * does not keep these locks.
- * \param s
- * \param m
- * \returns 
- * \sa action_agent_logoff(), load_module().
- */
-static int action_agents(struct mansession *s, const struct message *m)
-{
-       const char *id = astman_get_header(m,"ActionID");
-       char idText[256] = "";
-       struct agent_pvt *p;
-       char *username = NULL;
-       char *loginChan = NULL;
-       char *talkingto = NULL;
-       char *talkingtoChan = NULL;
-       char *status = NULL;
-       struct ast_channel *bridge;
-
-       if (!ast_strlen_zero(id))
-               snprintf(idText, sizeof(idText) ,"ActionID: %s\r\n", id);
-       astman_send_ack(s, m, "Agents will follow");
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               struct ast_channel *owner;
-               ast_mutex_lock(&p->lock);
-               owner = agent_lock_owner(p);
-
-               /* Status Values:
-                  AGENT_LOGGEDOFF - Agent isn't logged in
-                  AGENT_IDLE      - Agent is logged in, and waiting for call
-                  AGENT_ONCALL    - Agent is logged in, and on a call
-                  AGENT_UNKNOWN   - Don't know anything about agent. Shouldn't ever get this. */
-
-               username = S_OR(p->name, "None");
-
-               /* Set a default status. It 'should' get changed. */
-               status = "AGENT_UNKNOWN";
-
-               if (p->chan) {
-                       loginChan = ast_strdupa(ast_channel_name(p->chan));
-                       if (owner && ast_channel_internal_bridged_channel(owner)) {
-                               talkingto = S_COR(ast_channel_caller(p->chan)->id.number.valid,
-                                       ast_channel_caller(p->chan)->id.number.str, "n/a");
-                               if ((bridge = ast_bridged_channel(owner))) {
-                                       talkingtoChan = ast_strdupa(ast_channel_name(bridge));
-                               } else {
-                                       talkingtoChan = "n/a";
-                               }
-                               status = "AGENT_ONCALL";
-                       } else {
-                               talkingto = "n/a";
-                               talkingtoChan = "n/a";
-                               status = "AGENT_IDLE";
-                       }
-               } else {
-                       loginChan = "n/a";
-                       talkingto = "n/a";
-                       talkingtoChan = "n/a";
-                       status = "AGENT_LOGGEDOFF";
-               }
-
-               if (owner) {
-                       ast_channel_unlock(owner);
-                       owner = ast_channel_unref(owner);
-               }
-
-               astman_append(s, "Event: Agents\r\n"
-                       "Agent: %s\r\n"
-                       "Name: %s\r\n"
-                       "Status: %s\r\n"
-                       "LoggedInChan: %s\r\n"
-                       "LoggedInTime: %d\r\n"
-                       "TalkingTo: %s\r\n"
-                       "TalkingToChan: %s\r\n"
-                       "%s"
-                       "\r\n",
-                       p->agent, username, status, loginChan, (int)p->loginstart, talkingto, talkingtoChan, idText);
-               ast_mutex_unlock(&p->lock);
-       }
-       AST_LIST_UNLOCK(&agents);
-       astman_append(s, "Event: AgentsComplete\r\n"
-               "%s"
-               "\r\n",idText);
-       return 0;
-}
-
-static int agent_logoff(const char *agent, int soft)
-{
-       struct agent_pvt *p;
-       int ret = -1; /* Return -1 if no agent if found */
-
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               if (!strcasecmp(p->agent, agent)) {
-                       ret = 0;
-                       if (p->owner || p->chan) {
-                               if (!soft) {
-                                       struct ast_channel *owner;
-                                       ast_mutex_lock(&p->lock);
-                                       owner = agent_lock_owner(p);
-
-                                       if (owner) {
-                                               ast_softhangup(owner, AST_SOFTHANGUP_EXPLICIT);
-                                               ast_channel_unlock(owner);
-                                               owner = ast_channel_unref(owner);
-                                       }
-
-                                       while (p->chan && ast_channel_trylock(p->chan)) {
-                                               DEADLOCK_AVOIDANCE(&p->lock);
-                                       }
-                                       if (p->chan) {
-                                               ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
-                                               ast_channel_unlock(p->chan);
-                                       }
-
-                                       ast_mutex_unlock(&p->lock);
-                               } else
-                                       p->deferlogoff = 1;
-                       }
-                       break;
-               }
-       }
-       AST_LIST_UNLOCK(&agents);
-
-       return ret;
-}
-
-static char *agent_logoff_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       int ret;
-       const char *agent;
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "agent logoff";
-               e->usage =
-                       "Usage: agent logoff <channel> [soft]\n"
-                       "       Sets an agent as no longer logged in.\n"
-                       "       If 'soft' is specified, do not hangup existing calls.\n";
-               return NULL;
-       case CLI_GENERATE:
-               return complete_agent_logoff_cmd(a->line, a->word, a->pos, a->n); 
-       }
-
-       if (a->argc < 3 || a->argc > 4)
-               return CLI_SHOWUSAGE;
-       if (a->argc == 4 && strcasecmp(a->argv[3], "soft"))
-               return CLI_SHOWUSAGE;
-
-       agent = a->argv[2] + 6;
-       ret = agent_logoff(agent, a->argc == 4);
-       if (ret == 0)
-               ast_cli(a->fd, "Logging out %s\n", agent);
-
-       return CLI_SUCCESS;
-}
-
-/*!
- * Sets an agent as no longer logged in in the Manager API.
- * It is registered on load_module() and it gets called by the manager backend.
- * \param s
- * \param m
- * \returns 
- * \sa action_agents(), load_module().
- */
-static int action_agent_logoff(struct mansession *s, const struct message *m)
-{
-       const char *agent = astman_get_header(m, "Agent");
-       const char *soft_s = astman_get_header(m, "Soft"); /* "true" is don't hangup */
-       int soft;
-       int ret; /* return value of agent_logoff */
-
-       if (ast_strlen_zero(agent)) {
-               astman_send_error(s, m, "No agent specified");
-               return 0;
-       }
-
-       soft = ast_true(soft_s) ? 1 : 0;
-       ret = agent_logoff(agent, soft);
-       if (ret == 0)
-               astman_send_ack(s, m, "Agent logged out");
-       else
-               astman_send_error(s, m, "No such agent");
-
-       return 0;
-}
-
-static char *complete_agent_logoff_cmd(const char *line, const char *word, int pos, int state)
-{
-       char *ret = NULL;
-
-       if (pos == 2) {
-               struct agent_pvt *p;
-               char name[AST_MAX_AGENT];
-               int which = 0, len = strlen(word);
-
-               AST_LIST_LOCK(&agents);
-               AST_LIST_TRAVERSE(&agents, p, list) {
-                       snprintf(name, sizeof(name), "Agent/%s", p->agent);
-                       if (!strncasecmp(word, name, len) && p->loginstart && ++which > state) {
-                               ret = ast_strdup(name);
-                               break;
-                       }
-               }
-               AST_LIST_UNLOCK(&agents);
-       } else if (pos == 3 && state == 0) 
-               return ast_strdup("soft");
-       
-       return ret;
-}
-
-/*!
- * Show agents in cli.
- */
-static char *agents_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       struct agent_pvt *p;
-       char username[AST_MAX_BUF];
-       char location[AST_MAX_BUF] = "";
-       char talkingto[AST_MAX_BUF] = "";
-       char music[AST_MAX_BUF];
-       int count_agents = 0;           /*!< Number of agents configured */
-       int online_agents = 0;          /*!< Number of online agents */
-       int offline_agents = 0;         /*!< Number of offline agents */
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "agent show";
-               e->usage =
-                       "Usage: agent show\n"
-                       "       Provides summary information on agents.\n";
-               return NULL;
-       case CLI_GENERATE:
-               return NULL;
-       }
-
-       if (a->argc != 2)
-               return CLI_SHOWUSAGE;
-
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               struct ast_channel *owner;
-               ast_mutex_lock(&p->lock);
-               owner = agent_lock_owner(p);
-               if (p->pending) {
-                       if (p->group)
-                               ast_cli(a->fd, "-- Pending call to group %d\n", powerof(p->group));
-                       else
-                               ast_cli(a->fd, "-- Pending call to agent %s\n", p->agent);
-               } else {
-                       if (!ast_strlen_zero(p->name))
-                               snprintf(username, sizeof(username), "(%s) ", p->name);
-                       else
-                               username[0] = '\0';
-                       if (p->chan) {
-                               snprintf(location, sizeof(location), "logged in on %s", ast_channel_name(p->chan));
-                               if (owner && ast_bridged_channel(owner)) {
-                                       snprintf(talkingto, sizeof(talkingto), " talking to %s", ast_channel_name(ast_bridged_channel(p->owner)));
-                               } else {
-                                       strcpy(talkingto, " is idle");
-                               }
-                               online_agents++;
-                       } else {
-                               strcpy(location, "not logged in");
-                               talkingto[0] = '\0';
-                               offline_agents++;
-                       }
-                       if (!ast_strlen_zero(p->moh))
-                               snprintf(music, sizeof(music), " (musiconhold is '%s')", p->moh);
-                       ast_cli(a->fd, "%-12.12s %s%s%s%s\n", p->agent, 
-                               username, location, talkingto, music);
-                       count_agents++;
-               }
-
-               if (owner) {
-                       ast_channel_unlock(owner);
-                       owner = ast_channel_unref(owner);
-               }
-               ast_mutex_unlock(&p->lock);
-       }
-       AST_LIST_UNLOCK(&agents);
-       if ( !count_agents ) 
-               ast_cli(a->fd, "No Agents are configured in %s\n",config);
-       else 
-               ast_cli(a->fd, "%d agents configured [%d online , %d offline]\n",count_agents, online_agents, offline_agents);
-       ast_cli(a->fd, "\n");
-                       
-       return CLI_SUCCESS;
-}
-
-
-static char *agents_show_online(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       struct agent_pvt *p;
-       char username[AST_MAX_BUF];
-       char location[AST_MAX_BUF] = "";
-       char talkingto[AST_MAX_BUF] = "";
-       char music[AST_MAX_BUF];
-       int count_agents = 0;           /* Number of agents configured */
-       int online_agents = 0;          /* Number of online agents */
-       int agent_status = 0;           /* 0 means offline, 1 means online */
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "agent show online";
-               e->usage =
-                       "Usage: agent show online\n"
-                       "       Provides a list of all online agents.\n";
-               return NULL;
-       case CLI_GENERATE:
-               return NULL;
-       }
-
-       if (a->argc != 3)
-               return CLI_SHOWUSAGE;
-
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               struct ast_channel *owner;
-
-               agent_status = 0;       /* reset it to offline */
-               ast_mutex_lock(&p->lock);
-               owner = agent_lock_owner(p);
-
-               if (!ast_strlen_zero(p->name))
-                       snprintf(username, sizeof(username), "(%s) ", p->name);
-               else
-                       username[0] = '\0';
-               if (p->chan) {
-                       snprintf(location, sizeof(location), "logged in on %s", ast_channel_name(p->chan));
-                       if (p->owner && ast_bridged_channel(p->owner)) {
-                               snprintf(talkingto, sizeof(talkingto), " talking to %s", ast_channel_name(ast_bridged_channel(p->owner)));
-                       } else {
-                               strcpy(talkingto, " is idle");
-                       }
-                       agent_status = 1;
-                       online_agents++;
-               }
-
-               if (owner) {
-                       ast_channel_unlock(owner);
-                       owner = ast_channel_unref(owner);
-               }
-
-               if (!ast_strlen_zero(p->moh))
-                       snprintf(music, sizeof(music), " (musiconhold is '%s')", p->moh);
-               if (agent_status)
-                       ast_cli(a->fd, "%-12.12s %s%s%s%s\n", p->agent, username, location, talkingto, music);
-               count_agents++;
-               ast_mutex_unlock(&p->lock);
-       }
-       AST_LIST_UNLOCK(&agents);
-       if (!count_agents) 
-               ast_cli(a->fd, "No Agents are configured in %s\n", config);
-       else
-               ast_cli(a->fd, "%d agents online\n", online_agents);
-       ast_cli(a->fd, "\n");
-       return CLI_SUCCESS;
-}
-
-static const char agent_logoff_usage[] =
-"Usage: agent logoff <channel> [soft]\n"
-"       Sets an agent as no longer logged in.\n"
-"       If 'soft' is specified, do not hangup existing calls.\n";
-
-static struct ast_cli_entry cli_agents[] = {
-       AST_CLI_DEFINE(agents_show, "Show status of agents"),
-       AST_CLI_DEFINE(agents_show_online, "Show all online agents"),
-       AST_CLI_DEFINE(agent_logoff_cmd, "Sets an agent offline"),
-};
-
-/*!
- * Called by the AgentLogin application (from the dial plan).
- * 
- * \brief Log in agent application.
- *
- * \param chan
- * \param data
- * \returns
- * \sa agentmonitoroutgoing_exec(), load_module().
- */
-static int login_exec(struct ast_channel *chan, const char *data)
-{
-       int res=0;
-       int tries = 0;
-       int max_login_tries = maxlogintries;
-       struct agent_pvt *p;
-       char user[AST_MAX_AGENT];
-       char pass[AST_MAX_AGENT];
-       char xpass[AST_MAX_AGENT];
-       char *errmsg;
-       char *parse;
-       AST_DECLARE_APP_ARGS(args,
-                            AST_APP_ARG(agent_id);
-                            AST_APP_ARG(options);
-                            AST_APP_ARG(extension);
-               );
-       const char *tmpoptions = NULL;
-       int play_announcement = 1;
-       char agent_goodbye[AST_MAX_FILENAME_LEN];
-
-       user[0] = '\0';
-       xpass[0] = '\0';
-
-       parse = ast_strdupa(data);
-
-       AST_STANDARD_APP_ARGS(args, parse);
-
-       ast_copy_string(agent_goodbye, agentgoodbye, sizeof(agent_goodbye));
-
-       ast_channel_lock(chan);
-       /* Set Channel Specific Login Overrides */
-       if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTLMAXLOGINTRIES"))) {
-               max_login_tries = atoi(pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES"));
-               if (max_login_tries < 0)
-                       max_login_tries = 0;
-               tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES");
-               ast_verb(3, "Saw variable AGENTMAXLOGINTRIES=%s, setting max_login_tries to: %d on Channel '%s'.\n",tmpoptions,max_login_tries,ast_channel_name(chan));
-       }
-       if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"))) {
-               strcpy(agent_goodbye, pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"));
-               tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTGOODBYE");
-               ast_verb(3, "Saw variable AGENTGOODBYE=%s, setting agent_goodbye to: %s on Channel '%s'.\n",tmpoptions,agent_goodbye,ast_channel_name(chan));
-       }
-       ast_channel_unlock(chan);
-       /* End Channel Specific Login Overrides */
-       
-       if (!ast_strlen_zero(args.options)) {
-               if (strchr(args.options, 's')) {
-                       play_announcement = 0;
-               }
-       }
-
-       if (ast_channel_state(chan) != AST_STATE_UP)
-               res = ast_answer(chan);
-       if (!res) {
-               if (!ast_strlen_zero(args.agent_id))
-                       ast_copy_string(user, args.agent_id, AST_MAX_AGENT);
-               else
-                       res = ast_app_getdata(chan, "agent-user", user, sizeof(user) - 1, 0);
-       }
-       while (!res && (max_login_tries==0 || tries < max_login_tries)) {
-               tries++;
-               /* Check for password */
-               AST_LIST_LOCK(&agents);
-               AST_LIST_TRAVERSE(&agents, p, list) {
-                       if (!strcmp(p->agent, user) && !p->pending)
-                               ast_copy_string(xpass, p->password, sizeof(xpass));
-               }
-               AST_LIST_UNLOCK(&agents);
-               if (!res) {
-                       if (!ast_strlen_zero(xpass))
-                               res = ast_app_getdata(chan, "agent-pass", pass, sizeof(pass) - 1, 0);
-                       else
-                               pass[0] = '\0';
-               }
-               errmsg = "agent-incorrect";
-
-#if 0
-               ast_log(LOG_NOTICE, "user: %s, pass: %s\n", user, pass);
-#endif         
-
-               /* Check again for accuracy */
-               AST_LIST_LOCK(&agents);
-               AST_LIST_TRAVERSE(&agents, p, list) {
-                       int unlock_channel = 1;
-
-                       ast_channel_lock(chan);
-                       ast_mutex_lock(&p->lock);
-                       if (!strcmp(p->agent, user) &&
-                           !strcmp(p->password, pass) && !p->pending) {
-
-                               /* Set Channel Specific Agent Overrides */
-                               if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTACKCALL"))) {
-                                       if (ast_true(pbx_builtin_getvar_helper(chan, "AGENTACKCALL"))) {
-                                               p->ackcall = 1;
-                                       } else {
-                                               p->ackcall = 0;
-                                       }
-                                       tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTACKCALL");
-                                       ast_verb(3, "Saw variable AGENTACKCALL=%s, setting ackcall to: %d for Agent '%s'.\n", tmpoptions, p->ackcall, p->agent);
-                                       ast_set_flag(p, AGENT_FLAG_ACKCALL);
-                               } else {
-                                       p->ackcall = ackcall;
-                               }
-                               if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF"))) {
-                                       p->autologoff = atoi(pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF"));
-                                       if (p->autologoff < 0)
-                                               p->autologoff = 0;
-                                       tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF");
-                                       ast_verb(3, "Saw variable AGENTAUTOLOGOFF=%s, setting autologff to: %d for Agent '%s'.\n", tmpoptions, p->autologoff, p->agent);
-                                       ast_set_flag(p, AGENT_FLAG_AUTOLOGOFF);
-                               } else {
-                                       p->autologoff = autologoff;
-                               }
-                               if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME"))) {
-                                       p->wrapuptime = atoi(pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME"));
-                                       if (p->wrapuptime < 0)
-                                               p->wrapuptime = 0;
-                                       tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME");
-                                       ast_verb(3, "Saw variable AGENTWRAPUPTIME=%s, setting wrapuptime to: %d for Agent '%s'.\n", tmpoptions, p->wrapuptime, p->agent);
-                                       ast_set_flag(p, AGENT_FLAG_WRAPUPTIME);
-                               } else {
-                                       p->wrapuptime = wrapuptime;
-                               }
-                               tmpoptions = pbx_builtin_getvar_helper(chan, "AGENTACCEPTDTMF");
-                               if (!ast_strlen_zero(tmpoptions)) {
-                                       p->acceptdtmf = *tmpoptions;
-                                       ast_verb(3, "Saw variable AGENTACCEPTDTMF=%s, setting acceptdtmf to: %c for Agent '%s'.\n", tmpoptions, p->acceptdtmf, p->agent);
-                                       ast_set_flag(p, AGENT_FLAG_ACCEPTDTMF);
-                               }
-                               tmpoptions = pbx_builtin_getvar_helper(chan, "AGENTENDDTMF");
-                               if (!ast_strlen_zero(tmpoptions)) {
-                                       p->enddtmf = *tmpoptions;
-                                       ast_verb(3, "Saw variable AGENTENDDTMF=%s, setting enddtmf to: %c for Agent '%s'.\n", tmpoptions, p->enddtmf, p->agent);
-                                       ast_set_flag(p, AGENT_FLAG_ENDDTMF);
-                               }
-                               ast_channel_unlock(chan);
-                               unlock_channel = 0;
-                               /* End Channel Specific Agent Overrides */
-
-                               if (!p->chan) {
-                                       /* Ensure nobody else can be this agent until we're done. */
-                                       p->chan = chan;
-
-                                       p->acknowledged = 0;
-
-                                       if (!res) {
-                                               struct ast_format tmpfmt;
-                                               res = ast_set_read_format_from_cap(chan, ast_channel_nativeformats(chan));
-                                               if (res) {
-                                                       ast_log(LOG_WARNING, "Unable to set read format to %s\n", ast_getformatname(&tmpfmt));
-                                               }
-                                       }
-                                       if (!res) {
-                                               struct ast_format tmpfmt;
-                                               res = ast_set_write_format_from_cap(chan, ast_channel_nativeformats(chan));
-                                               if (res) {
-                                                       ast_log(LOG_WARNING, "Unable to set write format to %s\n", ast_getformatname(&tmpfmt));
-                                               }
-                                       }
-                                       if (!res && play_announcement == 1) {
-                                               ast_mutex_unlock(&p->lock);
-                                               AST_LIST_UNLOCK(&agents);
-                                               res = ast_streamfile(chan, "agent-loginok", ast_channel_language(chan));
-                                               if (!res) {
-                                                       ast_waitstream(chan, "");
-                                               }
-                                               AST_LIST_LOCK(&agents);
-                                               ast_mutex_lock(&p->lock);
-                                       }
-
-                                       if (!res) {
-                                               long logintime;
-                                               char agent[AST_MAX_AGENT];
-
-                                               snprintf(agent, sizeof(agent), "Agent/%s", p->agent);
-
-                                               /* Login this channel and wait for it to go away */
-                                               ast_indicate_data(chan, AST_CONTROL_HOLD, 
-                                                       S_OR(p->moh, NULL), 
-                                                       !ast_strlen_zero(p->moh) ? strlen(p->moh) + 1 : 0);
-
-                                               /* Must be done after starting HOLD. */
-                                               p->lastdisc = ast_tvnow();
-                                               time(&p->loginstart);
-
-                                               /*** DOCUMENTATION
-                                                       <managerEventInstance>
-                                                               <synopsis>Raised when an Agent has logged in.</synopsis>
-                                                               <syntax>
-                                                                       <parameter name="Agent">
-                                                                               <para>The name of the agent.</para>
-                                                                       </parameter>
-                                                               </syntax>
-                                                               <see-also>
-                                                                       <ref type="application">AgentLogin</ref>
-                                                                       <ref type="managerEvent">Agentlogoff</ref>
-                                                               </see-also>
-                                                       </managerEventInstance>
-                                               ***/
-                                               manager_event(EVENT_FLAG_AGENT, "Agentlogin",
-                                                             "Agent: %s\r\n"
-                                                             "Channel: %s\r\n"
-                                                             "Uniqueid: %s\r\n",
-                                                             p->agent, ast_channel_name(chan), ast_channel_uniqueid(chan));
-                                               ast_queue_log("NONE", ast_channel_uniqueid(chan), agent, "AGENTLOGIN", "%s", ast_channel_name(chan));
-                                               ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", p->agent,
-                                                                   ast_getformatname(ast_channel_readformat(chan)), ast_getformatname(ast_channel_writeformat(chan)));
-
-                                               ast_mutex_unlock(&p->lock);
-                                               AST_LIST_UNLOCK(&agents);
-
-                                               while (res >= 0) {
-                                                       ast_mutex_lock(&p->lock);
-                                                       if (p->deferlogoff) {
-                                                               p->deferlogoff = 0;
-                                                               ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
-                                                               ast_mutex_unlock(&p->lock);
-                                                               break;
-                                                       }
-                                                       ast_mutex_unlock(&p->lock);
-
-                                                       AST_LIST_LOCK(&agents);
-                                                       ast_mutex_lock(&p->lock);
-                                                       if (p->lastdisc.tv_sec) {
-                                                               if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0) {
-                                                                       ast_debug(1, "Wrapup time for %s expired!\n", agent);
-                                                                       p->lastdisc = ast_tv(0, 0);
-                                                                       ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "%s", agent);
-                                                                       if (p->ackcall) {
-                                                                               check_beep(p, 0);
-                                                                       } else {
-                                                                               check_availability(p, 0);
-                                                                       }
-                                                               }
-                                                       }
-                                                       ast_mutex_unlock(&p->lock);
-                                                       AST_LIST_UNLOCK(&agents);
-
-                                                       /* Synchronize channel ownership between call to agent and itself. */
-                                                       ast_mutex_lock(&p->lock);
-                                                       if (p->app_lock_flag) {
-                                                               ast_cond_signal(&p->login_wait_cond);
-                                                               ast_cond_wait(&p->app_complete_cond, &p->lock);
-                                                               if (ast_check_hangup(chan)) {
-                                                                       /* Agent hungup */
-                                                                       ast_mutex_unlock(&p->lock);
-                                                                       break;
-                                                               }
-                                                       }
-                                                       ast_mutex_unlock(&p->lock);
-
-                                                       if (p->ackcall) {
-                                                               res = agent_ack_sleep(p);
-                                                               if (res == 1) {
-                                                                       AST_LIST_LOCK(&agents);
-                                                                       ast_mutex_lock(&p->lock);
-                                                                       check_availability(p, 0);
-                                                                       ast_mutex_unlock(&p->lock);
-                                                                       AST_LIST_UNLOCK(&agents);
-                                                               }
-                                                       } else {
-                                                               res = ast_safe_sleep_conditional( chan, 1000, agent_cont_sleep, p );
-                                                       }
-                                               }
-                                               ast_mutex_lock(&p->lock);
-
-                                               /* Logoff this channel */
-                                               p->chan = NULL;
-                                               logintime = time(NULL) - p->loginstart;
-                                               p->loginstart = 0;
-
-                                               /* Synchronize channel ownership between call to agent and itself. */
-                                               if (p->app_lock_flag) {
-                                                       ast_cond_signal(&p->login_wait_cond);
-                                                       ast_cond_wait(&p->app_complete_cond, &p->lock);
-                                               }
-
-                                               if (p->owner) {
-                                                       ast_log(LOG_WARNING, "Huh?  We broke out when there was still an owner?\n");
-                                               }
-
-                                               p->acknowledged = 0;
-                                               ast_mutex_unlock(&p->lock);
-
-                                               ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "%s", agent);
-                                               /*** DOCUMENTATION
-                                                       <managerEventInstance>
-                                                               <synopsis>Raised when an Agent has logged off.</synopsis>
-                                                               <syntax>
-                                                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='Agentlogin']/managerEventInstance/syntax/parameter[@name='Agent'])" />
-                                                               </syntax>
-                                                               <see-also>
-                                                                       <ref type="managerEvent">Agentlogin</ref>
-                                                               </see-also>
-                                                       </managerEventInstance>
-                                               ***/
-                                               manager_event(EVENT_FLAG_AGENT, "Agentlogoff",
-                                                             "Agent: %s\r\n"
-                                                             "Logintime: %ld\r\n"
-                                                             "Uniqueid: %s\r\n",
-                                                             p->agent, logintime, ast_channel_uniqueid(chan));
-                                               ast_queue_log("NONE", ast_channel_uniqueid(chan), agent, "AGENTLOGOFF", "%s|%ld", ast_channel_name(chan), logintime);
-                                               ast_verb(2, "Agent '%s' logged out\n", p->agent);
-
-                                               /* If there is no owner, go ahead and kill it now */
-                                               if (p->dead && !p->owner) {
-                                                       agent_pvt_destroy(p);
-                                               }
-                                               AST_LIST_LOCK(&agents);
-                                       } else {
-                                               /* Agent hung up before could be logged in. */
-                                               p->chan = NULL;
-
-                                               ast_mutex_unlock(&p->lock);
-                                       }
-                                       res = -1;
-                               } else {
-                                       ast_mutex_unlock(&p->lock);
-                                       errmsg = "agent-alreadyon";
-                               }
-                               break;
-                       }
-                       ast_mutex_unlock(&p->lock);
-                       if (unlock_channel) {
-                               ast_channel_unlock(chan);
-                       }
-               }
-               AST_LIST_UNLOCK(&agents);
-
-               if (!res && (max_login_tries==0 || tries < max_login_tries))
-                       res = ast_app_getdata(chan, errmsg, user, sizeof(user) - 1, 0);
-       }
-               
-       if (!res)
-               res = ast_safe_sleep(chan, 500);
-
-       return -1;
-}
-
-/*!
- *  \brief Called by the AgentMonitorOutgoing application (from the dial plan).
- *
- * \param chan
- * \param data
- * \returns
- * \sa login_exec(), load_module().
- */
-static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data)
-{
-       int exitifnoagentid = 0;
-       int nowarnings = 0;
-       int res = 0;
-       char agent[AST_MAX_AGENT];
-
-       if (data) {
-               if (strchr(data, 'd')) {
-                       exitifnoagentid = 1;
-               }
-               if (strchr(data, 'n')) {
-                       nowarnings = 1;
-               }
-       }
-       if (ast_channel_caller(chan)->id.number.valid
-               && !ast_strlen_zero(ast_channel_caller(chan)->id.number.str)) {
-               const char *tmp;
-               char agentvar[AST_MAX_BUF];
-               snprintf(agentvar, sizeof(agentvar), "%s_%s", GETAGENTBYCALLERID,
-                       ast_channel_caller(chan)->id.number.str);
-               if ((tmp = pbx_builtin_getvar_helper(NULL, agentvar))) {
-                       struct agent_pvt *p;
-                       ast_copy_string(agent, tmp, sizeof(agent));
-                       AST_LIST_LOCK(&agents);
-                       AST_LIST_TRAVERSE(&agents, p, list) {
-                               if (!strcasecmp(p->agent, tmp)) {
-                                       __agent_start_monitoring(chan, p, 1);
-                                       break;
-                               }
-                       }
-                       AST_LIST_UNLOCK(&agents);
-                       
-               } else {
-                       res = -1;
-                       if (!nowarnings)
-                               ast_log(LOG_WARNING, "Couldn't find the global variable %s, so I can't figure out which agent (if it's an agent) is placing outgoing call.\n", agentvar);
-               }
-       } else {
-               res = -1;
-               if (!nowarnings)
-                       ast_log(LOG_WARNING, "There is no callerid on that call, so I can't figure out which agent (if it's an agent) is placing outgoing call.\n");
-       }
-       if (res) {
-               if (exitifnoagentid)
-                       return res;
-       }
-       return 0;
-}
-
-/*! \brief Part of PBX channel interface */
-static int agent_devicestate(const char *data)
-{
-       struct agent_pvt *p;
-       const char *device = data;
-       int res = AST_DEVICE_INVALID;
-
-       if (device[0] == '@' || device[0] == ':') {
-               /* Device state of groups not supported. */
-               return AST_DEVICE_INVALID;
-       }
-
-       /* Want device state of a specific agent. */
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               ast_mutex_lock(&p->lock);
-               if (!p->pending && !strcmp(device, p->agent)) {
-                       if (p->owner) {
-                               res = AST_DEVICE_BUSY;
-                       } else if (p->chan) {
-                               if (p->lastdisc.tv_sec || p->deferlogoff) {
-                                       /* Agent is in wrapup time so unavailable for another call. */
-                                       res = AST_DEVICE_INUSE;
-                               } else {
-                                       res = AST_DEVICE_NOT_INUSE;
-                               }
-                       } else {
-                               res = AST_DEVICE_UNAVAILABLE;
-                       }
-                       ast_mutex_unlock(&p->lock);
-                       break;
-               }
-               ast_mutex_unlock(&p->lock);
-       }
-       AST_LIST_UNLOCK(&agents);
-       return res;
-}
-
-/*!
- * \note This function expects the agent list to be locked
- */
-static struct agent_pvt *find_agent(char *agentid)
-{
-       struct agent_pvt *cur;
-
-       AST_LIST_TRAVERSE(&agents, cur, list) {
-               if (!strcmp(cur->agent, agentid))
-                       break;  
-       }
-
-       return cur;     
-}
-
-static int function_agent(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
-{
-       char *parse;    
-       AST_DECLARE_APP_ARGS(args,
-               AST_APP_ARG(agentid);
-               AST_APP_ARG(item);
-       );
-       char *tmp;
-       struct agent_pvt *agent;
-
-       buf[0] = '\0';
-
-       if (ast_strlen_zero(data)) {
-               ast_log(LOG_WARNING, "The AGENT function requires an argument - agentid!\n");
-               return -1;
-       }
-
-       parse = ast_strdupa(data);
-
-       AST_NONSTANDARD_APP_ARGS(args, parse, ':');
-       if (!args.item)
-               args.item = "status";
-
-       AST_LIST_LOCK(&agents);
-
-       if (!(agent = find_agent(args.agentid))) {
-               AST_LIST_UNLOCK(&agents);
-               ast_log(LOG_WARNING, "Agent '%s' not found!\n", args.agentid);
-               return -1;
-       }
-
-       if (!strcasecmp(args.item, "status")) {
-               char *status = "LOGGEDOUT";
-               if (agent->chan) {
-                       status = "LOGGEDIN";
-               }
-               ast_copy_string(buf, status, len);
-       } else if (!strcasecmp(args.item, "password")) 
-               ast_copy_string(buf, agent->password, len);
-       else if (!strcasecmp(args.item, "name"))
-               ast_copy_string(buf, agent->name, len);
-       else if (!strcasecmp(args.item, "mohclass"))
-               ast_copy_string(buf, agent->moh, len);
-       else if (!strcasecmp(args.item, "channel")) {
-               if (agent->chan) {
-                       ast_channel_lock(agent->chan);
-                       ast_copy_string(buf, ast_channel_name(agent->chan), len);
-                       ast_channel_unlock(agent->chan);
-                       tmp = strrchr(buf, '-');
-                       if (tmp)
-                               *tmp = '\0';
-               } 
-       } else if (!strcasecmp(args.item, "fullchannel")) {
-               if (agent->chan) {
-                       ast_channel_lock(agent->chan);
-                       ast_copy_string(buf, ast_channel_name(agent->chan), len);
-                       ast_channel_unlock(agent->chan);
-               } 
-       } else if (!strcasecmp(args.item, "exten")) {
-               buf[0] = '\0';
-       }
-
-       AST_LIST_UNLOCK(&agents);
-
-       return 0;
-}
-
-static struct ast_custom_function agent_function = {
-       .name = "AGENT",
-       .read = function_agent,
-};
-
-/*!
- * \internal
- * \brief Callback used to generate the agents tree.
- * \param[in] search The search pattern tree.
- * \retval NULL on error.
- * \retval non-NULL The generated tree.
- */
-static int agents_data_provider_get(const struct ast_data_search *search,
-       struct ast_data *data_root)
-{
-       struct agent_pvt *p;
-       struct ast_data *data_agent, *data_channel, *data_talkingto;
-
-       AST_LIST_LOCK(&agents);
-       AST_LIST_TRAVERSE(&agents, p, list) {
-               struct ast_channel *owner;
-
-               data_agent = ast_data_add_node(data_root, "agent");
-               if (!data_agent) {
-                       continue;
-               }
-
-               ast_mutex_lock(&p->lock);
-               owner = agent_lock_owner(p);
-
-               if (!(p->pending)) {
-                       ast_data_add_str(data_agent, "id", p->agent);
-                       ast_data_add_structure(agent_pvt, data_agent, p);
-
-                       ast_data_add_bool(data_agent, "logged", p->chan ? 1 : 0);
-                       if (p->chan) {
-                               data_channel = ast_data_add_node(data_agent, "loggedon");
-                               if (!data_channel) {
-                                       ast_mutex_unlock(&p->lock);
-                                       ast_data_remove_node(data_root, data_agent);
-                                       if (owner) {
-                                               ast_channel_unlock(owner);
-                                               owner = ast_channel_unref(owner);
-                                       }
-                                       continue;
-                               }
-                               ast_channel_data_add_structure(data_channel, p->chan, 0);
-                               if (owner && ast_bridged_channel(owner)) {
-                                       data_talkingto = ast_data_add_node(data_agent, "talkingto");
-                                       if (!data_talkingto) {
-                                               ast_mutex_unlock(&p->lock);
-                                               ast_data_remove_node(data_root, data_agent);
-                                               if (owner) {
-                                                       ast_channel_unlock(owner);
-                                                       owner = ast_channel_unref(owner);
-                                               }
-                                               continue;
-                                       }
-                                       ast_channel_data_add_structure(data_talkingto, ast_bridged_channel(owner), 0);
-                               }
-                       } else {
-                               ast_data_add_node(data_agent, "talkingto");
-                               ast_data_add_node(data_agent, "loggedon");
-                       }
-                       ast_data_add_str(data_agent, "musiconhold", p->moh);
-               }
-
-               if (owner) {
-                       ast_channel_unlock(owner);
-                       owner = ast_channel_unref(owner);
-               }
-
-               ast_mutex_unlock(&p->lock);
-
-               /* if this agent doesn't match remove the added agent. */
-               if (!ast_data_search_match(search, data_agent)) {
-                       ast_data_remove_node(data_root, data_agent);
-               }
-       }
-       AST_LIST_UNLOCK(&agents);
-
-       return 0;
-}
-
-static const struct ast_data_handler agents_data_provider = {
-       .version = AST_DATA_HANDLER_VERSION,
-       .get = agents_data_provider_get
-};
-
-static const struct ast_data_entry agents_data_providers[] = {
-       AST_DATA_ENTRY("asterisk/channel/agent/list", &agents_data_provider),
-};
-
-/*!
- * \brief Initialize the Agents module.
- * This function is being called by Asterisk when loading the module. 
- * Among other things it registers applications, cli commands and reads the cofiguration file.
- *
- * \returns int Always 0.
- */
-static int load_module(void)
-{
-       if (!(agent_tech.capabilities = ast_format_cap_alloc())) {
-               ast_log(LOG_ERROR, "ast_format_cap_alloc_nolock fail.\n");
-               return AST_MODULE_LOAD_FAILURE;
-       }
-       ast_format_cap_add_all(agent_tech.capabilities);
-       /* Make sure we can register our agent channel type */
-       if (ast_channel_register(&agent_tech)) {
-               agent_tech.capabilities = ast_format_cap_destroy(agent_tech.capabilities);
-               ast_log(LOG_ERROR, "Unable to register channel class 'Agent'\n");
-               return AST_MODULE_LOAD_FAILURE;
-       }
-       /* Read in the config */
-       if (!read_agent_config(0)) {
-               agent_tech.capabilities = ast_format_cap_destroy(agent_tech.capabilities);
-               return AST_MODULE_LOAD_DECLINE;
-       }
-       /* Dialplan applications */
-       ast_register_application_xml(app, login_exec);
-       ast_register_application_xml(app3, agentmonitoroutgoing_exec);
-
-       /* data tree */
-       ast_data_register_multiple(agents_data_providers, ARRAY_LEN(agents_data_providers));
-
-       /* Manager commands */
-       ast_manager_register_xml("Agents", EVENT_FLAG_AGENT, action_agents);
-       ast_manager_register_xml("AgentLogoff", EVENT_FLAG_AGENT, action_agent_logoff);
-
-       /* CLI Commands */
-       ast_cli_register_multiple(cli_agents, ARRAY_LEN(cli_agents));
-
-       /* Dialplan Functions */
-       ast_custom_function_register(&agent_function);
-
-       return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int reload(void)
-{
-       return read_agent_config(1);
-}
-
-static int unload_module(void)
-{
-       struct agent_pvt *p;
-       /* First, take us out of the channel loop */
-       ast_channel_unregister(&agent_tech);
-       /* Unregister dialplan functions */
-       ast_custom_function_unregister(&agent_function);        
-       /* Unregister CLI commands */
-       ast_cli_unregister_multiple(cli_agents, ARRAY_LEN(cli_agents));
-       /* Unregister dialplan applications */
-       ast_unregister_application(app);
-       ast_unregister_application(app3);
-       /* Unregister manager command */
-       ast_manager_unregister("Agents");
-       ast_manager_unregister("AgentLogoff");
-       /* Unregister the data tree */
-       ast_data_unregister(NULL);
-       /* Unregister channel */
-       AST_LIST_LOCK(&agents);
-       /* Hangup all interfaces if they have an owner */
-       while ((p = AST_LIST_REMOVE_HEAD(&agents, list))) {
-               if (p->owner)
-                       ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
-               ast_free(p);
-       }
-       AST_LIST_UNLOCK(&agents);
-
-       agent_tech.capabilities = ast_format_cap_destroy(agent_tech.capabilities);
-       return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Agent Proxy Channel",
-               .load = load_module,
-               .unload = unload_module,
-               .reload = reload,
-               .load_pri = AST_MODPRI_CHANNEL_DRIVER,
-               .nonoptreq = "res_monitor",
-              );
index 29e6e07..0db451d 100644 (file)
 ;
-; Agent configuration
+; Agent pool configuration
 ;
 
 [general]
+; The general section of this config is not currently used, but reserved
+; for future use.
 
-[agents]
-;
-; Define maxlogintries to allow agent to try max logins before
-; failed.
-; default to 3
-;
-;maxlogintries=5
-;
-;
-; Define autologoff times if appropriate.  This is how long
-; the phone has to ring with no answer before the agent is
-; automatically logged off (in seconds)
-;
-;autologoff=15
-;
-; Define autologoffunavail to have agents automatically logged
-; out when the extension that they are at returns a CHANUNAVAIL
-; status when a call is attempted to be sent there.
+;[agent-id]
+; Define ackcall to require the agent to give a DTMF acknowledgement
+; when the agent receives a call.
+; The channel variable AGENTACKCALL overrides on agent login.
 ; Default is "no".
-;
-;autologoffunavail=yes
-;
-; Define ackcall to require a DTMF acknowledgement when
-; a logged-in agent receives a call.  Default is "no".
-; Use the acceptdtmf option to configure what DTMF key
-; press should be used to acknowledge the call. The
-; default is '#'.
-;
 ;ackcall=no
-;acceptdtmf=#
-;
-; Define endcall to allow an agent to hangup a call with a
-; DTMF keypress. Default is "yes". Use the enddtmf option to
-; configure which DTMF key will end a call. The default is
-; '*'.
 ;
-;endcall=yes
-;enddtmf=*
+; Set what DTMF key sequence the agent should use to acknowledge a call.
+; The channel variable AGENTACCEPTDTMF overrides on agent login.
+; Default is "#".
+;acceptdtmf=##
 ;
-; Define wrapuptime.  This is the minimum amount of time when
-; after disconnecting before the caller can receive a new call
-; note this is in milliseconds.
+; Set how many seconds a call for the agent has to wait for the agent to
+; acknowledge the call before the agent is automatically logged off.  If
+; set to zero then the call will wait forever for the agent to acknowledge.
+; The channel variable AGENTAUTOLOGOFF overrides on agent login.
+; Default is 0.
+;autologoff=15
 ;
+; Set the minimum amount of time after disconnecting a call before
+; the agent can receive a new call in milliseconds.
+; The channel variable AGENTWRAPUPTIME overrides on agent login.
+; Default is 0.
 ;wrapuptime=5000
 ;
-; Define the default musiconhold for agents
-; musiconhold => music_class
-;
-;musiconhold => default
-;
-; Define the default good bye sound file for agents
-; default to vm-goodbye
+; Set the musiconhold class for the agent.
+; Default is "default".
+;musiconhold=default
 ;
-;goodbye => goodbye_file
-;
-; Define updatecdr. This is whether or not to change the source
-; channel in the CDR record for this call to agent/agent_id so
-; that we know which agent generates the call
-;
-;updatecdr=no
-;
-; Group memberships for agents (may change in mid-file)
-;
-;group=3
-;group=1,2
-;group=
-;
-; --------------------------------------------------
-; This section is devoted to recording agent's calls
-; The keywords are global to the chan_agent channel driver
-;
-; Enable recording calls addressed to agents. It's turned off by default.
+; Enable recording calls the agent takes automatically by invoking the
+; DTMF automixmon feature when the agent connects to a caller.
+; See features.conf.sample for information about the automixmon feature.
+; Default is "no".
 ;recordagentcalls=yes
 ;
-; The format to be used to record the calls: wav, gsm, wav49.
-; By default its "wav".
-;recordformat=gsm
-;
-; The text to be added to the name of the recording. Allows forming a url link.
-;urlprefix=http://localhost/calls/
-;
-; The optional directory to save the conversations in. The default is
-; /var/spool/asterisk/monitor
-;savecallsin=/var/calls
-;
-; An optional custom beep sound file to play to always-connected agents.
+; The sound file played to alert the agent when a call is present.
+; Default is "beep".
 ;custom_beep=beep
 ;
+; A friendly name for the agent used in log messages.
+; Default is "".
+;fullname=Mark Spencer
+;
 ; --------------------------------------------------
 ;
-; This section contains the agent definitions, in the form:
+; This section contains example agent definitions:
+;
+; Define a template called my-agents:
+;[my-agents](!)
+;autologoff=15
+;ackcall=yes
+;acceptdtmf=##
 ;
-; agent => agentid,agentpassword,name
+; Define agent 1001 using the my-agents template:
+;[1001](my-agents)
+;fullname=Mark Spencer
 ;
-;agent => 1001,4321,Mark Spencer
-;agent => 1002,4321,Will Meadows
+; Define agent 1002 using the my-agents template:
+;[1002](my-agents)
+;fullname=Will Meadows
index 9f3ba97..994ad31 100644 (file)
@@ -543,18 +543,7 @@ monitor-type = MixMonitor
 ;member => DAHDI/1
 ;member => DAHDI/2,10
 ;member => DAHDI/3,10,Bob Johnson
-;member => Agent/1001
-;member => Agent/1002
+;member => Local/1001@agents,0,May Flowers,Agent:1001
+;member => Local/1002@agents,0,John Doe,Agent:1002
 ;member => Local/1000@default,0,John Smith,SIP/1000
 ;member => Local/2000@default,0,Lorem Ipsum,SIP/2000,no
-
-;
-; Note that using agent groups is probably not what you want.  Strategies do
-; not propagate down to the Agent system so if you want round robin, least
-; recent, etc, you should list all the agents in this file individually and not
-; use agent groups.
-;
-;member => Agent/@1            ; Any agent in group 1
-;member => Agent/:1,1          ; Any agent in group 1, wait for first
-                                ; available, but consider with penalty
-
index e03bfd0..cb50883 100644 (file)
@@ -1647,7 +1647,7 @@ void ast_after_bridge_goto_read(struct ast_channel *chan, char *buffer, size_t b
 
 /*! Reason the the after bridge callback will not be called. */
 enum ast_after_bridge_cb_reason {
-       /*! The datastore is being destroyed.  Likely due to hangup. */
+       /*! The datastore is being destroyed.  Likely due to hangup. (Enum value must be zero.) */
        AST_AFTER_BRIDGE_CB_REASON_DESTROY,
        /*! Something else replaced the callback with another. */
        AST_AFTER_BRIDGE_CB_REASON_REPLACED,
@@ -1666,6 +1666,9 @@ enum ast_after_bridge_cb_reason {
  * \param reason Reason callback is failing.
  * \param data Extra data what setup the callback wanted to pass.
  *
+ * \note Called when the channel leaves the bridging system or
+ * is destroyed.
+ *
  * \return Nothing
  */
 typedef void (*ast_after_bridge_cb_failed)(enum ast_after_bridge_cb_reason reason, void *data);
@@ -1705,6 +1708,9 @@ void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_
  *
  * \note chan is locked by this function.
  *
+ * \note failed is called when the channel leaves the bridging
+ * system or is destroyed.
+ *
  * \retval 0 on success.
  * \retval -1 on error.
  */
index 6b44446..d557b56 100644 (file)
@@ -461,7 +461,7 @@ enum aco_process_status {
 /*! \brief Process a config info via the options registered with an aco_info
  *
  * \param info The config_options_info to be used for handling the config
- * \param reload Whether or not this is a reload
+ * \param reload Non-zero if this is for a reload.
  *
  * \retval ACO_PROCESS_OK Success
  * \retval ACO_PROCESS_ERROR Failure
index 24fe01c..4a900dd 100644 (file)
@@ -422,6 +422,22 @@ struct stasis_message_type *ast_channel_monitor_start_type(void);
 struct stasis_message_type *ast_channel_monitor_stop_type(void);
 
 /*!
+ * \since 12.0.0
+ * \brief Message type for agent login on a channel
+ *
+ * \retval A stasis message type
+ */
+struct stasis_message_type *ast_channel_agent_login_type(void);
+
+/*!
+ * \since 12.0.0
+ * \brief Message type for agent logoff on a channel
+ *
+ * \retval A stasis message type
+ */
+struct stasis_message_type *ast_channel_agent_logoff_type(void);
+
+/*!
  * \since 12
  * \brief Message type for starting music on hold on a channel
  *
index 110f525..99e97a4 100644 (file)
@@ -3145,15 +3145,70 @@ static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge
        return bridge_channel;
 }
 
-struct after_bridge_cb_ds {
+struct after_bridge_cb_node {
+       /*! Next list node. */
+       AST_LIST_ENTRY(after_bridge_cb_node) list;
        /*! Desired callback function. */
        ast_after_bridge_cb callback;
        /*! After bridge callback will not be called and destroy any resources data may contain. */
        ast_after_bridge_cb_failed failed;
        /*! Extra data to pass to the callback. */
        void *data;
+       /*! Reason the after bridge callback failed. */
+       enum ast_after_bridge_cb_reason reason;
 };
 
+struct after_bridge_cb_ds {
+       /*! After bridge callbacks container. */
+       AST_LIST_HEAD(, after_bridge_cb_node) callbacks;
+};
+
+/*!
+ * \internal
+ * \brief Indicate after bridge callback failed.
+ * \since 12.0.0
+ *
+ * \param node After bridge callback node.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_failed(struct after_bridge_cb_node *node)
+{
+       if (node->failed) {
+               node->failed(node->reason, node->data);
+               node->failed = NULL;
+       }
+}
+
+/*!
+ * \internal
+ * \brief Run discarding any after bridge callbacks.
+ * \since 12.0.0
+ *
+ * \param after_bridge After bridge callback container process.
+ * \param reason Why are we doing this.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_run_discard(struct after_bridge_cb_ds *after_bridge, enum ast_after_bridge_cb_reason reason)
+{
+       struct after_bridge_cb_node *node;
+
+       for (;;) {
+               AST_LIST_LOCK(&after_bridge->callbacks);
+               node = AST_LIST_REMOVE_HEAD(&after_bridge->callbacks, list);
+               AST_LIST_UNLOCK(&after_bridge->callbacks);
+               if (!node) {
+                       break;
+               }
+               if (!node->reason) {
+                       node->reason = reason;
+               }
+               after_bridge_cb_failed(node);
+               ast_free(node);
+       }
+}
+
 /*!
  * \internal
  * \brief Destroy the after bridge callback datastore.
@@ -3167,10 +3222,9 @@ static void after_bridge_cb_destroy(void *data)
 {
        struct after_bridge_cb_ds *after_bridge = data;
 
-       if (after_bridge->failed) {
-               after_bridge->failed(AST_AFTER_BRIDGE_CB_REASON_DESTROY, after_bridge->data);
-               after_bridge->failed = NULL;
-       }
+       after_bridge_cb_run_discard(after_bridge, AST_AFTER_BRIDGE_CB_REASON_DESTROY);
+
+       AST_LIST_HEAD_DESTROY(&after_bridge->callbacks);
        ast_free(after_bridge);
 }
 
@@ -3187,7 +3241,6 @@ static void after_bridge_cb_destroy(void *data)
  */
 static void after_bridge_cb_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
 {
-       /* There can be only one.  Discard any already on the new channel. */
        ast_after_bridge_callback_discard(new_chan, AST_AFTER_BRIDGE_CB_REASON_MASQUERADE);
 }
 
@@ -3199,47 +3252,67 @@ static const struct ast_datastore_info after_bridge_cb_info = {
 
 /*!
  * \internal
- * \brief Remove channel after the bridge callback and return it.
+ * \brief Setup/create an after bridge callback datastore container.
  * \since 12.0.0
  *
- * \param chan Channel to remove after bridge callback.
+ * \param chan Channel to setup/create the after bridge callback container on.
  *
- * \retval datastore on success.
- * \retval NULL on error or not found.
+ * \retval after_bridge datastore container on success.
+ * \retval NULL on error.
  */
-static struct ast_datastore *after_bridge_cb_remove(struct ast_channel *chan)
+static struct after_bridge_cb_ds *after_bridge_cb_setup(struct ast_channel *chan)
 {
        struct ast_datastore *datastore;
+       struct after_bridge_cb_ds *after_bridge;
+       SCOPED_CHANNELLOCK(lock, chan);
 
-       ast_channel_lock(chan);
        datastore = ast_channel_datastore_find(chan, &after_bridge_cb_info, NULL);
-       if (datastore && ast_channel_datastore_remove(chan, datastore)) {
-               datastore = NULL;
+       if (datastore) {
+               return datastore->data;
        }
-       ast_channel_unlock(chan);
 
-       return datastore;
+       /* Create a new datastore. */
+       datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL);
+       if (!datastore) {
+               return NULL;
+       }
+       after_bridge = ast_calloc(1, sizeof(*after_bridge));
+       if (!after_bridge) {
+               ast_datastore_free(datastore);
+               return NULL;
+       }
+       AST_LIST_HEAD_INIT(&after_bridge->callbacks);
+       datastore->data = after_bridge;
+       ast_channel_datastore_add(chan, datastore);
+
+       return datastore->data;
 }
 
-void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason)
+/*!
+ * \internal
+ * \brief Find an after bridge callback datastore container.
+ * \since 12.0.0
+ *
+ * \param chan Channel to find the after bridge callback container on.
+ *
+ * \retval after_bridge datastore container on success.
+ * \retval NULL on error.
+ */
+static struct after_bridge_cb_ds *after_bridge_cb_find(struct ast_channel *chan)
 {
        struct ast_datastore *datastore;
+       SCOPED_CHANNELLOCK(lock, chan);
 
-       datastore = after_bridge_cb_remove(chan);
-       if (datastore) {
-               struct after_bridge_cb_ds *after_bridge = datastore->data;
-
-               if (after_bridge && after_bridge->failed) {
-                       after_bridge->failed(reason, after_bridge->data);
-                       after_bridge->failed = NULL;
-               }
-               ast_datastore_free(datastore);
+       datastore = ast_channel_datastore_find(chan, &after_bridge_cb_info, NULL);
+       if (!datastore) {
+               return NULL;
        }
+       return datastore->data;
 }
 
 /*!
  * \internal
- * \brief Run any after bridge callback if possible.
+ * \brief Run any after bridge callback.
  * \since 12.0.0
  *
  * \param chan Channel to run after bridge callback.
@@ -3248,33 +3321,75 @@ void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_
  */
 static void after_bridge_callback_run(struct ast_channel *chan)
 {
-       struct ast_datastore *datastore;
        struct after_bridge_cb_ds *after_bridge;
+       struct after_bridge_cb_node *node;
 
-       if (ast_check_hangup(chan)) {
+       after_bridge = after_bridge_cb_find(chan);
+       if (!after_bridge) {
                return;
        }
 
-       /* Get after bridge goto datastore. */
-       datastore = after_bridge_cb_remove(chan);
-       if (!datastore) {
+       for (;;) {
+               AST_LIST_LOCK(&after_bridge->callbacks);
+               node = AST_LIST_REMOVE_HEAD(&after_bridge->callbacks, list);
+               AST_LIST_UNLOCK(&after_bridge->callbacks);
+               if (!node) {
+                       break;
+               }
+               if (node->reason) {
+                       after_bridge_cb_failed(node);
+               } else {
+                       node->failed = NULL;
+                       node->callback(chan, node->data);
+               }
+               ast_free(node);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Run discarding any after bridge callbacks.
+ * \since 12.0.0
+ *
+ * \param chan Channel to run after bridge callback.
+ *
+ * \return Nothing
+ */
+static void after_bridge_callback_run_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason)
+{
+       struct after_bridge_cb_ds *after_bridge;
+
+       after_bridge = after_bridge_cb_find(chan);
+       if (!after_bridge) {
                return;
        }
 
-       after_bridge = datastore->data;
-       if (after_bridge) {
-               after_bridge->failed = NULL;
-               after_bridge->callback(chan, after_bridge->data);
+       after_bridge_cb_run_discard(after_bridge, reason);
+}
+
+void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason)
+{
+       struct after_bridge_cb_ds *after_bridge;
+       struct after_bridge_cb_node *node;
+
+       after_bridge = after_bridge_cb_find(chan);
+       if (!after_bridge) {
+               return;
        }
 
-       /* Discard after bridge callback datastore. */
-       ast_datastore_free(datastore);
+       AST_LIST_LOCK(&after_bridge->callbacks);
+       node = AST_LIST_LAST(&after_bridge->callbacks);
+       if (node && !node->reason) {
+               node->reason = reason;
+       }
+       AST_LIST_UNLOCK(&after_bridge->callbacks);
 }
 
 int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data)
 {
-       struct ast_datastore *datastore;
        struct after_bridge_cb_ds *after_bridge;
+       struct after_bridge_cb_node *new_node;
+       struct after_bridge_cb_node *last_node;
 
        /* Sanity checks. */
        ast_assert(chan != NULL);
@@ -3282,29 +3397,28 @@ int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb
                return -1;
        }
 
-       /* Create a new datastore. */
-       datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL);
-       if (!datastore) {
-               return -1;
-       }
-       after_bridge = ast_calloc(1, sizeof(*after_bridge));
+       after_bridge = after_bridge_cb_setup(chan);
        if (!after_bridge) {
-               ast_datastore_free(datastore);
                return -1;
        }
 
-       /* Initialize it. */
-       after_bridge->callback = callback;
-       after_bridge->failed = failed;
-       after_bridge->data = data;
-       datastore->data = after_bridge;
-
-       /* Put it on the channel replacing any existing one. */
-       ast_channel_lock(chan);
-       ast_after_bridge_callback_discard(chan, AST_AFTER_BRIDGE_CB_REASON_REPLACED);
-       ast_channel_datastore_add(chan, datastore);
-       ast_channel_unlock(chan);
+       /* Create a new callback node. */
+       new_node = ast_calloc(1, sizeof(*new_node));
+       if (!new_node) {
+               return -1;
+       }
+       new_node->callback = callback;
+       new_node->failed = failed;
+       new_node->data = data;
 
+       /* Put it in the container disabling any previously active one. */
+       AST_LIST_LOCK(&after_bridge->callbacks);
+       last_node = AST_LIST_LAST(&after_bridge->callbacks);
+       if (last_node && !last_node->reason) {
+               last_node->reason = AST_AFTER_BRIDGE_CB_REASON_REPLACED;
+       }
+       AST_LIST_INSERT_TAIL(&after_bridge->callbacks, new_node, list);
+       AST_LIST_UNLOCK(&after_bridge->callbacks);
        return 0;
 }
 
@@ -3769,7 +3883,7 @@ static void *bridge_channel_depart_thread(void *data)
        ast_bridge_features_destroy(bridge_channel->features);
        bridge_channel->features = NULL;
 
-       ast_after_bridge_callback_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART);
+       after_bridge_callback_run_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART);
        ast_after_bridge_goto_discard(bridge_channel->chan);
 
        return NULL;
@@ -4993,6 +5107,23 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
        return 0;
 }
 
+int ast_bridge_features_do(enum ast_bridge_builtin_feature feature, struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       ast_bridge_hook_callback callback;
+
+       if (ARRAY_LEN(builtin_features_handlers) <= feature) {
+               return -1;
+       }
+
+       callback = builtin_features_handlers[feature];
+       if (!callback) {
+               return -1;
+       }
+       callback(bridge, bridge_channel, hook_pvt);
+
+       return 0;
+}
+
 int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback)
 {
        if (ARRAY_LEN(builtin_interval_handlers) <= interval
index 908b6ac..caa697c 100644 (file)
@@ -598,6 +598,12 @@ enum aco_process_status aco_process_config(struct aco_info *info, int reload)
                return ACO_PROCESS_ERROR;
        }
 
+/*
+ * BUGBUG must fix config framework loading of multiple files.
+ *
+ * A reload with multiple files must reload all files if any
+ * file has been touched.
+ */
        while (res != ACO_PROCESS_ERROR && (file = info->files[x++])) {
                const char *filename = file->filename;
 try_alias:
index d121279..dd8f7b4 100644 (file)
@@ -53,6 +53,35 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        </syntax>
                </managerEventInstance>
        </managerEvent>
+       <managerEvent language="en_US" name="AgentLogin">
+               <managerEventInstance class="EVENT_FLAG_AGENT">
+                       <synopsis>Raised when an Agent has logged in.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="Agent">
+                                       <para>The name of the agent.</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="application">AgentLogin</ref>
+                               <ref type="managerEvent">Agentlogoff</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="AgentLogoff">
+               <managerEventInstance class="EVENT_FLAG_AGENT">
+                       <synopsis>Raised when an Agent has logged off.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='AgentLogin']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="Logintime">
+                                       <para>The number of seconds the agent was logged in.</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">AgentLogin</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
 ***/
 
 #define NUM_MULTI_CHANNEL_BLOB_BUCKETS 7
@@ -590,6 +619,44 @@ static struct ast_manager_event_blob *varset_to_ami(struct stasis_message *msg)
                ast_str_buffer(channel_event_string), variable, value);
 }
 
+static struct ast_manager_event_blob *agent_login_to_ami(struct stasis_message *msg)
+{
+       RAII_VAR(struct ast_str *, channel_string, NULL, ast_free);
+       RAII_VAR(struct ast_str *, party_string, ast_str_create(256), ast_free);
+       struct ast_channel_blob *obj = stasis_message_data(msg);
+       const char *agent = ast_json_string_get(ast_json_object_get(obj->blob, "agent"));
+
+       channel_string = ast_manager_build_channel_state_string(obj->snapshot);
+       if (!channel_string) {
+               return NULL;
+       }
+
+       return ast_manager_event_blob_create(EVENT_FLAG_AGENT, "AgentLogin",
+               "%s"
+               "Agent: %s\r\n",
+               ast_str_buffer(channel_string), agent);
+}
+
+static struct ast_manager_event_blob *agent_logoff_to_ami(struct stasis_message *msg)
+{
+       RAII_VAR(struct ast_str *, channel_string, NULL, ast_free);
+       RAII_VAR(struct ast_str *, party_string, ast_str_create(256), ast_free);
+       struct ast_channel_blob *obj = stasis_message_data(msg);
+       const char *agent = ast_json_string_get(ast_json_object_get(obj->blob, "agent"));
+       long logintime = ast_json_integer_get(ast_json_object_get(obj->blob, "logintime"));
+
+       channel_string = ast_manager_build_channel_state_string(obj->snapshot);
+       if (!channel_string) {
+               return NULL;
+       }
+
+       return ast_manager_event_blob_create(EVENT_FLAG_AGENT, "AgentLogoff",
+               "%s"
+               "Agent: %s\r\n"
+               "Logintime: %ld\r\n",
+               ast_str_buffer(channel_string), agent, logintime);
+}
+
 void ast_publish_channel_state(struct ast_channel *chan)
 {
        RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -790,6 +857,12 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_moh_start_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_moh_stop_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_monitor_start_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_monitor_stop_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_agent_login_type,
+       .to_ami = agent_login_to_ami,
+       );
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_agent_logoff_type,
+       .to_ami = agent_logoff_to_ami,
+       );
 
 /*! @} */
 
@@ -816,6 +889,8 @@ static void stasis_channels_cleanup(void)
        STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_moh_stop_type);
        STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_monitor_start_type);
        STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_monitor_stop_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_agent_login_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_agent_logoff_type);
 }
 
 void ast_stasis_channels_init(void)
@@ -839,6 +914,8 @@ void ast_stasis_channels_init(void)
        STASIS_MESSAGE_TYPE_INIT(ast_channel_moh_stop_type);
        STASIS_MESSAGE_TYPE_INIT(ast_channel_monitor_start_type);
        STASIS_MESSAGE_TYPE_INIT(ast_channel_monitor_stop_type);
+       STASIS_MESSAGE_TYPE_INIT(ast_channel_agent_login_type);
+       STASIS_MESSAGE_TYPE_INIT(ast_channel_agent_logoff_type);
 
        channel_topic_all = stasis_topic_create("ast_channel_topic_all");
        channel_topic_all_cached = stasis_caching_topic_create(channel_topic_all, channel_snapshot_get_id);