@ -54,421 +54,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
# include "asterisk/mixmonitor.h"
# include "asterisk/audiohook.h"
/*!
* \ brief Helper function that presents dialtone and grabs extension
*
* \ retval 0 on success
* \ retval - 1 on failure
*/
static int grab_transfer ( struct ast_channel * chan , char * exten , size_t exten_len , const char * context )
{
int res ;
int digit_timeout ;
RAII_VAR ( struct ast_features_xfer_config * , xfer_cfg , NULL , ao2_cleanup ) ;
ast_channel_lock ( chan ) ;
xfer_cfg = ast_get_chan_features_xfer_config ( chan ) ;
if ( ! xfer_cfg ) {
ast_log ( LOG_ERROR , " Unable to get transfer configuration \n " ) ;
ast_channel_unlock ( chan ) ;
return - 1 ;
}
digit_timeout = xfer_cfg - > transferdigittimeout ;
ast_channel_unlock ( chan ) ;
/* Play the simple "transfer" prompt out and wait */
res = ast_stream_and_wait ( chan , " pbx-transfer " , AST_DIGIT_ANY ) ;
ast_stopstream ( chan ) ;
if ( res < 0 ) {
/* Hangup or error */
return - 1 ;
}
if ( res ) {
/* Store the DTMF digit that interrupted playback of the file. */
exten [ 0 ] = res ;
}
/* Drop to dialtone so they can enter the extension they want to transfer to */
res = ast_app_dtget ( chan , context , exten , exten_len , exten_len - 1 , digit_timeout ) ;
if ( res < 0 ) {
/* Hangup or error */
res = - 1 ;
} else if ( ! res ) {
/* 0 for invalid extension dialed. */
if ( ast_strlen_zero ( exten ) ) {
ast_debug ( 1 , " %s dialed no digits. \n " , ast_channel_name ( chan ) ) ;
} else {
ast_debug ( 1 , " %s dialed '%s@%s' does not exist. \n " ,
ast_channel_name ( chan ) , exten , context ) ;
}
ast_stream_and_wait ( chan , " pbx-invalid " , AST_DIGIT_NONE ) ;
res = - 1 ;
} else {
/* Dialed extension is valid. */
res = 0 ;
}
return res ;
}
static void copy_caller_data ( struct ast_channel * dest , struct ast_channel * caller )
{
ast_channel_lock_both ( caller , dest ) ;
ast_connected_line_copy_from_caller ( ast_channel_connected ( dest ) , ast_channel_caller ( caller ) ) ;
ast_channel_inherit_variables ( caller , dest ) ;
ast_channel_datastore_inherit ( caller , dest ) ;
ast_channel_unlock ( dest ) ;
ast_channel_unlock ( caller ) ;
}
/*! \brief Helper function that creates an outgoing channel and returns it immediately */
static struct ast_channel * dial_transfer ( struct ast_channel * caller , const char * exten , const char * context )
{
char destination [ AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1 ] ;
struct ast_channel * chan ;
int cause ;
/* Fill the variable with the extension and context we want to call */
snprintf ( destination , sizeof ( destination ) , " %s@%s " , exten , context ) ;
/* Now we request a local channel to prepare to call the destination */
chan = ast_request ( " Local " , ast_channel_nativeformats ( caller ) , caller , destination ,
& cause ) ;
if ( ! chan ) {
return NULL ;
}
/* Who is transferring the call. */
pbx_builtin_setvar_helper ( chan , " TRANSFERERNAME " , ast_channel_name ( caller ) ) ;
/* To work as an analog to BLINDTRANSFER */
pbx_builtin_setvar_helper ( chan , " ATTENDEDTRANSFER " , ast_channel_name ( caller ) ) ;
/* Before we actually dial out let's inherit appropriate information. */
copy_caller_data ( chan , caller ) ;
/* Since the above worked fine now we actually call it and return the channel */
if ( ast_call ( chan , destination , 0 ) ) {
ast_hangup ( chan ) ;
return NULL ;
}
return chan ;
}
/*!
* \ internal
* \ brief Determine the transfer context to use .
* \ since 12.0 .0
*
* \ param transferer Channel initiating the transfer .
* \ param context User supplied context if available . May be NULL .
*
* \ return The context to use for the transfer .
*/
static const char * get_transfer_context ( struct ast_channel * transferer , const char * context )
{
if ( ! ast_strlen_zero ( context ) ) {
return context ;
}
context = pbx_builtin_getvar_helper ( transferer , " TRANSFER_CONTEXT " ) ;
if ( ! ast_strlen_zero ( context ) ) {
return context ;
}
context = ast_channel_macrocontext ( transferer ) ;
if ( ! ast_strlen_zero ( context ) ) {
return context ;
}
context = ast_channel_context ( transferer ) ;
if ( ! ast_strlen_zero ( context ) ) {
return context ;
}
return " default " ;
}
static void blind_transfer_cb ( struct ast_channel * new_channel , void * user_data ,
enum ast_transfer_type transfer_type )
{
struct ast_channel * transferer_channel = user_data ;
if ( transfer_type = = AST_BRIDGE_TRANSFER_MULTI_PARTY ) {
copy_caller_data ( new_channel , transferer_channel ) ;
}
}
/*! \brief Internal built in feature for blind transfers */
static int feature_blind_transfer ( struct ast_bridge * bridge , struct ast_bridge_channel * bridge_channel , void * hook_pvt )
{
char exten [ AST_MAX_EXTENSION ] = " " ;
struct ast_bridge_features_blind_transfer * blind_transfer = hook_pvt ;
const char * context ;
char * goto_on_blindxfr ;
ast_bridge_channel_write_hold ( bridge_channel , NULL ) ;
ast_channel_lock ( bridge_channel - > chan ) ;
context = ast_strdupa ( get_transfer_context ( bridge_channel - > chan ,
blind_transfer ? blind_transfer - > context : NULL ) ) ;
goto_on_blindxfr = ast_strdupa ( S_OR ( pbx_builtin_getvar_helper ( bridge_channel - > chan ,
" GOTO_ON_BLINDXFR " ) , " " ) ) ;
ast_channel_unlock ( bridge_channel - > chan ) ;
/* Grab the extension to transfer to */
if ( grab_transfer ( bridge_channel - > chan , exten , sizeof ( exten ) , context ) ) {
ast_bridge_channel_write_unhold ( bridge_channel ) ;
return 0 ;
}
if ( ! ast_strlen_zero ( goto_on_blindxfr ) ) {
ast_debug ( 1 , " After transfer, transferer %s goes to %s \n " ,
ast_channel_name ( bridge_channel - > chan ) , goto_on_blindxfr ) ;
ast_after_bridge_set_go_on ( bridge_channel - > chan , NULL , NULL , 0 , goto_on_blindxfr ) ;
}
if ( ast_bridge_transfer_blind ( 0 , bridge_channel - > chan , exten , context , blind_transfer_cb ,
bridge_channel - > chan ) ! = AST_BRIDGE_TRANSFER_SUCCESS & &
! ast_strlen_zero ( goto_on_blindxfr ) ) {
ast_after_bridge_goto_discard ( bridge_channel - > chan ) ;
}
return 0 ;
}
/*! Attended transfer code */
enum atxfer_code {
/*! Party C hungup or other reason to abandon the transfer. */
ATXFER_INCOMPLETE ,
/*! Transfer party C to party A. */
ATXFER_COMPLETE ,
/*! Turn the transfer into a threeway call. */
ATXFER_THREEWAY ,
/*! Hangup party C and return party B to the bridge. */
ATXFER_ABORT ,
} ;
/*! \brief Attended transfer feature to complete transfer */
static int attended_transfer_complete ( struct ast_bridge * bridge , struct ast_bridge_channel * bridge_channel , void * hook_pvt )
{
enum atxfer_code * transfer_code = hook_pvt ;
* transfer_code = ATXFER_COMPLETE ;
ast_bridge_change_state ( bridge_channel , AST_BRIDGE_CHANNEL_STATE_HANGUP ) ;
return 0 ;
}
/*! \brief Attended transfer feature to turn it into a threeway call */
static int attended_transfer_threeway ( struct ast_bridge * bridge , struct ast_bridge_channel * bridge_channel , void * hook_pvt )
{
enum atxfer_code * transfer_code = hook_pvt ;
* transfer_code = ATXFER_THREEWAY ;
ast_bridge_change_state ( bridge_channel , AST_BRIDGE_CHANNEL_STATE_HANGUP ) ;
return 0 ;
}
/*! \brief Attended transfer feature to abort transfer */
static int attended_transfer_abort ( struct ast_bridge * bridge , struct ast_bridge_channel * bridge_channel , void * hook_pvt )
{
enum atxfer_code * transfer_code = hook_pvt ;
* transfer_code = ATXFER_ABORT ;
ast_bridge_change_state ( bridge_channel , AST_BRIDGE_CHANNEL_STATE_HANGUP ) ;
return 0 ;
}
/*! \brief Internal built in feature for attended transfers */
static int feature_attended_transfer ( struct ast_bridge * bridge , struct ast_bridge_channel * bridge_channel , void * hook_pvt )
{
char exten [ AST_MAX_EXTENSION ] = " " ;
struct ast_channel * peer ;
struct ast_bridge * attended_bridge ;
struct ast_bridge_features caller_features ;
int xfer_failed ;
struct ast_bridge_features_attended_transfer * attended_transfer = hook_pvt ;
const char * complete_sound ;
const char * context ;
enum atxfer_code transfer_code = ATXFER_INCOMPLETE ;
const char * atxfer_abort ;
const char * atxfer_threeway ;
const char * atxfer_complete ;
const char * fail_sound ;
RAII_VAR ( struct ast_features_xfer_config * , xfer_cfg , NULL , ao2_cleanup ) ;
ast_bridge_channel_write_hold ( bridge_channel , NULL ) ;
bridge = ast_bridge_channel_merge_inhibit ( bridge_channel , + 1 ) ;
ast_channel_lock ( bridge_channel - > chan ) ;
context = ast_strdupa ( get_transfer_context ( bridge_channel - > chan ,
attended_transfer ? attended_transfer - > context : NULL ) ) ;
xfer_cfg = ast_get_chan_features_xfer_config ( bridge_channel - > chan ) ;
if ( ! xfer_cfg ) {
ast_log ( LOG_ERROR , " Unable to get transfer configuration options \n " ) ;
ast_channel_unlock ( bridge_channel - > chan ) ;
return 0 ;
}
if ( attended_transfer ) {
atxfer_abort = ast_strdupa ( S_OR ( attended_transfer - > abort , xfer_cfg - > atxferabort ) ) ;
atxfer_threeway = ast_strdupa ( S_OR ( attended_transfer - > threeway , xfer_cfg - > atxferthreeway ) ) ;
atxfer_complete = ast_strdupa ( S_OR ( attended_transfer - > complete , xfer_cfg - > atxfercomplete ) ) ;
} else {
atxfer_abort = ast_strdupa ( xfer_cfg - > atxferabort ) ;
atxfer_threeway = ast_strdupa ( xfer_cfg - > atxferthreeway ) ;
atxfer_complete = ast_strdupa ( xfer_cfg - > atxfercomplete ) ;
}
fail_sound = ast_strdupa ( xfer_cfg - > xferfailsound ) ;
ast_channel_unlock ( bridge_channel - > chan ) ;
/* Grab the extension to transfer to */
if ( grab_transfer ( bridge_channel - > chan , exten , sizeof ( exten ) , context ) ) {
ast_bridge_merge_inhibit ( bridge , - 1 ) ;
ao2_ref ( bridge , - 1 ) ;
ast_bridge_channel_write_unhold ( bridge_channel ) ;
return 0 ;
}
/* Get a channel that is the destination we wish to call */
peer = dial_transfer ( bridge_channel - > chan , exten , context ) ;
if ( ! peer ) {
ast_bridge_merge_inhibit ( bridge , - 1 ) ;
ao2_ref ( bridge , - 1 ) ;
ast_stream_and_wait ( bridge_channel - > chan , fail_sound , AST_DIGIT_NONE ) ;
ast_bridge_channel_write_unhold ( bridge_channel ) ;
return 0 ;
}
/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */
/* Setup a DTMF menu to control the transfer. */
if ( ast_bridge_features_init ( & caller_features )
| | ast_bridge_hangup_hook ( & caller_features ,
attended_transfer_complete , & transfer_code , NULL , 0 )
| | ast_bridge_dtmf_hook ( & caller_features , atxfer_abort ,
attended_transfer_abort , & transfer_code , NULL , 0 )
| | ast_bridge_dtmf_hook ( & caller_features , atxfer_complete ,
attended_transfer_complete , & transfer_code , NULL , 0 )
| | ast_bridge_dtmf_hook ( & caller_features , atxfer_threeway ,
attended_transfer_threeway , & transfer_code , NULL , 0 ) ) {
ast_bridge_features_cleanup ( & caller_features ) ;
ast_hangup ( peer ) ;
ast_bridge_merge_inhibit ( bridge , - 1 ) ;
ao2_ref ( bridge , - 1 ) ;
ast_stream_and_wait ( bridge_channel - > chan , fail_sound , AST_DIGIT_NONE ) ;
ast_bridge_channel_write_unhold ( bridge_channel ) ;
return 0 ;
}
/* Create a bridge to use to talk to the person we are calling */
attended_bridge = ast_bridge_base_new ( AST_BRIDGE_CAPABILITY_1TO1MIX ,
AST_BRIDGE_FLAG_DISSOLVE_HANGUP ) ;
if ( ! attended_bridge ) {
ast_bridge_features_cleanup ( & caller_features ) ;
ast_hangup ( peer ) ;
ast_bridge_merge_inhibit ( bridge , - 1 ) ;
ao2_ref ( bridge , - 1 ) ;
ast_stream_and_wait ( bridge_channel - > chan , fail_sound , AST_DIGIT_NONE ) ;
ast_bridge_channel_write_unhold ( bridge_channel ) ;
return 0 ;
}
ast_bridge_merge_inhibit ( attended_bridge , + 1 ) ;
/* This is how this is going down, we are imparting the channel we called above into this bridge first */
/* BUGBUG we should impart the peer as an independent and move it to the original bridge. */
if ( ast_bridge_impart ( attended_bridge , peer , NULL , NULL , 0 ) ) {
ast_bridge_destroy ( attended_bridge ) ;
ast_bridge_features_cleanup ( & caller_features ) ;
ast_hangup ( peer ) ;
ast_bridge_merge_inhibit ( bridge , - 1 ) ;
ao2_ref ( bridge , - 1 ) ;
ast_stream_and_wait ( bridge_channel - > chan , fail_sound , AST_DIGIT_NONE ) ;
ast_bridge_channel_write_unhold ( bridge_channel ) ;
return 0 ;
}
/*
* For the caller we want to join the bridge in a blocking
* fashion so we don ' t spin around in this function doing
* nothing while waiting .
*/
ast_bridge_join ( attended_bridge , bridge_channel - > chan , NULL , & caller_features , NULL , 0 ) ;
/*
* BUGBUG there is a small window where the channel does not point to the bridge_channel .
*
* This window is expected to go away when atxfer is redesigned
* to fully support existing functionality . There will be one
* and only one ast_bridge_channel structure per channel .
*/
/* Point the channel back to the original bridge and bridge_channel. */
ast_bridge_channel_lock ( bridge_channel ) ;
ast_channel_lock ( bridge_channel - > chan ) ;
ast_channel_internal_bridge_channel_set ( bridge_channel - > chan , bridge_channel ) ;
ast_channel_internal_bridge_set ( bridge_channel - > chan , bridge_channel - > bridge ) ;
ast_channel_unlock ( bridge_channel - > chan ) ;
ast_bridge_channel_unlock ( bridge_channel ) ;
/* Wait for peer thread to exit bridge and die. */
if ( ! ast_autoservice_start ( bridge_channel - > chan ) ) {
ast_bridge_depart ( peer ) ;
ast_autoservice_stop ( bridge_channel - > chan ) ;
} else {
ast_bridge_depart ( peer ) ;
}
/* Now that all channels are out of it we can destroy the bridge and the feature structures */
ast_bridge_destroy ( attended_bridge ) ;
ast_bridge_features_cleanup ( & caller_features ) ;
/* Is there a courtesy sound to play to the peer? */
ast_channel_lock ( bridge_channel - > chan ) ;
complete_sound = pbx_builtin_getvar_helper ( bridge_channel - > chan ,
" ATTENDED_TRANSFER_COMPLETE_SOUND " ) ;
if ( ! ast_strlen_zero ( complete_sound ) ) {
complete_sound = ast_strdupa ( complete_sound ) ;
} else {
complete_sound = NULL ;
}
ast_channel_unlock ( bridge_channel - > chan ) ;
if ( complete_sound ) {
pbx_builtin_setvar_helper ( peer , " BRIDGE_PLAY_SOUND " , complete_sound ) ;
}
xfer_failed = - 1 ;
switch ( transfer_code ) {
case ATXFER_INCOMPLETE :
/* Peer hungup */
break ;
case ATXFER_COMPLETE :
/* The peer takes our place in the bridge. */
ast_bridge_channel_write_unhold ( bridge_channel ) ;
ast_bridge_change_state ( bridge_channel , AST_BRIDGE_CHANNEL_STATE_HANGUP ) ;
xfer_failed = ast_bridge_impart ( bridge_channel - > bridge , peer , bridge_channel - > chan , NULL , 1 ) ;
break ;
case ATXFER_THREEWAY :
/*
* Transferer wants to convert to a threeway call .
*
* Just impart the peer onto the bridge and have us return to it
* as normal .
*/
ast_bridge_channel_write_unhold ( bridge_channel ) ;
xfer_failed = ast_bridge_impart ( bridge_channel - > bridge , peer , NULL , NULL , 1 ) ;
break ;
case ATXFER_ABORT :
/* Transferer decided not to transfer the call after all. */
break ;
}
ast_bridge_merge_inhibit ( bridge , - 1 ) ;
ao2_ref ( bridge , - 1 ) ;
if ( xfer_failed ) {
ast_hangup ( peer ) ;
if ( ! ast_check_hangup_locked ( bridge_channel - > chan ) ) {
ast_stream_and_wait ( bridge_channel - > chan , fail_sound , AST_DIGIT_NONE ) ;
}
ast_bridge_channel_write_unhold ( bridge_channel ) ;
}
return 0 ;
}
enum set_touch_variables_res {
SET_TOUCH_SUCCESS ,
SET_TOUCH_UNSET ,
@ -909,8 +494,6 @@ static int unload_module(void)
static int load_module ( void )
{
ast_bridge_features_register ( AST_BRIDGE_BUILTIN_BLINDTRANSFER , feature_blind_transfer , NULL ) ;
ast_bridge_features_register ( AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER , feature_attended_transfer , NULL ) ;
ast_bridge_features_register ( AST_BRIDGE_BUILTIN_HANGUP , feature_hangup , NULL ) ;
ast_bridge_features_register ( AST_BRIDGE_BUILTIN_AUTOMON , feature_automonitor , NULL ) ;
ast_bridge_features_register ( AST_BRIDGE_BUILTIN_AUTOMIXMON , feature_automixmonitor , NULL ) ;