diff --git a/CHANGES b/CHANGES index 1edbe92028f60f90f1d2106abb07f5ae465aff16..e6da22224e3228ff13d0c4c4deecee68b5585d5c 100644 --- a/CHANGES +++ b/CHANGES @@ -14,19 +14,10 @@ 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 ------------------ @@ -253,8 +244,8 @@ AMI (Asterisk Manager Interface) of "CallerID" and "ConnectedID" to avoid confusion with similarly named parameters in the channel snapshot. - * The AMI events "Agentlogin" and "Agentlogoff" have been renamed - "AgentLogin" and "AgentLogoff" respectively. + * The "Agentlogin" and "Agentlogoff" events 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. @@ -432,21 +423,6 @@ 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 ------------------ diff --git a/UPGRADE.txt b/UPGRADE.txt index fb7b65bf7298d8425096cf64dc85e27ea9d858ee..7a5261b94d7da4c2f4fbb45f38617b13f99e296d 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -24,8 +24,6 @@ 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. @@ -126,15 +124,6 @@ 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 deleted file mode 100644 index b3f67d804164f3f0966a17fc678e3b1402565663..0000000000000000000000000000000000000000 --- a/apps/app_agent_pool.c +++ /dev/null @@ -1,2486 +0,0 @@ -/* - * 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 new file mode 100644 index 0000000000000000000000000000000000000000..57f0914cfd1d4cf693e166934f875c3f0109d925 --- /dev/null +++ b/channels/chan_agent.c @@ -0,0 +1,2568 @@ +/* + * 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", + ); diff --git a/configs/agents.conf.sample b/configs/agents.conf.sample index 0db451dfc3de3ce5e9a1dc13972f68bab547e74b..29e6e07eadbb4e8ed5269ae3dbd8bf727b750666 100644 --- a/configs/agents.conf.sample +++ b/configs/agents.conf.sample @@ -1,68 +1,102 @@ ; -; Agent pool configuration +; Agent configuration ; [general] -; The general section of this config is not currently used, but reserved -; for future use. -;[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. +[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. ; 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=# ; -; 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 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 +; '*'. ; -; 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 +;endcall=yes +;enddtmf=* +; +; 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 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 ; -; Set the musiconhold class for the agent. -; Default is "default". -;musiconhold=default +; Define the default musiconhold for agents +; musiconhold => music_class ; -; 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 +;musiconhold => default ; -; The sound file played to alert the agent when a call is present. -; Default is "beep". -;custom_beep=beep +; Define the default good bye sound file for agents +; default to vm-goodbye +; +;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 ; -; A friendly name for the agent used in log messages. -; Default is "". -;fullname=Mark Spencer +; 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 ; -; This section contains example agent definitions: +; Enable recording calls addressed to agents. It's turned off by default. +;recordagentcalls=yes ; -; Define a template called my-agents: -;[my-agents](!) -;autologoff=15 -;ackcall=yes -;acceptdtmf=## +; 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. +;custom_beep=beep +; +; -------------------------------------------------- +; +; This section contains the agent definitions, in the form: ; -; Define agent 1001 using the my-agents template: -;[1001](my-agents) -;fullname=Mark Spencer +; agent => agentid,agentpassword,name ; -; Define agent 1002 using the my-agents template: -;[1002](my-agents) -;fullname=Will Meadows +;agent => 1001,4321,Mark Spencer +;agent => 1002,4321,Will Meadows diff --git a/configs/queues.conf.sample b/configs/queues.conf.sample index 994ad31daf0406d496d23364d0c7aad2851c32d7..9f3ba97def3d4435ab2dc5272b26ad75965b843b 100644 --- a/configs/queues.conf.sample +++ b/configs/queues.conf.sample @@ -543,7 +543,18 @@ monitor-type = MixMonitor ;member => DAHDI/1 ;member => DAHDI/2,10 ;member => DAHDI/3,10,Bob Johnson -;member => Local/1001@agents,0,May Flowers,Agent:1001 -;member => Local/1002@agents,0,John Doe,Agent:1002 +;member => Agent/1001 +;member => 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 + diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h index cb508837629e5e137ede42a20e581642e9001f62..e03bfd0cdbb65e4d74b5dac237915aa27196d050 100644 --- a/include/asterisk/bridging.h +++ b/include/asterisk/bridging.h @@ -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. (Enum value must be zero.) */ + /*! The datastore is being destroyed. Likely due to hangup. */ AST_AFTER_BRIDGE_CB_REASON_DESTROY, /*! Something else replaced the callback with another. */ AST_AFTER_BRIDGE_CB_REASON_REPLACED, @@ -1666,9 +1666,6 @@ 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); @@ -1708,9 +1705,6 @@ 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. */ diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h index d557b5685f78aa823a59e1ae2eae788ba2250163..6b444466f59a5bfc3e7a0ab30e6399853d0f3e22 100644 --- a/include/asterisk/config_options.h +++ b/include/asterisk/config_options.h @@ -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 Non-zero if this is for a reload. + * \param reload Whether or not this is a reload * * \retval ACO_PROCESS_OK Success * \retval ACO_PROCESS_ERROR Failure diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h index 4a900dddca14ab227f9dbfc481c38208d11b6f3f..24fe01ceab8548318ab7da730f1319600cab25a2 100644 --- a/include/asterisk/stasis_channels.h +++ b/include/asterisk/stasis_channels.h @@ -421,22 +421,6 @@ 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 diff --git a/main/bridging.c b/main/bridging.c index 99e97a4ed7aae65c31f4603b43464437b720e093..110f5255b1a21b21ed515d7b266f593c2b6beb1f 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -3145,70 +3145,15 @@ static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge return bridge_channel; } -struct after_bridge_cb_node { - /*! Next list node. */ - AST_LIST_ENTRY(after_bridge_cb_node) list; +struct after_bridge_cb_ds { /*! 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. @@ -3222,9 +3167,10 @@ static void after_bridge_cb_destroy(void *data) { struct after_bridge_cb_ds *after_bridge = data; - after_bridge_cb_run_discard(after_bridge, AST_AFTER_BRIDGE_CB_REASON_DESTROY); - - AST_LIST_HEAD_DESTROY(&after_bridge->callbacks); + if (after_bridge->failed) { + after_bridge->failed(AST_AFTER_BRIDGE_CB_REASON_DESTROY, after_bridge->data); + after_bridge->failed = NULL; + } ast_free(after_bridge); } @@ -3241,6 +3187,7 @@ 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); } @@ -3252,144 +3199,82 @@ static const struct ast_datastore_info after_bridge_cb_info = { /*! * \internal - * \brief Setup/create an after bridge callback datastore container. + * \brief Remove channel after the bridge callback and return it. * \since 12.0.0 * - * \param chan Channel to setup/create the after bridge callback container on. + * \param chan Channel to remove after bridge callback. * - * \retval after_bridge datastore container on success. - * \retval NULL on error. + * \retval datastore on success. + * \retval NULL on error or not found. */ -static struct after_bridge_cb_ds *after_bridge_cb_setup(struct ast_channel *chan) +static struct ast_datastore *after_bridge_cb_remove(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) { - return datastore->data; - } - - /* 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; + if (datastore && ast_channel_datastore_remove(chan, datastore)) { + datastore = NULL; } - AST_LIST_HEAD_INIT(&after_bridge->callbacks); - datastore->data = after_bridge; - ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); - return datastore->data; + return datastore; } -/*! - * \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) +void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason) { struct ast_datastore *datastore; - SCOPED_CHANNELLOCK(lock, chan); - 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. - * \since 12.0.0 - * - * \param chan Channel to run after bridge callback. - * - * \return Nothing - */ -static void after_bridge_callback_run(struct ast_channel *chan) -{ - struct after_bridge_cb_ds *after_bridge; - struct after_bridge_cb_node *node; - - after_bridge = after_bridge_cb_find(chan); - if (!after_bridge) { - return; - } + datastore = after_bridge_cb_remove(chan); + if (datastore) { + struct after_bridge_cb_ds *after_bridge = datastore->data; - 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); + if (after_bridge && after_bridge->failed) { + after_bridge->failed(reason, after_bridge->data); + after_bridge->failed = NULL; } - ast_free(node); + ast_datastore_free(datastore); } } /*! * \internal - * \brief Run discarding any after bridge callbacks. + * \brief Run any after bridge callback if possible. * \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) +static void after_bridge_callback_run(struct ast_channel *chan) { + struct ast_datastore *datastore; struct after_bridge_cb_ds *after_bridge; - after_bridge = after_bridge_cb_find(chan); - if (!after_bridge) { + if (ast_check_hangup(chan)) { return; } - 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) { + /* Get after bridge goto datastore. */ + datastore = after_bridge_cb_remove(chan); + if (!datastore) { return; } - AST_LIST_LOCK(&after_bridge->callbacks); - node = AST_LIST_LAST(&after_bridge->callbacks); - if (node && !node->reason) { - node->reason = reason; + after_bridge = datastore->data; + if (after_bridge) { + after_bridge->failed = NULL; + after_bridge->callback(chan, after_bridge->data); } - AST_LIST_UNLOCK(&after_bridge->callbacks); + + /* Discard after bridge callback datastore. */ + ast_datastore_free(datastore); } 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); @@ -3397,28 +3282,29 @@ int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb return -1; } - after_bridge = after_bridge_cb_setup(chan); - if (!after_bridge) { + /* Create a new datastore. */ + datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL); + if (!datastore) { return -1; } - - /* Create a new callback node. */ - new_node = ast_calloc(1, sizeof(*new_node)); - if (!new_node) { + after_bridge = ast_calloc(1, sizeof(*after_bridge)); + if (!after_bridge) { + ast_datastore_free(datastore); 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); + /* 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); + return 0; } @@ -3883,7 +3769,7 @@ static void *bridge_channel_depart_thread(void *data) ast_bridge_features_destroy(bridge_channel->features); bridge_channel->features = NULL; - after_bridge_callback_run_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART); + ast_after_bridge_callback_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART); ast_after_bridge_goto_discard(bridge_channel->chan); return NULL; @@ -5107,23 +4993,6 @@ 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 diff --git a/main/config_options.c b/main/config_options.c index caa697c49a18d4eb7d2d1d39ec5412921fa86a3c..908b6ac124de2a57b7863b13971e5fc4c7347272 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -598,12 +598,6 @@ 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: diff --git a/main/stasis_channels.c b/main/stasis_channels.c index dd8f7b445cfe5537189f30d101d1a6e6dc1ac501..d121279d88ce8e63cefc29a2de40b965efaf847e 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -53,35 +53,6 @@ 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 @@ -619,44 +590,6 @@ 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); @@ -857,12 +790,6 @@ 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, - ); /*! @} */ @@ -889,8 +816,6 @@ 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) @@ -914,8 +839,6 @@ 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);