#include "asterisk/test.h"
#include "asterisk/bridging.h"
#include "asterisk/bridging_basic.h"
+#include "asterisk/features_config.h"
+
+/* BUGBUG TEST_FRAMEWORK is disabled because parking tests no longer work. */
+#undef TEST_FRAMEWORK
/*
* Party A - transferee
<para>Bridge together two channels already in the PBX.</para>
</description>
</manager>
- <function name="FEATURE" language="en_US">
- <synopsis>
- Get or set a feature option on a channel.
- </synopsis>
- <syntax>
- <parameter name="option_name" required="true">
- <para>The allowed values are:</para>
- <enumlist>
- <enum name="parkingtime"><para>Specified in seconds.</para></enum>
- <enum name="inherit"><para>Inherit feature settings made in FEATURE or FEATUREMAP to child channels.</para></enum>
- </enumlist>
- </parameter>
- </syntax>
- <description>
- <para>When this function is used as a read, it will get the current
- value of the specified feature option for this channel. It will be
- the value of this option configured in features.conf if a channel specific
- value has not been set. This function can also be used to set a channel
- specific value for the supported feature options.</para>
- </description>
- <see-also>
- <ref type="function">FEATUREMAP</ref>
- </see-also>
- </function>
- <function name="FEATUREMAP" language="en_US">
- <synopsis>
- Get or set a feature map to a given value on a specific channel.
- </synopsis>
- <syntax>
- <parameter name="feature_name" required="true">
- <para>The allowed values are:</para>
- <enumlist>
- <enum name="atxfer"><para>Attended Transfer</para></enum>
- <enum name="blindxfer"><para>Blind Transfer</para></enum>
- <enum name="automon"><para>Auto Monitor</para></enum>
- <enum name="disconnect"><para>Call Disconnect</para></enum>
- <enum name="parkcall"><para>Park Call</para></enum>
- <enum name="automixmon"><para>Auto MixMonitor</para></enum>
- </enumlist>
- </parameter>
- </syntax>
- <description>
- <para>When this function is used as a read, it will get the current
- digit sequence mapped to the specified feature for this channel. This
- value will be the one configured in features.conf if a channel specific
- value has not been set. This function can also be used to set a channel
- specific value for a feature mapping.</para>
- </description>
- <see-also>
- <ref type="function">FEATURE</ref>
- </see-also>
- </function>
<managerEvent language="en_US" name="ParkedCallTimeOut">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a parked call times out.</synopsis>
#define DEFAULT_PARK_TIME 45000 /*!< ms */
#define DEFAULT_PARK_EXTENSION "700"
-#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 /*!< ms */
-#define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000 /*!< ms */
-#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 /*!< ms */
-#define DEFAULT_ATXFER_DROP_CALL 0 /*!< Do not drop call. */
-#define DEFAULT_ATXFER_LOOP_DELAY 10000 /*!< ms */
-#define DEFAULT_ATXFER_CALLBACK_RETRIES 2
#define DEFAULT_COMEBACK_CONTEXT "parkedcallstimeout"
#define DEFAULT_COMEBACK_TO_ORIGIN 1
#define DEFAULT_COMEBACK_DIAL_TIME 30
/* TODO Scrape all of the parking stuff out of features.c */
-struct feature_group_exten {
- AST_LIST_ENTRY(feature_group_exten) entry;
- AST_DECLARE_STRING_FIELDS(
- AST_STRING_FIELD(exten);
- );
- struct ast_call_feature *feature;
-};
-
-struct feature_group {
- AST_LIST_ENTRY(feature_group) entry;
- AST_DECLARE_STRING_FIELDS(
- AST_STRING_FIELD(gname);
- );
- AST_LIST_HEAD_NOLOCK(, feature_group_exten) features;
-};
-
-static AST_RWLIST_HEAD_STATIC(feature_groups, feature_group);
-
typedef enum {
FEATURE_INTERPRET_DETECT, /* Used by ast_feature_detect */
FEATURE_INTERPRET_DO, /* Used by feature_interpret */
static const char *parkedcall = "ParkedCall";
-static char pickup_ext[AST_MAX_EXTENSION]; /*!< Call pickup extension */
-
/*! Parking lot access ramp dialplan usage entry. */
struct parking_dp_ramp {
/*! Next node in the parking lot spaces dialplan list. */
static struct ast_parkinglot *default_parkinglot;
/*! Force a config reload to reload regardless of config file timestamp. */
+#ifdef TEST_FRAMEWORK
static int force_reload_load;
+#endif
-static int parkedplay = 0; /*!< Who to play courtesytone to when someone picks up a parked call. */
static int parkeddynamic = 0; /*!< Enable creation of parkinglots dynamically */
-static char courtesytone[256]; /*!< Courtesy tone used to pickup parked calls and on-touch-record */
-static char xfersound[256]; /*!< Call transfer sound */
-static char xferfailsound[256]; /*!< Call transfer failure sound */
-static char pickupsound[256]; /*!< Pickup sound */
-static char pickupfailsound[256]; /*!< Pickup failure sound */
/*!
* \brief Context for parking dialback to parker.
static int adsipark;
-static int transferdigittimeout;
-static int featuredigittimeout;
-
-static int atxfernoanswertimeout;
-static unsigned int atxferdropcall;
-static unsigned int atxferloopdelay;
-static unsigned int atxfercallbackretries;
-
static char *registrar = "features"; /*!< Registrar for operations */
/*! PARK_APP_NAME application arguments */
return get_parking_exten(exten_str, chan, context) ? 1 : 0;
}
-const char *ast_pickup_ext(void)
-{
- return pickup_ext;
-}
-
struct ast_bridge_thread_obj
{
struct ast_bridge_config bconfig;
ast_channel_priority_set(chan, pri);
}
-/*!
- * \brief Check goto on transfer
- * \param chan
- *
- * Check if channel has 'GOTO_ON_BLINDXFR' set, if not exit.
- * When found make sure the types are compatible. Check if channel is valid
- * if so start the new channel else hangup the call.
- */
-static void check_goto_on_transfer(struct ast_channel *chan)
-{
- struct ast_channel *xferchan;
- const char *val;
- char *goto_on_transfer;
- char *x;
-
- ast_channel_lock(chan);
- val = pbx_builtin_getvar_helper(chan, "GOTO_ON_BLINDXFR");
- if (ast_strlen_zero(val)) {
- ast_channel_unlock(chan);
- return;
- }
- goto_on_transfer = ast_strdupa(val);
- ast_channel_unlock(chan);
-
- ast_debug(1, "Attempting GOTO_ON_BLINDXFR=%s for %s.\n", val, ast_channel_name(chan));
-
- xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", ast_channel_linkedid(chan), 0,
- "%s", ast_channel_name(chan));
- if (!xferchan) {
- return;
- }
-
- /* Make formats okay */
- ast_format_copy(ast_channel_readformat(xferchan), ast_channel_readformat(chan));
- ast_format_copy(ast_channel_writeformat(xferchan), ast_channel_writeformat(chan));
-
- if (ast_channel_masquerade(xferchan, chan)) {
- /* Failed to setup masquerade. */
- ast_hangup(xferchan);
- return;
- }
-
- for (x = goto_on_transfer; *x; ++x) {
- if (*x == '^') {
- *x = ',';
- }
- }
- ast_parseable_goto(xferchan, goto_on_transfer);
- ast_channel_state_set(xferchan, AST_STATE_UP);
- ast_clear_flag(ast_channel_flags(xferchan), AST_FLAGS_ALL);
- ast_channel_clear_softhangup(xferchan, AST_SOFTHANGUP_ALL);
-
- ast_do_masquerade(xferchan);
- if (ast_pbx_start(xferchan)) {
- /* Failed to start PBX. */
- ast_hangup(xferchan);
- }
-}
-
+#if 0
static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
const char *caller_name, struct ast_channel *requestor,
struct ast_channel *transferee, const char *type, struct ast_format_cap *cap, const char *addr,
int timeout, int *outstate, const char *language);
+#endif
static const struct ast_datastore_info channel_app_data_datastore = {
.type = "Channel appdata datastore",
.destroy = ast_free_ptr,
};
+#if 0
static int set_chan_app_data(struct ast_channel *chan, const char *src_app_data)
{
struct ast_datastore *datastore;
ast_channel_datastore_add(chan, datastore);
return 0;
}
+#endif
+#if 0
/*!
* \brief bridge the call
* \param data thread bridge.
return NULL;
}
+#endif
+#if 0
/*!
* \brief create thread for the bridging call
* \param tobj
ast_free(tobj);
}
}
+#endif
/*!
* \brief Announce call parking by ADSI
return pu;
}
-static unsigned int get_parkingtime(struct ast_channel *chan, struct ast_parkinglot *parkinglot);
-
/* Park a call */
static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, struct ast_park_call_args *args)
{
}
pu->start = ast_tvnow();
- pu->parkingtime = (args->timeout > 0) ? args->timeout : get_parkingtime(chan, pu->parkinglot);
+ /* XXX This line was changed to not use get_parkingtime. This is just a placeholder message, because
+ * likely this entire function is going away.
+ */
+ pu->parkingtime = args->timeout;
if (args->extout)
*(args->extout) = pu->parkingnum;
return masq_park_call(rchan, peer, &args);
}
+#if 0
static int finishup(struct ast_channel *chan)
{
ast_indicate(chan, AST_CONTROL_UNHOLD);
return ast_autoservice_stop(chan);
}
+#endif
+#if 0
/*!
* \internal
* \brief Builtin transfer park call helper.
return res ? AST_FEATURE_RETURN_SUCCESS : -1;
}
+#endif
+#if 0
/*!
* \brief set caller and callee according to the direction
* \param caller, callee, peer, chan, sense
*caller = chan;
}
}
+#endif
+#if 0
/*!
* \brief support routing for one touch call parking
* \param chan channel parking call
set_peers(&parker, &parkee, peer, chan, sense);
return masq_park_call(parkee, parker, &args) ? AST_FEATURE_RETURN_SUCCESS : -1;
}
+#endif
/*!
* \internal
return 0;
}
+#if 0
/*!
* \internal
* \brief Play file to specified channels.
return 0;
}
+#endif
+#if 0
/*!
* \brief Play message to both caller and callee in bridged call, plays synchronously, autoservicing the
* other channel during the message, so please don't use this for very long messages
return play_message_to_chans(caller_chan, callee_chan, 0, "automon message",
audiofile);
}
+#endif
+#if 0
/*!
* \brief Monitor a channel by DTMF
* \param chan channel requesting monitor
return AST_FEATURE_RETURN_SUCCESS;
}
+#endif
+#if 0
static int builtin_automixmonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
{
char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL;
pbx_builtin_setvar_helper(caller_chan, "TOUCH_MIXMONITOR_OUTPUT", touch_filename);
return AST_FEATURE_RETURN_SUCCESS;
}
+#endif
+#if 0
static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
{
ast_verb(4, "User hit '%s' to disconnect call.\n", code);
return AST_FEATURE_RETURN_HANGUP;
}
+#endif
+#if 0
/*!
* \brief Find the context for the transfer
* \param transferer
}
return s;
}
+#endif
-/*!
- * \brief Blind transfer user to another extension
- * \param chan channel to be transfered
- * \param peer channel initiated blind transfer
- * \param config
- * \param code
- * \param data
- * \param sense feature options
- *
- * Place chan on hold, check if transferred to parkinglot extension,
- * otherwise check extension exists and transfer caller.
- * \retval AST_FEATURE_RETURN_SUCCESS.
- * \retval -1 on failure.
- */
-static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
-{
- struct ast_channel *transferer;
- struct ast_channel *transferee;
- struct ast_exten *park_exten;
- const char *transferer_real_context;
- char xferto[256] = "";
- int res;
-
- ast_debug(1, "Executing Blind Transfer %s, %s (sense=%d) \n", ast_channel_name(chan), ast_channel_name(peer), sense);
- set_peers(&transferer, &transferee, peer, chan, sense);
- transferer_real_context = ast_strdupa(real_ctx(transferer, transferee));
-
- /* Start autoservice on transferee while we talk to the transferer */
- ast_autoservice_start(transferee);
- ast_indicate(transferee, AST_CONTROL_HOLD);
-
- /* Transfer */
- res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
- if (res < 0) {
- finishup(transferee);
- return -1; /* error ? */
- }
- if (res > 0) { /* If they've typed a digit already, handle it */
- xferto[0] = (char) res;
- }
-
- res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
- if (res < 0) { /* hangup or error, (would be 0 for invalid and 1 for valid) */
- finishup(transferee);
- return -1;
- }
- if (res == 0) {
- if (xferto[0]) {
- ast_log(LOG_WARNING, "Extension '%s' does not exist in context '%s'\n",
- xferto, transferer_real_context);
- } else {
- /* Does anyone care about this case? */
- ast_log(LOG_WARNING, "No digits dialed.\n");
- }
- ast_stream_and_wait(transferer, "pbx-invalid", "");
- finishup(transferee);
- return AST_FEATURE_RETURN_SUCCESS;
- }
-
- park_exten = get_parking_exten(xferto, transferer, transferer_real_context);
- if (park_exten) {
- /* We are transfering the transferee to a parking lot. */
- return xfer_park_call_helper(transferee, transferer, park_exten);
- }
-
- /* Do blind transfer. */
- ast_verb(3, "Blind transferring %s to '%s' (context %s) priority 1\n",
- ast_channel_name(transferee), xferto, transferer_real_context);
- ast_cel_report_event(transferer, AST_CEL_BLINDTRANSFER, NULL, xferto, transferee);
- pbx_builtin_setvar_helper(transferer, "BLINDTRANSFER", ast_channel_name(transferee));
- pbx_builtin_setvar_helper(transferee, "BLINDTRANSFER", ast_channel_name(transferer));
- finishup(transferee);
- ast_channel_lock(transferer);
- if (!ast_channel_cdr(transferer)) {
- /* this code should never get called (in a perfect world) */
- ast_channel_cdr_set(transferer, ast_cdr_alloc());
- if (ast_channel_cdr(transferer)) {
- ast_cdr_init(ast_channel_cdr(transferer), transferer); /* initialize our channel's cdr */
- ast_cdr_start(ast_channel_cdr(transferer));
- }
- }
- ast_channel_unlock(transferer);
- if (ast_channel_cdr(transferer)) {
- struct ast_cdr *swap = ast_channel_cdr(transferer);
-
- ast_debug(1,
- "transferer=%s; transferee=%s; lastapp=%s; lastdata=%s; chan=%s; dstchan=%s\n",
- ast_channel_name(transferer), ast_channel_name(transferee), ast_channel_cdr(transferer)->lastapp,
- ast_channel_cdr(transferer)->lastdata, ast_channel_cdr(transferer)->channel,
- ast_channel_cdr(transferer)->dstchannel);
- ast_debug(1, "TRANSFEREE; lastapp=%s; lastdata=%s, chan=%s; dstchan=%s\n",
- ast_channel_cdr(transferee)->lastapp, ast_channel_cdr(transferee)->lastdata, ast_channel_cdr(transferee)->channel,
- ast_channel_cdr(transferee)->dstchannel);
- ast_debug(1, "transferer_real_context=%s; xferto=%s\n",
- transferer_real_context, xferto);
- /* swap cdrs-- it will save us some time & work */
- ast_channel_cdr_set(transferer, ast_channel_cdr(transferee));
- ast_channel_cdr_set(transferee, swap);
- }
-
- res = ast_channel_pbx(transferee) ? AST_FEATURE_RETURN_SUCCESSBREAK : -1;
-
- /* Doh! Use our handy async_goto functions */
- if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) {
- ast_log(LOG_WARNING, "Async goto failed :-(\n");
- res = -1;
- }
- check_goto_on_transfer(transferer);
- return res;
-}
-
+#if 0
/*!
* \brief make channels compatible
* \param c
}
return 0;
}
+#endif
+#if 0
/*!
* \internal
* \brief Builtin attended transfer failed cleanup.
}
ast_party_connected_line_free(connected_line);
}
+#endif
+#if 0
/*!
* \brief Attended transfer
* \param chan transfered user
char *transferer_name;
char *transferer_name_orig;
char *dash;
+ RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
ast_debug(1, "Executing Attended Transfer %s, %s (sense=%d) \n", ast_channel_name(chan), ast_channel_name(peer), sense);
set_peers(&transferer, &transferee, peer, chan, sense);
xferto[0] = (char) res;
}
+ ast_channel_lock(transferer);
+ xfer_cfg = ast_get_chan_features_xfer_config(transferer);
+ ast_channel_unlock(transferer);
+
+ /* XXX All accesses to the xfer_cfg structure after this point are not thread-safe,
+ * but I don't care because this is dead code.
+ */
+
/* this is specific of atxfer */
- res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
+ res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, xfer_cfg->transferdigittimeout);
if (res < 0) { /* hangup or error, (would be 0 for invalid and 1 for valid) */
finishup(transferee);
return -1;
/* Dial party C */
newchan = feature_request_and_dial(transferer, transferer_name_orig, transferer,
transferee, "Local", ast_channel_nativeformats(transferer), xferto,
- atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
+ xfer_cfg->atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate);
if (!ast_check_hangup(transferer)) {
case AST_CONTROL_UNHOLD:/* Caller requested cancel or party C answer timeout. */
case AST_CONTROL_BUSY:
case AST_CONTROL_CONGESTION:
- if (ast_stream_and_wait(transferer, xfersound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xfersound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
}
break;
default:
- if (ast_stream_and_wait(transferer, xferfailsound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xferfailsound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer failed sound!\n");
}
break;
}
if (check_compat(transferer, newchan)) {
- if (ast_stream_and_wait(transferer, xferfailsound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xferfailsound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer failed sound!\n");
}
atxfer_fail_cleanup(transferee, transferer, &connected_line);
if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) {
ast_autoservice_chan_hangup_peer(transferer, newchan);
- if (ast_stream_and_wait(transferer, xfersound, "")) {
+ if (ast_stream_and_wait(transferer, xfer_cfg->xfersound, "")) {
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
}
atxfer_fail_cleanup(transferee, transferer, &connected_line);
/* Transferer (party B) has hung up at this point. Doing blonde transfer. */
ast_debug(1, "Actually doing a blonde transfer.\n");
- if (!newchan && !atxferdropcall) {
+ if (!newchan && !xfer_cfg->atxferdropcall) {
/* Party C is not available, try to call party B back. */
unsigned int tries = 0;
newchan = feature_request_and_dial(transferer, transferer_name_orig,
transferee, transferee, transferer_tech,
ast_channel_nativeformats(transferee), transferer_name,
- atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
+ xfer_cfg->atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
ast_debug(2, "Dial party B result: newchan:%d, outstate:%d\n",
!!newchan, outstate);
if (newchan) {
}
++tries;
- if (atxfercallbackretries <= tries) {
+ if (xfer_cfg->atxfercallbackretries <= tries) {
/* No more callback tries remaining. */
break;
}
- if (atxferloopdelay) {
+ if (xfer_cfg->atxferloopdelay) {
/* Transfer failed, sleeping */
ast_debug(1, "Sleeping for %d ms before retrying atxfer.\n",
- atxferloopdelay);
- ast_safe_sleep(transferee, atxferloopdelay);
+ xfer_cfg->atxferloopdelay);
+ ast_safe_sleep(transferee, xfer_cfg->atxferloopdelay);
if (ast_check_hangup(transferee)) {
ast_party_connected_line_free(&connected_line);
return -1;
newchan = feature_request_and_dial(transferer, transferer_name_orig,
transferer, transferee, "Local",
ast_channel_nativeformats(transferee), xferto,
- atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
+ xfer_cfg->atxfernoanswertimeout, &outstate, ast_channel_language(transferer));
ast_debug(2, "Redial party C result: newchan:%d, outstate:%d\n",
!!newchan, outstate);
if (newchan || ast_check_hangup(transferee)) {
ast_channel_update_connected_line(newchan, &connected_line, NULL);
}
- if (ast_stream_and_wait(newchan, xfersound, ""))
+ if (ast_stream_and_wait(newchan, xfer_cfg->xfersound, ""))
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
bridge_call_thread_launch(tobj);
ast_party_connected_line_free(&connected_line);
return -1;/* The transferee is masqueraded and the original bridged channels can be hungup. */
}
-
-/* add atxfer and automon as undefined so you can only use em if you configure them */
-#define FEATURES_COUNT ARRAY_LEN(builtin_features)
-
-AST_RWLOCK_DEFINE_STATIC(features_lock);
-
-/*! \note This is protected by features_lock. */
-static AST_LIST_HEAD_NOLOCK_STATIC(feature_list, ast_call_feature);
-
-static void ast_wrlock_call_features(void)
-{
- ast_rwlock_wrlock(&features_lock);
-}
-
-void ast_rdlock_call_features(void)
-{
- ast_rwlock_rdlock(&features_lock);
-}
-
-void ast_unlock_call_features(void)
-{
- ast_rwlock_unlock(&features_lock);
-}
-
-/*! \note This is protected by features_lock. */
-static struct ast_call_feature builtin_features[] = {
- { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_PARKCALL, "Park Call", "parkcall", "", "", builtin_parkcall, AST_FEATURE_FLAG_NEEDSDTMF, "" },
- { AST_FEATURE_AUTOMIXMON, "One Touch MixMonitor", "automixmon", "", "", builtin_automixmonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
-};
-
-/*! \brief register new feature into feature_list */
-void ast_register_feature(struct ast_call_feature *feature)
-{
- if (!feature) {
- ast_log(LOG_NOTICE,"You didn't pass a feature!\n");
- return;
- }
-
- ast_wrlock_call_features();
- AST_LIST_INSERT_HEAD(&feature_list, feature, feature_entry);
- ast_unlock_call_features();
-
- ast_verb(2, "Registered Feature '%s'\n",feature->sname);
-}
+#endif
/*!
- * \brief Add new feature group
- * \param fgname feature group name.
+ * \internal
+ * \brief Get the extension for a given builtin feature
*
- * Add new feature group to the feature group list insert at head of list.
- * \note This function MUST be called while feature_groups is locked.
- */
-static struct feature_group *register_group(const char *fgname)
-{
- struct feature_group *fg;
-
- if (!fgname) {
- ast_log(LOG_NOTICE, "You didn't pass a new group name!\n");
- return NULL;
- }
-
- if (!(fg = ast_calloc_with_stringfields(1, struct feature_group, 128))) {
- return NULL;
- }
-
- ast_string_field_set(fg, gname, fgname);
-
- AST_LIST_INSERT_HEAD(&feature_groups, fg, entry);
-
- ast_verb(2, "Registered group '%s'\n", fg->gname);
-
- return fg;
-}
-
-/*!
- * \brief Add feature to group
- * \param fg feature group
- * \param exten
- * \param feature feature to add.
+ * \pre expects features_lock to be readlocked
*
- * Check fg and feature specified, add feature to list
- * \note This function MUST be called while feature_groups is locked.
+ * \retval 0 success
+ * \retval non-zero failiure
*/
-static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature)
-{
- struct feature_group_exten *fge;
-
- if (!fg) {
- ast_log(LOG_NOTICE, "You didn't pass a group!\n");
- return;
- }
-
- if (!feature) {
- ast_log(LOG_NOTICE, "You didn't pass a feature!\n");
- return;
- }
-
- if (!(fge = ast_calloc_with_stringfields(1, struct feature_group_exten, 128))) {
- return;
- }
-
- ast_string_field_set(fge, exten, S_OR(exten, feature->exten));
-
- fge->feature = feature;
-
- AST_LIST_INSERT_HEAD(&fg->features, fge, entry);
-
- ast_verb(2, "Registered feature '%s' for group '%s' at exten '%s'\n",
- feature->sname, fg->gname, fge->exten);
-}
-
-void ast_unregister_feature(struct ast_call_feature *feature)
+static int builtin_feature_get_exten(struct ast_channel *chan, const char *feature_name,
+ char *buf, size_t len)
{
- if (!feature) {
- return;
- }
+ SCOPED_CHANNELLOCK(lock, chan);
- ast_wrlock_call_features();
- AST_LIST_REMOVE(&feature_list, feature, feature_entry);
- ast_unlock_call_features();
-
- ast_free(feature);
+ return ast_get_builtin_feature(chan, feature_name, buf, len);
}
-/*! \brief Remove all features in the list */
-static void ast_unregister_features(void)
+static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
{
- struct ast_call_feature *feature;
+/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */
+ ast_clear_flag(config, AST_FLAGS_ALL);
- ast_wrlock_call_features();
- while ((feature = AST_LIST_REMOVE_HEAD(&feature_list, feature_entry))) {
- ast_free(feature);
+ if (ast_test_flag(&config->features_caller, AST_FEATURE_DTMF_MASK)) {
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
}
- ast_unlock_call_features();
-}
-
-/*!
- * \internal
- * \brief find a dynamic call feature by name
- * \pre Expects features_lock to be at least readlocked
- */
-static struct ast_call_feature *find_dynamic_feature(const char *name)
-{
- struct ast_call_feature *tmp;
-
- AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
- if (!strcasecmp(tmp->sname, name)) {
- break;
- }
+ if (ast_test_flag(&config->features_callee, AST_FEATURE_DTMF_MASK)) {
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
}
- return tmp;
-}
+ if (!(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
+ RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
-/*! \brief Remove all feature groups in the list */
-static void ast_unregister_groups(void)
-{
- struct feature_group *fg;
- struct feature_group_exten *fge;
-
- AST_RWLIST_WRLOCK(&feature_groups);
- while ((fg = AST_LIST_REMOVE_HEAD(&feature_groups, entry))) {
- while ((fge = AST_LIST_REMOVE_HEAD(&fg->features, entry))) {
- ast_string_field_free_memory(fge);
- ast_free(fge);
+ ast_channel_lock(chan);
+ applicationmap = ast_get_chan_applicationmap(chan);
+ ast_channel_unlock(chan);
+
+ if (!applicationmap) {
+ return;
}
- ast_string_field_free_memory(fg);
- ast_free(fg);
+ /* If an applicationmap exists for this channel at all, then the channel needs the DTMF flag set */
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
}
- AST_RWLIST_UNLOCK(&feature_groups);
}
-/*!
- * \brief Find a group by name
- * \param name feature name
- * \retval feature group on success.
- * \retval NULL on failure.
- */
-static struct feature_group *find_group(const char *name)
-{
- struct feature_group *fg = NULL;
-
- AST_LIST_TRAVERSE(&feature_groups, fg, entry) {
- if (!strcasecmp(fg->gname, name))
- break;
- }
-
- return fg;
-}
-
-/*!
- * \internal
- * \pre Expects features_lock to be at least readlocked
- */
-struct ast_call_feature *ast_find_call_feature(const char *name)
-{
- int x;
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (!strcasecmp(name, builtin_features[x].sname))
- return &builtin_features[x];
- }
-
- return find_dynamic_feature(name);
-}
-
-struct feature_exten {
- char sname[FEATURE_SNAME_LEN];
- char exten[FEATURE_MAX_LEN];
-};
-
-struct feature_datastore {
- struct ao2_container *feature_map;
-
- /*!
- * \brief specified in seconds, stored in milliseconds
- *
- * \todo XXX This isn't pretty. At some point it would be nice to have all
- * of the global / [general] options in a config object that we store here
- * instead of handling each one manually.
- *
- * \note If anything gets added here, don't forget to update
- * feature_ds_duplicate, as well.
- * */
- unsigned int parkingtime;
- unsigned int parkingtime_is_set:1;
-};
-
-static int feature_exten_hash(const void *obj, int flags)
-{
- const struct feature_exten *fe = obj;
- const char *sname = obj;
-
- return ast_str_hash(flags & OBJ_KEY ? sname : fe->sname);
-}
-
-static int feature_exten_cmp(void *obj, void *arg, int flags)
-{
- const struct feature_exten *fe = obj, *fe2 = arg;
- const char *sname = arg;
-
- return !strcmp(fe->sname, flags & OBJ_KEY ? sname : fe2->sname) ?
- CMP_MATCH | CMP_STOP : 0;
-}
-
-static void feature_ds_destroy(void *data)
-{
- struct feature_datastore *feature_ds = data;
-
- if (feature_ds->feature_map) {
- ao2_ref(feature_ds->feature_map, -1);
- feature_ds->feature_map = NULL;
- }
-
- ast_free(feature_ds);
-}
-
-static void *feature_ds_duplicate(void *data)
-{
- struct feature_datastore *old_ds = data;
- struct feature_datastore *new_ds;
-
- if (!(new_ds = ast_calloc(1, sizeof(*new_ds)))) {
- return NULL;
- }
-
- if (old_ds->feature_map) {
- ao2_ref(old_ds->feature_map, +1);
- new_ds->feature_map = old_ds->feature_map;
- }
-
- new_ds->parkingtime = old_ds->parkingtime;
- new_ds->parkingtime_is_set = old_ds->parkingtime_is_set;
-
- return new_ds;
-}
-
-static const struct ast_datastore_info feature_ds_info = {
- .type = "FEATURE",
- .destroy = feature_ds_destroy,
- .duplicate = feature_ds_duplicate,
-};
-
+#if 0
/*!
* \internal
- * \brief Find or create feature datastore on a channel
+ * \brief Get feature and dial.
+ *
+ * \param caller Channel to represent as the calling channel for the dialed channel.
+ * \param caller_name Original caller channel name.
+ * \param requestor Channel to say is requesting the dial (usually the caller).
+ * \param transferee Channel that the dialed channel will be transferred to.
+ * \param type Channel technology type to dial.
+ * \param format Codec formats for dialed channel.
+ * \param addr destination of the call
+ * \param timeout Time limit for dialed channel to answer in ms. Must be greater than zero.
+ * \param outstate Status of dialed channel if unsuccessful.
+ * \param language Language of the caller.
+ *
+ * \note
+ * outstate can be:
+ * 0, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION,
+ * AST_CONTROL_ANSWER, or AST_CONTROL_UNHOLD. If
+ * AST_CONTROL_UNHOLD then the caller channel cancelled the
+ * transfer or the dialed channel did not answer before the
+ * timeout.
+ *
+ * \details
+ * Request channel, set channel variables, initiate call,
+ * check if they want to disconnect, go into loop, check if timeout has elapsed,
+ * check if person to be transfered hung up, check for answer break loop,
+ * set cdr return channel.
*
- * \pre chan is locked
+ * \retval Channel Connected channel for transfer.
+ * \retval NULL on failure to get third party connected.
*
- * \return the data on the FEATURE datastore, or NULL on error
+ * \note This is similar to __ast_request_and_dial() in channel.c
*/
-static struct feature_datastore *get_feature_ds(struct ast_channel *chan)
+static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
+ const char *caller_name, struct ast_channel *requestor,
+ struct ast_channel *transferee, const char *type, struct ast_format_cap *cap, const char *addr,
+ int timeout, int *outstate, const char *language)
{
- struct feature_datastore *feature_ds;
- struct ast_datastore *ds;
-
- if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
- feature_ds = ds->data;
- return feature_ds;
- }
-
- if (!(feature_ds = ast_calloc(1, sizeof(*feature_ds)))) {
- return NULL;
- }
-
- if (!(feature_ds->feature_map = ao2_container_alloc(7, feature_exten_hash, feature_exten_cmp))) {
- feature_ds_destroy(feature_ds);
- return NULL;
- }
+ int state = 0;
+ int cause = 0;
+ int to;
+ int caller_hungup;
+ int transferee_hungup;
+ struct ast_channel *chan;
+ struct ast_channel *monitor_chans[3];
+ struct ast_channel *active_channel;
+ int res;
+ int ready = 0;
+ struct timeval started;
+ int x, len = 0;
+ char disconnect_code[AST_FEATURE_MAX_LEN];
+ char *dialed_code = NULL;
+ struct ast_format_cap *tmp_cap;
+ struct ast_format best_audio_fmt;
+ struct ast_frame *f;
+ int disconnect_res;
+ AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
- if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) {
- feature_ds_destroy(feature_ds);
+ tmp_cap = ast_format_cap_alloc_nolock();
+ if (!tmp_cap) {
+ if (outstate) {
+ *outstate = 0;
+ }
return NULL;
}
+ ast_best_codec(cap, &best_audio_fmt);
+ ast_format_cap_add(tmp_cap, &best_audio_fmt);
- ds->data = feature_ds;
-
- ast_channel_datastore_add(chan, ds);
-
- return feature_ds;
-}
-
-static struct ast_datastore *get_feature_chan_ds(struct ast_channel *chan)
-{
- struct ast_datastore *ds;
-
- if (!(ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
- /* Hasn't been created yet. Trigger creation. */
- get_feature_ds(chan);
- ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL);
- }
-
- return ds;
-}
-
-/*!
- * \internal
- * \brief Get the extension for a given builtin feature
- *
- * \pre expects features_lock to be readlocked
- *
- * \retval 0 success
- * \retval non-zero failiure
- */
-static int builtin_feature_get_exten(struct ast_channel *chan, const char *feature_name,
- char *buf, size_t len)
-{
- struct ast_call_feature *feature;
- struct feature_datastore *feature_ds;
- struct feature_exten *fe = NULL;
-
- *buf = '\0';
+ caller_hungup = ast_check_hangup(caller);
- if (!(feature = ast_find_call_feature(feature_name))) {
- return -1;
+ if (!(chan = ast_request(type, tmp_cap, requestor, addr, &cause))) {
+ ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, addr);
+ switch (cause) {
+ case AST_CAUSE_BUSY:
+ state = AST_CONTROL_BUSY;
+ break;
+ case AST_CAUSE_CONGESTION:
+ state = AST_CONTROL_CONGESTION;
+ break;
+ default:
+ state = 0;
+ break;
+ }
+ goto done;
}
- ast_copy_string(buf, feature->exten, len);
-
- ast_unlock_call_features();
+ ast_channel_language_set(chan, language);
+ ast_channel_inherit_variables(caller, chan);
+ pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller_name);
ast_channel_lock(chan);
- if ((feature_ds = get_feature_ds(chan))) {
- fe = ao2_find(feature_ds->feature_map, feature_name, OBJ_KEY);
- }
+ ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(requestor));
ast_channel_unlock(chan);
- ast_rdlock_call_features();
-
- if (fe) {
- ao2_lock(fe);
- ast_copy_string(buf, fe->exten, len);
- ao2_unlock(fe);
- ao2_ref(fe, -1);
- fe = NULL;
- }
-
- return 0;
-}
-
-/*!
- * \brief exec an app by feature
- * \param chan,peer,config,code,sense,data
- *
- * Find a feature, determine which channel activated
- * \retval AST_FEATURE_RETURN_NO_HANGUP_PEER
- * \retval -1 error.
- * \retval -2 when an application cannot be found.
- */
-static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data)
-{
- struct ast_app *app;
- struct ast_call_feature *feature = data;
- struct ast_channel *work, *idle;
- int res;
-
- if (!feature) { /* shouldn't ever happen! */
- ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n");
- return -1;
- }
-
- if (sense == FEATURE_SENSE_CHAN) {
- if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
- return AST_FEATURE_RETURN_KEEPTRYING;
- if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
- work = chan;
- idle = peer;
- } else {
- work = peer;
- idle = chan;
- }
- } else {
- if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
- return AST_FEATURE_RETURN_KEEPTRYING;
- if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
- work = peer;
- idle = chan;
- } else {
- work = chan;
- idle = peer;
+ if (ast_call(chan, addr, timeout)) {
+ ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, addr);
+ switch (ast_channel_hangupcause(chan)) {
+ case AST_CAUSE_BUSY:
+ state = AST_CONTROL_BUSY;
+ break;
+ case AST_CAUSE_CONGESTION:
+ state = AST_CONTROL_CONGESTION;
+ break;
+ default:
+ state = 0;
+ break;
}
+ goto done;
}
- if (!(app = pbx_findapp(feature->app))) {
- ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app);
- return -2;
- }
-
- ast_autoservice_start(idle);
- ast_autoservice_ignore(idle, AST_FRAME_DTMF_END);
-
- pbx_builtin_setvar_helper(work, "DYNAMIC_PEERNAME", ast_channel_name(idle));
- pbx_builtin_setvar_helper(idle, "DYNAMIC_PEERNAME", ast_channel_name(work));
- pbx_builtin_setvar_helper(work, "DYNAMIC_FEATURENAME", feature->sname);
- pbx_builtin_setvar_helper(idle, "DYNAMIC_FEATURENAME", feature->sname);
-
- if (!ast_strlen_zero(feature->moh_class))
- ast_moh_start(idle, feature->moh_class, NULL);
-
- res = pbx_exec(work, app, feature->app_args);
-
- if (!ast_strlen_zero(feature->moh_class))
- ast_moh_stop(idle);
-
- ast_autoservice_stop(idle);
+ /* support dialing of the featuremap disconnect code while performing an attended tranfer */
+ ast_channel_lock(chan);
+ disconnect_res = ast_get_builtin_feature(chan, "disconnect",
+ disconnect_code, sizeof(disconnect_code));
+ ast_channel_unlock(chan);
- if (res) {
- return AST_FEATURE_RETURN_SUCCESSBREAK;
+ if (!disconnect_res) {
+ len = strlen(disconnect_code) + 1;
+ dialed_code = ast_alloca(len);
+ memset(dialed_code, 0, len);
}
- return AST_FEATURE_RETURN_SUCCESS; /*! \todo XXX should probably return res */
-}
-
-static void unmap_features(void)
-{
- int x;
-
- ast_wrlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++)
- strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
- ast_unlock_call_features();
-}
-
-static int remap_feature(const char *name, const char *value)
-{
- int x, res = -1;
-
- ast_wrlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (strcasecmp(builtin_features[x].sname, name))
- continue;
- ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
- res = 0;
- break;
- }
- ast_unlock_call_features();
+ x = 0;
+ started = ast_tvnow();
+ to = timeout;
+ AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
- return res;
-}
+ ast_poll_channel_add(caller, chan);
-/*!
- * \brief Helper function for feature_interpret and ast_feature_detect
- * \param chan,peer,config,code,sense,dynamic_features_buf,features,operation,feature
- *
- * Lock features list, browse for code, unlock list
- * If a feature is found and the operation variable is set, that feature's
- * operation is executed. The first feature found is copied to the feature parameter.
- * \retval res on success.
- * \retval -1 on failure.
- */
-static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel *peer,
- struct ast_bridge_config *config, const char *code, int sense, const struct ast_str *dynamic_features_buf,
- struct ast_flags *features, feature_interpret_op operation, struct ast_call_feature *feature)
-{
- int x;
- struct feature_group *fg = NULL;
- struct feature_group_exten *fge;
- struct ast_call_feature *tmpfeature;
- char *tmp, *tok;
- int res = AST_FEATURE_RETURN_PASSDIGITS;
- int feature_detected = 0;
+ transferee_hungup = 0;
+ while (!ast_check_hangup(transferee) && (ast_channel_state(chan) != AST_STATE_UP)) {
+ int num_chans = 0;
- if (!(peer && chan && config) && operation == FEATURE_INTERPRET_DO) {
- return -1; /* can not run feature operation */
- }
+ monitor_chans[num_chans++] = transferee;
+ monitor_chans[num_chans++] = chan;
+ if (!caller_hungup) {
+ if (ast_check_hangup(caller)) {
+ caller_hungup = 1;
- ast_rdlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- char feature_exten[FEATURE_MAX_LEN] = "";
+#if defined(ATXFER_NULL_TECH)
+ /* Change caller's name to ensure that it will remain unique. */
+ set_new_chan_name(caller);
- if (!ast_test_flag(features, builtin_features[x].feature_mask)) {
- continue;
+ /*
+ * Get rid of caller's physical technology so it is free for
+ * other calls.
+ */
+ set_kill_chan_tech(caller);
+#endif /* defined(ATXFER_NULL_TECH) */
+ } else {
+ /* caller is not hungup so monitor it. */
+ monitor_chans[num_chans++] = caller;
+ }
}
- if (builtin_feature_get_exten(chan, builtin_features[x].sname, feature_exten, sizeof(feature_exten))) {
- continue;
+ /* see if the timeout has been violated */
+ if (ast_tvdiff_ms(ast_tvnow(), started) > timeout) {
+ state = AST_CONTROL_UNHOLD;
+ ast_log(LOG_NOTICE, "We exceeded our AT-timeout for %s\n", ast_channel_name(chan));
+ break; /*doh! timeout*/
}
- /* Feature is up for consideration */
-
- if (!strcmp(feature_exten, code)) {
- ast_debug(3, "Feature detected: fname=%s sname=%s exten=%s\n", builtin_features[x].fname, builtin_features[x].sname, feature_exten);
- if (operation == FEATURE_INTERPRET_CHECK) {
- res = AST_FEATURE_RETURN_SUCCESS; /* We found something */
- } else if (operation == FEATURE_INTERPRET_DO) {
- res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
- ast_test_suite_event_notify("FEATURE_DETECTION",
- "Result: success\r\n"
- "Feature: %s",
- builtin_features[x].sname);
+ active_channel = ast_waitfor_n(monitor_chans, num_chans, &to);
+ if (!active_channel)
+ continue;
+
+ f = NULL;
+ if (transferee == active_channel) {
+ struct ast_frame *dup_f;
+
+ f = ast_read(transferee);
+ if (f == NULL) { /*doh! where'd he go?*/
+ transferee_hungup = 1;
+ state = 0;
+ break;
}
- if (feature) {
- memcpy(feature, &builtin_features[x], sizeof(*feature));
+ if (ast_is_deferrable_frame(f)) {
+ dup_f = ast_frisolate(f);
+ if (dup_f) {
+ if (dup_f == f) {
+ f = NULL;
+ }
+ AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
+ }
}
- feature_detected = 1;
- break;
- } else if (!strncmp(feature_exten, code, strlen(code))) {
- if (res == AST_FEATURE_RETURN_PASSDIGITS) {
- res = AST_FEATURE_RETURN_STOREDIGITS;
+ } else if (chan == active_channel) {
+ if (!ast_strlen_zero(ast_channel_call_forward(chan))) {
+ state = 0;
+ ast_autoservice_start(transferee);
+ chan = ast_call_forward(caller, chan, NULL, tmp_cap, NULL, &state);
+ ast_autoservice_stop(transferee);
+ if (!chan) {
+ break;
+ }
+ continue;
+ }
+ f = ast_read(chan);
+ if (f == NULL) { /*doh! where'd he go?*/
+ switch (ast_channel_hangupcause(chan)) {
+ case AST_CAUSE_BUSY:
+ state = AST_CONTROL_BUSY;
+ break;
+ case AST_CAUSE_CONGESTION:
+ state = AST_CONTROL_CONGESTION;
+ break;
+ default:
+ state = 0;
+ break;
+ }
+ break;
}
- }
- }
-
- if (operation == FEATURE_INTERPRET_CHECK && x == FEATURES_COUNT) {
- ast_test_suite_event_notify("FEATURE_DETECTION",
- "Result: fail");
- }
-
- ast_unlock_call_features();
-
- if (!dynamic_features_buf || !ast_str_strlen(dynamic_features_buf) || feature_detected) {
- return res;
- }
- tmp = ast_str_buffer(dynamic_features_buf);
+ if (f->frametype == AST_FRAME_CONTROL) {
+ if (f->subclass.integer == AST_CONTROL_RINGING) {
+ ast_verb(3, "%s is ringing\n", ast_channel_name(chan));
+ ast_indicate(caller, AST_CONTROL_RINGING);
+ } else if (f->subclass.integer == AST_CONTROL_BUSY) {
+ state = f->subclass.integer;
+ ast_verb(3, "%s is busy\n", ast_channel_name(chan));
+ ast_indicate(caller, AST_CONTROL_BUSY);
+ ast_frfree(f);
+ break;
+ } else if (f->subclass.integer == AST_CONTROL_INCOMPLETE) {
+ ast_verb(3, "%s dialed incomplete extension %s; ignoring\n", ast_channel_name(chan), ast_channel_exten(chan));
+ } else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
+ state = f->subclass.integer;
+ ast_verb(3, "%s is congested\n", ast_channel_name(chan));
+ ast_indicate(caller, AST_CONTROL_CONGESTION);
+ ast_frfree(f);
+ break;
+ } else if (f->subclass.integer == AST_CONTROL_ANSWER) {
+ /* This is what we are hoping for */
+ state = f->subclass.integer;
+ ast_frfree(f);
+ ready=1;
+ break;
+ } else if (f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
+ ast_indicate_data(caller, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
+ } else if (f->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
+ if (caller_hungup) {
+ struct ast_party_connected_line connected;
- while ((tok = strsep(&tmp, "#"))) {
- AST_RWLIST_RDLOCK(&feature_groups);
- fg = find_group(tok);
- if (fg) {
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- if (!strcmp(fge->exten, code)) {
- if (operation) {
- res = fge->feature->operation(chan, peer, config, code, sense, fge->feature);
+ /* Just save it for the transfer. */
+ ast_party_connected_line_set_init(&connected, ast_channel_connected(caller));
+ res = ast_connected_line_parse_data(f->data.ptr, f->datalen,
+ &connected);
+ if (!res) {
+ ast_channel_set_connected_line(caller, &connected, NULL);
+ }
+ ast_party_connected_line_free(&connected);
+ } else {
+ ast_autoservice_start(transferee);
+ if (ast_channel_connected_line_sub(chan, caller, f, 1) &&
+ ast_channel_connected_line_macro(chan, caller, f, 1, 1)) {
+ ast_indicate_data(caller, AST_CONTROL_CONNECTED_LINE,
+ f->data.ptr, f->datalen);
+ }
+ ast_autoservice_stop(transferee);
+ }
+ } else if (f->subclass.integer == AST_CONTROL_REDIRECTING) {
+ if (!caller_hungup) {
+ ast_autoservice_start(transferee);
+ if (ast_channel_redirecting_sub(chan, caller, f, 1) &&
+ ast_channel_redirecting_macro(chan, caller, f, 1, 1)) {
+ ast_indicate_data(caller, AST_CONTROL_REDIRECTING,
+ f->data.ptr, f->datalen);
+ }
+ ast_autoservice_stop(transferee);
}
- if (feature) {
- memcpy(feature, fge->feature, sizeof(*feature));
+ } else if (f->subclass.integer != -1
+ && f->subclass.integer != AST_CONTROL_PROGRESS
+ && f->subclass.integer != AST_CONTROL_PROCEEDING) {
+ ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass.integer);
+ }
+ /* else who cares */
+ } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
+ ast_write(caller, f);
+ }
+ } else if (caller == active_channel) {
+ f = ast_read(caller);
+ if (f) {
+ if (f->frametype == AST_FRAME_DTMF && dialed_code) {
+ dialed_code[x++] = f->subclass.integer;
+ dialed_code[x] = '\0';
+ if (strlen(dialed_code) == len) {
+ x = 0;
+ } else if (x && strncmp(dialed_code, disconnect_code, x)) {
+ x = 0;
+ dialed_code[x] = '\0';
}
- if (res != AST_FEATURE_RETURN_KEEPTRYING) {
- AST_RWLIST_UNLOCK(&feature_groups);
+ if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
+ /* Caller Canceled the call */
+ state = AST_CONTROL_UNHOLD;
+ ast_frfree(f);
break;
}
- res = AST_FEATURE_RETURN_PASSDIGITS;
- } else if (!strncmp(fge->exten, code, strlen(code))) {
- res = AST_FEATURE_RETURN_STOREDIGITS;
+ } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
+ ast_write(chan, f);
}
}
- if (fge) {
- break;
- }
}
- AST_RWLIST_UNLOCK(&feature_groups);
+ if (f)
+ ast_frfree(f);
+ } /* end while */
- ast_rdlock_call_features();
+ ast_poll_channel_del(caller, chan);
- if (!(tmpfeature = find_dynamic_feature(tok))) {
- ast_unlock_call_features();
- continue;
+ /*
+ * We need to free all the deferred frames, but we only need to
+ * queue the deferred frames if no hangup was received.
+ */
+ ast_channel_lock(transferee);
+ transferee_hungup = (transferee_hungup || ast_check_hangup(transferee));
+ while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) {
+ if (!transferee_hungup) {
+ ast_queue_frame_head(transferee, f);
}
-
- /* Feature is up for consideration */
- if (!strcmp(tmpfeature->exten, code)) {
- ast_verb(3, " Feature Found: %s exten: %s\n",tmpfeature->sname, tok);
- if (operation == FEATURE_INTERPRET_CHECK) {
- res = AST_FEATURE_RETURN_SUCCESS; /* We found something */
- } else if (operation == FEATURE_INTERPRET_DO) {
- res = tmpfeature->operation(chan, peer, config, code, sense, tmpfeature);
- }
- if (feature) {
- memcpy(feature, tmpfeature, sizeof(*feature));
- }
- if (res != AST_FEATURE_RETURN_KEEPTRYING) {
- ast_unlock_call_features();
- break;
- }
- res = AST_FEATURE_RETURN_PASSDIGITS;
- } else if (!strncmp(tmpfeature->exten, code, strlen(code)))
- res = AST_FEATURE_RETURN_STOREDIGITS;
-
- ast_unlock_call_features();
- }
-
- return res;
-}
-
-#if 0//BUGBUG
-/*!
- * \brief Check the dynamic features
- * \param chan,peer,config,code,sense
- *
- * \retval res on success.
- * \retval -1 on failure.
- */
-static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense) {
-
- struct ast_str *dynamic_features_buf;
- const char *peer_dynamic_features, *chan_dynamic_features;
- struct ast_flags features;
- struct ast_call_feature feature;
- int res;
-
- if (sense == FEATURE_SENSE_CHAN) {
- /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
- ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
- }
- else {
- /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
- ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
+ ast_frfree(f);
}
+ ast_channel_unlock(transferee);
- ast_channel_lock(peer);
- peer_dynamic_features = ast_strdupa(S_OR(pbx_builtin_getvar_helper(peer, "DYNAMIC_FEATURES"),""));
- ast_channel_unlock(peer);
-
- ast_channel_lock(chan);
- chan_dynamic_features = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"),""));
- ast_channel_unlock(chan);
-
- if (!(dynamic_features_buf = ast_str_create(128))) {
- return AST_FEATURE_RETURN_PASSDIGITS;
+done:
+ ast_indicate(caller, -1);
+ if (chan && (ready || ast_channel_state(chan) == AST_STATE_UP)) {
+ state = AST_CONTROL_ANSWER;
+ } else if (chan) {
+ ast_hangup(chan);
+ chan = NULL;
}
- ast_str_set(&dynamic_features_buf, 0, "%s%s%s", S_OR(chan_dynamic_features, ""), chan_dynamic_features && peer_dynamic_features ? "#" : "", S_OR(peer_dynamic_features,""));
-
- ast_debug(3, "Feature interpret: chan=%s, peer=%s, code=%s, sense=%d, features=%d, dynamic=%s\n", ast_channel_name(chan), ast_channel_name(peer), code, sense, features.flags, ast_str_buffer(dynamic_features_buf));
-
- res = feature_interpret_helper(chan, peer, config, code, sense, dynamic_features_buf, &features, FEATURE_INTERPRET_DO, &feature);
+ tmp_cap = ast_format_cap_destroy(tmp_cap);
- ast_free(dynamic_features_buf);
+ if (outstate)
+ *outstate = state;
- return res;
+ return chan;
}
#endif
+void ast_channel_log(char *title, struct ast_channel *chan);
-int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) {
- return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature);
-}
-
-#if 0//BUGBUG
-/*! \brief Check if a feature exists */
-static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) {
- struct ast_str *chan_dynamic_features;
- int res;
-
- if (!(chan_dynamic_features = ast_str_create(128))) {
- return AST_FEATURE_RETURN_PASSDIGITS;
+void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this is handy enough to justify keeping it in the source */
+{
+ ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
+ ast_log(LOG_NOTICE, "CHAN: name: %s; appl: %s; data: %s; contxt: %s; exten: %s; pri: %d;\n",
+ ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
+ ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+ ast_log(LOG_NOTICE, "CHAN: acctcode: %s; dialcontext: %s; amaflags: %x; maccontxt: %s; macexten: %s; macpri: %d;\n",
+ ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
+ ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
+ ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; uniqueID: %s; linkedID:%s\n",
+ ast_channel_masq(chan), ast_channel_masqr(chan),
+ ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
+ if (ast_channel_masqr(chan)) {
+ ast_log(LOG_NOTICE, "CHAN: masquerading as: %s; cdr: %p;\n",
+ ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
}
- ast_channel_lock(chan);
- ast_str_set(&chan_dynamic_features, 0, "%s", S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"),""));
- ast_channel_unlock(chan);
-
- res = feature_interpret_helper(chan, NULL, NULL, code, 0, chan_dynamic_features, features, FEATURE_INTERPRET_CHECK, NULL);
- ast_free(chan_dynamic_features);
-
- return res;
+ ast_log(LOG_NOTICE, "===== done ====\n");
}
-#endif
-static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
+static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
{
- int x;
-
-/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */
-/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */
-/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */
- ast_clear_flag(config, AST_FLAGS_ALL);
-
- ast_rdlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF))
- continue;
-
- if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+ const char *feature;
- if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
+ if (ast_strlen_zero(features)) {
+ return;
}
- ast_unlock_call_features();
-
- if (!(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
- const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
-
- if (dynamic_features) {
- char *tmp = ast_strdupa(dynamic_features);
- char *tok;
- struct ast_call_feature *feature;
- /* while we have a feature */
- while ((tok = strsep(&tmp, "#"))) {
- struct feature_group *fg;
-
- AST_RWLIST_RDLOCK(&feature_groups);
- fg = find_group(tok);
- if (fg) {
- struct feature_group_exten *fge;
-
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- if (ast_test_flag(fge->feature, AST_FEATURE_FLAG_BYCALLER)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
- }
- if (ast_test_flag(fge->feature, AST_FEATURE_FLAG_BYCALLEE)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
- }
- }
- }
- AST_RWLIST_UNLOCK(&feature_groups);
+ for (feature = features; *feature; feature++) {
+ struct ast_flags *party;
- ast_rdlock_call_features();
- if ((feature = find_dynamic_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
- if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
- }
- if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE)) {
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
- }
- }
- ast_unlock_call_features();
- }
+ if (isupper(*feature)) {
+ party = &config->features_caller;
+ } else {
+ party = &config->features_callee;
}
- }
-}
-/*!
- * \internal
- * \brief Get feature and dial.
- *
- * \param caller Channel to represent as the calling channel for the dialed channel.
- * \param caller_name Original caller channel name.
- * \param requestor Channel to say is requesting the dial (usually the caller).
- * \param transferee Channel that the dialed channel will be transferred to.
- * \param type Channel technology type to dial.
- * \param format Codec formats for dialed channel.
- * \param addr destination of the call
- * \param timeout Time limit for dialed channel to answer in ms. Must be greater than zero.
- * \param outstate Status of dialed channel if unsuccessful.
- * \param language Language of the caller.
- *
- * \note
- * outstate can be:
- * 0, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION,
- * AST_CONTROL_ANSWER, or AST_CONTROL_UNHOLD. If
- * AST_CONTROL_UNHOLD then the caller channel cancelled the
- * transfer or the dialed channel did not answer before the
- * timeout.
- *
- * \details
- * Request channel, set channel variables, initiate call,
- * check if they want to disconnect, go into loop, check if timeout has elapsed,
- * check if person to be transfered hung up, check for answer break loop,
- * set cdr return channel.
- *
- * \retval Channel Connected channel for transfer.
- * \retval NULL on failure to get third party connected.
- *
- * \note This is similar to __ast_request_and_dial() in channel.c
- */
-static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
- const char *caller_name, struct ast_channel *requestor,
- struct ast_channel *transferee, const char *type, struct ast_format_cap *cap, const char *addr,
- int timeout, int *outstate, const char *language)
-{
- int state = 0;
- int cause = 0;
- int to;
- int caller_hungup;
- int transferee_hungup;
- struct ast_channel *chan;
- struct ast_channel *monitor_chans[3];
- struct ast_channel *active_channel;
- int res;
- int ready = 0;
- struct timeval started;
- int x, len = 0;
- char *disconnect_code = NULL, *dialed_code = NULL;
- struct ast_format_cap *tmp_cap;
- struct ast_format best_audio_fmt;
- struct ast_frame *f;
- AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
-
- tmp_cap = ast_format_cap_alloc_nolock();
- if (!tmp_cap) {
- if (outstate) {
- *outstate = 0;
- }
- return NULL;
- }
- ast_best_codec(cap, &best_audio_fmt);
- ast_format_cap_add(tmp_cap, &best_audio_fmt);
-
- caller_hungup = ast_check_hangup(caller);
-
- if (!(chan = ast_request(type, tmp_cap, requestor, addr, &cause))) {
- ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, addr);
- switch (cause) {
- case AST_CAUSE_BUSY:
- state = AST_CONTROL_BUSY;
- break;
- case AST_CAUSE_CONGESTION:
- state = AST_CONTROL_CONGESTION;
- break;
- default:
- state = 0;
- break;
- }
- goto done;
- }
-
- ast_channel_language_set(chan, language);
- ast_channel_inherit_variables(caller, chan);
- pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller_name);
-
- ast_channel_lock(chan);
- ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(requestor));
- ast_channel_unlock(chan);
-
- if (ast_call(chan, addr, timeout)) {
- ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, addr);
- switch (ast_channel_hangupcause(chan)) {
- case AST_CAUSE_BUSY:
- state = AST_CONTROL_BUSY;
- break;
- case AST_CAUSE_CONGESTION:
- state = AST_CONTROL_CONGESTION;
- break;
- default:
- state = 0;
- break;
- }
- goto done;
- }
-
- /* support dialing of the featuremap disconnect code while performing an attended tranfer */
- ast_rdlock_call_features();
- for (x = 0; x < FEATURES_COUNT; x++) {
- if (strcasecmp(builtin_features[x].sname, "disconnect"))
- continue;
-
- disconnect_code = builtin_features[x].exten;
- len = strlen(disconnect_code) + 1;
- dialed_code = ast_alloca(len);
- memset(dialed_code, 0, len);
- break;
- }
- ast_unlock_call_features();
- x = 0;
- started = ast_tvnow();
- to = timeout;
- AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
-
- ast_poll_channel_add(caller, chan);
-
- transferee_hungup = 0;
- while (!ast_check_hangup(transferee) && (ast_channel_state(chan) != AST_STATE_UP)) {
- int num_chans = 0;
-
- monitor_chans[num_chans++] = transferee;
- monitor_chans[num_chans++] = chan;
- if (!caller_hungup) {
- if (ast_check_hangup(caller)) {
- caller_hungup = 1;
-
-#if defined(ATXFER_NULL_TECH)
- /* Change caller's name to ensure that it will remain unique. */
- set_new_chan_name(caller);
-
- /*
- * Get rid of caller's physical technology so it is free for
- * other calls.
- */
- set_kill_chan_tech(caller);
-#endif /* defined(ATXFER_NULL_TECH) */
- } else {
- /* caller is not hungup so monitor it. */
- monitor_chans[num_chans++] = caller;
- }
- }
-
- /* see if the timeout has been violated */
- if (ast_tvdiff_ms(ast_tvnow(), started) > timeout) {
- state = AST_CONTROL_UNHOLD;
- ast_log(LOG_NOTICE, "We exceeded our AT-timeout for %s\n", ast_channel_name(chan));
- break; /*doh! timeout*/
- }
-
- active_channel = ast_waitfor_n(monitor_chans, num_chans, &to);
- if (!active_channel)
- continue;
-
- f = NULL;
- if (transferee == active_channel) {
- struct ast_frame *dup_f;
-
- f = ast_read(transferee);
- if (f == NULL) { /*doh! where'd he go?*/
- transferee_hungup = 1;
- state = 0;
- break;
- }
- if (ast_is_deferrable_frame(f)) {
- dup_f = ast_frisolate(f);
- if (dup_f) {
- if (dup_f == f) {
- f = NULL;
- }
- AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
- }
- }
- } else if (chan == active_channel) {
- if (!ast_strlen_zero(ast_channel_call_forward(chan))) {
- state = 0;
- ast_autoservice_start(transferee);
- chan = ast_call_forward(caller, chan, NULL, tmp_cap, NULL, &state);
- ast_autoservice_stop(transferee);
- if (!chan) {
- break;
- }
- continue;
- }
- f = ast_read(chan);
- if (f == NULL) { /*doh! where'd he go?*/
- switch (ast_channel_hangupcause(chan)) {
- case AST_CAUSE_BUSY:
- state = AST_CONTROL_BUSY;
- break;
- case AST_CAUSE_CONGESTION:
- state = AST_CONTROL_CONGESTION;
- break;
- default:
- state = 0;
- break;
- }
- break;
- }
-
- if (f->frametype == AST_FRAME_CONTROL) {
- if (f->subclass.integer == AST_CONTROL_RINGING) {
- ast_verb(3, "%s is ringing\n", ast_channel_name(chan));
- ast_indicate(caller, AST_CONTROL_RINGING);
- } else if (f->subclass.integer == AST_CONTROL_BUSY) {
- state = f->subclass.integer;
- ast_verb(3, "%s is busy\n", ast_channel_name(chan));
- ast_indicate(caller, AST_CONTROL_BUSY);
- ast_frfree(f);
- break;
- } else if (f->subclass.integer == AST_CONTROL_INCOMPLETE) {
- ast_verb(3, "%s dialed incomplete extension %s; ignoring\n", ast_channel_name(chan), ast_channel_exten(chan));
- } else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
- state = f->subclass.integer;
- ast_verb(3, "%s is congested\n", ast_channel_name(chan));
- ast_indicate(caller, AST_CONTROL_CONGESTION);
- ast_frfree(f);
- break;
- } else if (f->subclass.integer == AST_CONTROL_ANSWER) {
- /* This is what we are hoping for */
- state = f->subclass.integer;
- ast_frfree(f);
- ready=1;
- break;
- } else if (f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
- ast_indicate_data(caller, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
- } else if (f->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
- if (caller_hungup) {
- struct ast_party_connected_line connected;
-
- /* Just save it for the transfer. */
- ast_party_connected_line_set_init(&connected, ast_channel_connected(caller));
- res = ast_connected_line_parse_data(f->data.ptr, f->datalen,
- &connected);
- if (!res) {
- ast_channel_set_connected_line(caller, &connected, NULL);
- }
- ast_party_connected_line_free(&connected);
- } else {
- ast_autoservice_start(transferee);
- if (ast_channel_connected_line_sub(chan, caller, f, 1) &&
- ast_channel_connected_line_macro(chan, caller, f, 1, 1)) {
- ast_indicate_data(caller, AST_CONTROL_CONNECTED_LINE,
- f->data.ptr, f->datalen);
- }
- ast_autoservice_stop(transferee);
- }
- } else if (f->subclass.integer == AST_CONTROL_REDIRECTING) {
- if (!caller_hungup) {
- ast_autoservice_start(transferee);
- if (ast_channel_redirecting_sub(chan, caller, f, 1) &&
- ast_channel_redirecting_macro(chan, caller, f, 1, 1)) {
- ast_indicate_data(caller, AST_CONTROL_REDIRECTING,
- f->data.ptr, f->datalen);
- }
- ast_autoservice_stop(transferee);
- }
- } else if (f->subclass.integer != -1
- && f->subclass.integer != AST_CONTROL_PROGRESS
- && f->subclass.integer != AST_CONTROL_PROCEEDING) {
- ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass.integer);
- }
- /* else who cares */
- } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
- ast_write(caller, f);
- }
- } else if (caller == active_channel) {
- f = ast_read(caller);
- if (f) {
- if (f->frametype == AST_FRAME_DTMF) {
- dialed_code[x++] = f->subclass.integer;
- dialed_code[x] = '\0';
- if (strlen(dialed_code) == len) {
- x = 0;
- } else if (x && strncmp(dialed_code, disconnect_code, x)) {
- x = 0;
- dialed_code[x] = '\0';
- }
- if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
- /* Caller Canceled the call */
- state = AST_CONTROL_UNHOLD;
- ast_frfree(f);
- break;
- }
- } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) {
- ast_write(chan, f);
- }
- }
- }
- if (f)
- ast_frfree(f);
- } /* end while */
-
- ast_poll_channel_del(caller, chan);
-
- /*
- * We need to free all the deferred frames, but we only need to
- * queue the deferred frames if no hangup was received.
- */
- ast_channel_lock(transferee);
- transferee_hungup = (transferee_hungup || ast_check_hangup(transferee));
- while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) {
- if (!transferee_hungup) {
- ast_queue_frame_head(transferee, f);
- }
- ast_frfree(f);
- }
- ast_channel_unlock(transferee);
-
-done:
- ast_indicate(caller, -1);
- if (chan && (ready || ast_channel_state(chan) == AST_STATE_UP)) {
- state = AST_CONTROL_ANSWER;
- } else if (chan) {
- ast_hangup(chan);
- chan = NULL;
- }
-
- tmp_cap = ast_format_cap_destroy(tmp_cap);
-
- if (outstate)
- *outstate = state;
-
- return chan;
-}
-
-void ast_channel_log(char *title, struct ast_channel *chan);
-
-void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this is handy enough to justify keeping it in the source */
-{
- ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
- ast_log(LOG_NOTICE, "CHAN: name: %s; appl: %s; data: %s; contxt: %s; exten: %s; pri: %d;\n",
- ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
- ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
- ast_log(LOG_NOTICE, "CHAN: acctcode: %s; dialcontext: %s; amaflags: %x; maccontxt: %s; macexten: %s; macpri: %d;\n",
- ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
- ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
- ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; uniqueID: %s; linkedID:%s\n",
- ast_channel_masq(chan), ast_channel_masqr(chan),
- ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
- if (ast_channel_masqr(chan)) {
- ast_log(LOG_NOTICE, "CHAN: masquerading as: %s; cdr: %p;\n",
- ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
- }
-
- ast_log(LOG_NOTICE, "===== done ====\n");
-}
-
-static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
-{
- const char *feature;
-
- if (ast_strlen_zero(features)) {
- return;
- }
-
- for (feature = features; *feature; feature++) {
- struct ast_flags *party;
-
- if (isupper(*feature)) {
- party = &config->features_caller;
- } else {
- party = &config->features_callee;
- }
-
- switch (tolower(*feature)) {
- case 't' :
- ast_set_flag(party, AST_FEATURE_REDIRECT);
- break;
- case 'k' :
- ast_set_flag(party, AST_FEATURE_PARKCALL);
- break;
- case 'h' :
- ast_set_flag(party, AST_FEATURE_DISCONNECT);
- break;
- case 'w' :
- ast_set_flag(party, AST_FEATURE_AUTOMON);
- break;
- case 'x' :
- ast_set_flag(party, AST_FEATURE_AUTOMIXMON);
- break;
- default :
- ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
- break;
- }
- }
-}
-
-static void add_features_datastores(struct ast_channel *caller, struct ast_channel *callee, struct ast_bridge_config *config)
-{
- if (add_features_datastore(caller, &config->features_caller, &config->features_callee)) {
- /*
- * If we don't return here, then when we do a builtin_atxfer we
- * will copy the disconnect flags over from the atxfer to the
- * callee (Party C).
- */
- return;
- }
-
- add_features_datastore(callee, &config->features_callee, &config->features_caller);
-}
-
-static void clear_dialed_interfaces(struct ast_channel *chan)
-{
- struct ast_datastore *di_datastore;
-
- ast_channel_lock(chan);
- if ((di_datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL))) {
- if (option_debug) {
- ast_log(LOG_DEBUG, "Removing dialed interfaces datastore on %s since we're bridging\n", ast_channel_name(chan));
- }
- if (!ast_channel_datastore_remove(chan, di_datastore)) {
- ast_datastore_free(di_datastore);
- }
- }
- ast_channel_unlock(chan);
-}
-
-void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval start, const char *why)
-{
- int dead;
- long duration;
-
- ast_channel_lock(chan);
- dead = ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
- || (ast_channel_softhangup_internal_flag(chan)
- & ~(AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE));
- ast_channel_unlock(chan);
- if (dead) {
- /* Channel is a zombie or a real hangup. */
- return;
- }
-
- duration = ast_tvdiff_ms(ast_tvnow(), start);
- ast_senddigit_end(chan, digit, duration);
- ast_log(LOG_DTMF, "DTMF end '%c' simulated on %s due to %s, duration %ld ms\n",
- digit, ast_channel_name(chan), why, duration);
-}
-
-/*!
- * \internal
- * \brief Setup bridge builtin features.
- * \since 12.0.0
- *
- * \param features Bridge features to setup.
- * \param chan Get features from this channel.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
-{
- struct ast_flags *flags;
- char dtmf[FEATURE_MAX_LEN];
- int res;
-
- ast_channel_lock(chan);
- flags = ast_bridge_features_ds_get(chan);
- ast_channel_unlock(chan);
- if (!flags) {
- return 0;
- }
-
- res = 0;
- ast_rdlock_call_features();
- if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
- /* Add atxfer and blind transfer. */
- builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
-/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
- builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
-/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
- }
- if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) {
- builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
- }
- if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) {
- builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
- }
- if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) {
- builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
- }
- if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) {
- builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf));
- if (!ast_strlen_zero(dtmf)) {
- res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
- NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
- }
- }
- ast_unlock_call_features();
-
-#if 0 /* BUGBUG don't report errors untill all of the builtin features are supported. */
- return res ? -1 : 0;
-#else
- return 0;
-#endif
-}
-
-struct dtmf_hook_run_app {
- /*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
- unsigned int flags;
- /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */
- int moh_offset;
- /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
- int app_args_offset;
- /*! Application name to run. */
- char app_name[0];
-};
-
-/*!
- * \internal
- * \brief Setup bridge dynamic features.
- * \since 12.0.0
- *
- * \param bridge The bridge that the channel is part of
- * \param bridge_channel Channel executing the feature
- * \param hook_pvt Private data passed in when the hook was created
- *
- * \retval 0 Keep the callback hook.
- * \retval -1 Remove the callback hook.
- */
-static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
-{
- struct dtmf_hook_run_app *pvt = hook_pvt;
- void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
-
- if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
- run_it = ast_bridge_channel_write_app;
- } else {
- run_it = ast_bridge_channel_run_app;
- }
-
-/*
- * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
- *
- * This would replace DYNAMIC_PEERNAME which is redundant with
- * BRIDGEPEER anyway. The value of DYNAMIC_WHO_TRIGGERED is
- * really useful in the case of a multi-party bridge.
- */
- run_it(bridge_channel, pvt->app_name,
- pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
- pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
- return 0;
-}
-
-/*!
- * \internal
- * \brief Add a dynamic DTMF feature hook to the bridge features.
- * \since 12.0.0
- *
- * \param features Bridge features to setup.
- * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
- * \param dtmf DTMF trigger sequence.
- * \param app_name Dialplan application name to run.
- * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
- * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
-{
- struct dtmf_hook_run_app *app_data;
- size_t len_name = strlen(app_name) + 1;
- size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
- size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
- size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
-
- /* Fill in application run hook data. */
- app_data = ast_malloc(len_data);
- if (!app_data) {
- return -1;
- }
- app_data->flags = flags;
- app_data->app_args_offset = len_args ? len_name : 0;
- app_data->moh_offset = len_moh ? len_name + len_args : 0;
- strcpy(app_data->app_name, app_name);/* Safe */
- if (len_args) {
- strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
- }
- if (len_moh) {
- strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
- }
-
- return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
- app_data, ast_free_ptr, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
-}
-
-/*!
- * \internal
- * \brief Setup bridge dynamic features.
- * \since 12.0.0
- *
- * \param features Bridge features to setup.
- * \param chan Get features from this channel.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
-{
- const char *feat;
- char *dynamic_features = NULL;
- char *tok;
- int res;
-
- ast_channel_lock(chan);
- feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
- if (!ast_strlen_zero(feat)) {
- dynamic_features = ast_strdupa(feat);
- }
- ast_channel_unlock(chan);
- if (!dynamic_features) {
- return 0;
- }
-
-/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
- res = 0;
- while ((tok = strsep(&dynamic_features, "#"))) {
- struct feature_group *fg;
- struct ast_call_feature *feature;
-
- AST_RWLIST_RDLOCK(&feature_groups);
- fg = find_group(tok);
- if (fg) {
- struct feature_group_exten *fge;
-
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten,
- fge->feature->app, fge->feature->app_args, fge->feature->moh_class);
- }
- }
- AST_RWLIST_UNLOCK(&feature_groups);
-
- ast_rdlock_call_features();
- feature = find_dynamic_feature(tok);
- if (feature) {
- res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten,
- feature->app, feature->app_args, feature->moh_class);
- }
- ast_unlock_call_features();
- }
- return res;
-}
-
-/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
-/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
-int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
-{
- int res = 0;
-
- /* Always pass through any DTMF digits. */
- bridge_channel->features->dtmf_passthrough = 1;
-
- res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
- res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
-
- return res;
-}
-
-static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
-{
- if (config->end_sound) {
- ast_string_field_set(limits, duration_sound, config->end_sound);
- }
-
- if (config->warning_sound) {
- ast_string_field_set(limits, warning_sound, config->warning_sound);
- }
-
- if (config->start_sound) {
- ast_string_field_set(limits, connect_sound, config->start_sound);
- }
-
- limits->frequency = config->warning_freq;
- limits->warning = config->play_warning;
-}
-
-/*!
- * \internal brief Setup limit hook structures on calls that need limits
- *
- * \param config ast_bridge_config which provides the limit data
- * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
- * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
- */
-static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
-{
- if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
- bridge_config_set_limits_warning_values(config, caller_limits);
- }
-
- if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
- bridge_config_set_limits_warning_values(config, callee_limits);
- }
-
- caller_limits->duration = config->timelimit;
- callee_limits->duration = config->timelimit;
-}
-
-/*!
- * \internal
- * \brief Check if Monitor needs to be started on a channel.
- * \since 12.0.0
- *
- * \param chan The bridge considers this channel the caller.
- * \param peer The bridge considers this channel the callee.
- *
- * \return Nothing
- */
-static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *peer)
-{
- const char *value;
- const char *monitor_args = NULL;
- struct ast_channel *monitor_chan = NULL;
-
- ast_channel_lock(chan);
- value = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR");
- if (!ast_strlen_zero(value)) {
- monitor_args = ast_strdupa(value);
- monitor_chan = chan;
- }
- ast_channel_unlock(chan);
- if (!monitor_chan) {
- ast_channel_lock(peer);
- value = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR");
- if (!ast_strlen_zero(value)) {
- monitor_args = ast_strdupa(value);
- monitor_chan = peer;
- }
- ast_channel_unlock(peer);
- }
- if (monitor_chan) {
- struct ast_app *monitor_app;
-
- monitor_app = pbx_findapp("Monitor");
- if (monitor_app) {
- pbx_exec(monitor_chan, monitor_app, monitor_args);
- }
- }
-}
-
-/*!
- * \internal
- * \brief Send the peer channel on its way on bridge start failure.
- * \since 12.0.0
- *
- * \param chan Chan to put into autoservice.
- * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
- *
- * \return Nothing
- */
-static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
-{
- if (ast_after_bridge_goto_setup(peer)
- || ast_pbx_start(peer)) {
- ast_autoservice_chan_hangup_peer(chan, peer);
- }
-}
-
-static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config,
- struct ast_bridge_features *chan_features, struct ast_bridge_features *peer_features)
-{
- int res;
-
-/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
- pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
- pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
-
-/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
- /* Clear any BLINDTRANSFER since the transfer has completed. */
- pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
- pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
-
- set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
- add_features_datastores(chan, peer, config);
-
- /*
- * This is an interesting case. One example is if a ringing
- * channel gets redirected to an extension that picks up a
- * parked call. This will make sure that the call taken out of
- * parking gets told that the channel it just got bridged to is
- * still ringing.
- */
- if (ast_channel_state(chan) == AST_STATE_RINGING
- && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
- ast_indicate(peer, AST_CONTROL_RINGING);
- }
-
- bridge_check_monitor(chan, peer);
-
- set_config_flags(chan, config);
-
- /* Answer if need be */
- if (ast_channel_state(chan) != AST_STATE_UP) {
- if (ast_raw_answer(chan, 1)) {
- return -1;
- }
- }
-
-#ifdef FOR_DEBUG
- /* show the two channels and cdrs involved in the bridge for debug & devel purposes */
- ast_channel_log("Pre-bridge CHAN Channel info", chan);
- ast_channel_log("Pre-bridge PEER Channel info", peer);
-#endif
- /* two channels are being marked as linked here */
- ast_channel_set_linkgroup(chan, peer);
-
- /*
- * If we are bridging a call, stop worrying about forwarding
- * loops. We presume that if a call is being bridged, that the
- * humans in charge know what they're doing. If they don't,
- * well, what can we do about that?
- */
- clear_dialed_interfaces(chan);
- clear_dialed_interfaces(peer);
-
- res = 0;
- ast_channel_lock(chan);
- res |= ast_bridge_features_ds_set(chan, &config->features_caller);
- ast_channel_unlock(chan);
- ast_channel_lock(peer);
- res |= ast_bridge_features_ds_set(peer, &config->features_callee);
- ast_channel_unlock(peer);
-
- if (res) {
- return -1;
- }
-
- if (config->timelimit) {
- struct ast_bridge_features_limits call_duration_limits_chan;
- struct ast_bridge_features_limits call_duration_limits_peer;
- int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
-
- if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
- ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
-
- return -1;
- }
-
- if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
- ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
- ast_bridge_features_limits_destroy(&call_duration_limits_chan);
-
- return -1;
- }
-
- bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
-
- if (ast_bridge_features_set_limits(chan_features, &call_duration_limits_chan, 0)) {
- abandon_call = 1;
- }
- if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
- abandon_call = 1;
- }
-
- /* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
- ast_bridge_features_limits_destroy(&call_duration_limits_chan);
- ast_bridge_features_limits_destroy(&call_duration_limits_peer);
-
- if (abandon_call) {
- ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
- return -1;
- }
- }
-
- return 0;
-}
-
-/*!
- * \brief bridge the call and set CDR
- *
- * \param chan The bridge considers this channel the caller.
- * \param peer The bridge considers this channel the callee.
- * \param config Configuration for this bridge.
- *
- * Set start time, check for two channels,check if monitor on
- * check for feature activation, create new CDR
- * \retval res on success.
- * \retval -1 on failure to bridge.
- */
-int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
-{
- int res;
- struct ast_bridge *bridge;
- struct ast_bridge_features chan_features;
- struct ast_bridge_features *peer_features;
-
- /* Setup features. */
- res = ast_bridge_features_init(&chan_features);
- peer_features = ast_bridge_features_new();
- if (res || !peer_features) {
- ast_bridge_features_destroy(peer_features);
- ast_bridge_features_cleanup(&chan_features);
- bridge_failed_peer_goto(chan, peer);
- return -1;
- }
-
- if (pre_bridge_setup(chan, peer, config, &chan_features, peer_features)) {
- ast_bridge_features_destroy(peer_features);
- ast_bridge_features_cleanup(&chan_features);
- bridge_failed_peer_goto(chan, peer);
- return -1;
- }
-
- /* Create bridge */
- bridge = ast_bridge_basic_new();
- if (!bridge) {
- ast_bridge_features_destroy(peer_features);
- ast_bridge_features_cleanup(&chan_features);
- bridge_failed_peer_goto(chan, peer);
- return -1;
- }
-
- /* Put peer into the bridge */
- if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
- ast_bridge_destroy(bridge);
- ast_bridge_features_cleanup(&chan_features);
- bridge_failed_peer_goto(chan, peer);
- return -1;
- }
-
- /* Join bridge */
- ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
-
- /*
- * If the bridge was broken for a hangup that isn't real, then
- * don't run the h extension, because the channel isn't really
- * hung up. This should really only happen with
- * AST_SOFTHANGUP_ASYNCGOTO.
- */
- res = -1;
- ast_channel_lock(chan);
- if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
- res = 0;
- }
- ast_channel_unlock(chan);
-
- ast_bridge_features_cleanup(&chan_features);
-
-/* BUGBUG this is used by Dial and FollowMe for CDR information. By Queue for Queue stats like CDRs. */
- if (res && config->end_bridge_callback) {
- config->end_bridge_callback(config->end_bridge_callback_data);
- }
-
- return res;
-}
-
-/*! \brief Output parking event to manager */
-static void post_manager_event(const char *s, struct parkeduser *pu)
-{
- manager_event(EVENT_FLAG_CALL, s,
- "Exten: %s\r\n"
- "Channel: %s\r\n"
- "Parkinglot: %s\r\n"
- "CallerIDNum: %s\r\n"
- "CallerIDName: %s\r\n"
- "ConnectedLineNum: %s\r\n"
- "ConnectedLineName: %s\r\n"
- "UniqueID: %s\r\n",
- pu->parkingexten,
- ast_channel_name(pu->chan),
- pu->parkinglot->name,
- S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
- S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
- S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
- S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
- ast_channel_uniqueid(pu->chan)
- );
-}
-
-static char *callback_dialoptions(struct ast_flags *features_callee, struct ast_flags *features_caller, char *options, size_t len)
-{
- int i = 0;
- enum {
- OPT_CALLEE_REDIRECT = 't',
- OPT_CALLER_REDIRECT = 'T',
- OPT_CALLEE_AUTOMON = 'w',
- OPT_CALLER_AUTOMON = 'W',
- OPT_CALLEE_DISCONNECT = 'h',
- OPT_CALLER_DISCONNECT = 'H',
- OPT_CALLEE_PARKCALL = 'k',
- OPT_CALLER_PARKCALL = 'K',
- };
-
- memset(options, 0, len);
- if (ast_test_flag(features_caller, AST_FEATURE_REDIRECT) && i < len) {
- options[i++] = OPT_CALLER_REDIRECT;
- }
- if (ast_test_flag(features_caller, AST_FEATURE_AUTOMON) && i < len) {
- options[i++] = OPT_CALLER_AUTOMON;
- }
- if (ast_test_flag(features_caller, AST_FEATURE_DISCONNECT) && i < len) {
- options[i++] = OPT_CALLER_DISCONNECT;
- }
- if (ast_test_flag(features_caller, AST_FEATURE_PARKCALL) && i < len) {
- options[i++] = OPT_CALLER_PARKCALL;
- }
-
- if (ast_test_flag(features_callee, AST_FEATURE_REDIRECT) && i < len) {
- options[i++] = OPT_CALLEE_REDIRECT;
- }
- if (ast_test_flag(features_callee, AST_FEATURE_AUTOMON) && i < len) {
- options[i++] = OPT_CALLEE_AUTOMON;
- }
- if (ast_test_flag(features_callee, AST_FEATURE_DISCONNECT) && i < len) {
- options[i++] = OPT_CALLEE_DISCONNECT;
- }
- if (ast_test_flag(features_callee, AST_FEATURE_PARKCALL) && i < len) {
- options[i++] = OPT_CALLEE_PARKCALL;
- }
-
- return options;
-}
-
-/*!
- * \internal
- * \brief Run management on a parked call.
- *
- * \note The parkinglot parkings list is locked on entry.
- *
- * \retval TRUE if the parking completed.
- */
-static int manage_parked_call(struct parkeduser *pu, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
-{
- struct ast_channel *chan = pu->chan; /* shorthand */
- int tms; /* timeout for this item */
- int x; /* fd index in channel */
-
- tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
- if (tms > pu->parkingtime) {
- /*
- * Call has been parked too long.
- * Stop entertaining the caller.
- */
- switch (pu->hold_method) {
- case AST_CONTROL_HOLD:
- ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
- break;
- case AST_CONTROL_RINGING:
- ast_indicate(pu->chan, -1);
- break;
- default:
+ switch (tolower(*feature)) {
+ case 't' :
+ ast_set_flag(party, AST_FEATURE_REDIRECT);
+ break;
+ case 'k' :
+ ast_set_flag(party, AST_FEATURE_PARKCALL);
+ break;
+ case 'h' :
+ ast_set_flag(party, AST_FEATURE_DISCONNECT);
+ break;
+ case 'w' :
+ ast_set_flag(party, AST_FEATURE_AUTOMON);
+ break;
+ case 'x' :
+ ast_set_flag(party, AST_FEATURE_AUTOMIXMON);
+ break;
+ default :
+ ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
break;
- }
- pu->hold_method = 0;
-
- /* Get chan, exten from derived kludge */
- if (pu->peername[0]) {
- char *peername;
- char *dash;
- char *peername_flat; /* using something like DAHDI/52 for an extension name is NOT a good idea */
- char parkingslot[AST_MAX_EXTENSION]; /* buffer for parkinglot slot number */
- int i;
-
- peername = ast_strdupa(pu->peername);
- dash = strrchr(peername, '-');
- if (dash) {
- *dash = '\0';
- }
-
- peername_flat = ast_strdupa(peername);
- for (i = 0; peername_flat[i]; i++) {
- if (peername_flat[i] == '/') {
- peername_flat[i] = '_';
- }
- }
-
- if (!ast_context_find_or_create(NULL, NULL, parking_con_dial, registrar)) {
- ast_log(LOG_ERROR,
- "Parking dial context '%s' does not exist and unable to create\n",
- parking_con_dial);
- } else {
- char returnexten[AST_MAX_EXTENSION];
- char comebackdialtime[AST_MAX_EXTENSION];
- struct ast_datastore *features_datastore;
- struct ast_dial_features *dialfeatures;
-
- if (!strncmp(peername, "Parked/", 7)) {
- peername += 7;
- }
-
- ast_channel_lock(chan);
- features_datastore = ast_channel_datastore_find(chan, &dial_features_info,
- NULL);
- if (features_datastore && (dialfeatures = features_datastore->data)) {
- char buf[MAX_DIAL_FEATURE_OPTIONS] = {0,};
-
- snprintf(returnexten, sizeof(returnexten), "%s,%u,%s", peername,
- pu->parkinglot->cfg.comebackdialtime,
- callback_dialoptions(&dialfeatures->peer_features,
- &dialfeatures->my_features, buf, sizeof(buf)));
- } else { /* Existing default */
- ast_log(LOG_NOTICE, "Dial features not found on %s, using default!\n",
- ast_channel_name(chan));
- snprintf(returnexten, sizeof(returnexten), "%s,%u,t", peername,
- pu->parkinglot->cfg.comebackdialtime);
- }
- ast_channel_unlock(chan);
-
- snprintf(comebackdialtime, sizeof(comebackdialtime), "%u",
- pu->parkinglot->cfg.comebackdialtime);
- pbx_builtin_setvar_helper(chan, "COMEBACKDIALTIME", comebackdialtime);
-
- pbx_builtin_setvar_helper(chan, "PARKER", peername);
-
- if (ast_add_extension(parking_con_dial, 1, peername_flat, 1, NULL, NULL,
- "Dial", ast_strdup(returnexten), ast_free_ptr, registrar)) {
- ast_log(LOG_ERROR,
- "Could not create parking return dial exten: %s@%s\n",
- peername_flat, parking_con_dial);
- }
- }
-
- snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
- pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parkingslot);
- pbx_builtin_setvar_helper(chan, "PARKEDLOT", pu->parkinglot->name);
-
- if (pu->options_specified) {
- /*
- * Park() was called with overriding return arguments, respect
- * those arguments.
- */
- set_c_e_p(chan, pu->context, pu->exten, pu->priority);
- } else if (pu->parkinglot->cfg.comebacktoorigin) {
- set_c_e_p(chan, parking_con_dial, peername_flat, 1);
- } else {
- /* Handle fallback when extensions don't exist here since that logic was removed from pbx */
- if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1, NULL)) {
- set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1);
- } else if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, "s", 1, NULL)) {
- ast_verb(2, "Can not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
- pu->parkinglot->cfg.comebackcontext, peername_flat, pu->parkinglot->cfg.comebackcontext);
- set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, "s", 1);
- } else {
- ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
- ast_channel_name(chan),
- pu->parkinglot->cfg.comebackcontext, peername_flat,
- pu->parkinglot->cfg.comebackcontext);
- set_c_e_p(chan, "default", "s", 1);
- }
- }
- } else {
- /*
- * They've been waiting too long, send them back to where they
- * came. Theoretically they should have their original
- * extensions and such, but we copy to be on the safe side.
- */
- set_c_e_p(chan, pu->context, pu->exten, pu->priority);
- }
- post_manager_event("ParkedCallTimeOut", pu);
- ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallTimeOut", NULL);
-
- ast_verb(2, "Timeout for %s parked on %d (%s). Returning to %s,%s,%d\n",
- ast_channel_name(pu->chan), pu->parkingnum, pu->parkinglot->name, ast_channel_context(pu->chan),
- ast_channel_exten(pu->chan), ast_channel_priority(pu->chan));
-
- /* Start up the PBX, or hang them up */
- if (ast_pbx_start(chan)) {
- ast_log(LOG_WARNING,
- "Unable to restart the PBX for user on '%s', hanging them up...\n",
- ast_channel_name(pu->chan));
- ast_hangup(chan);
- }
-
- /* And take them out of the parking lot */
- return 1;
- }
-
- /* still within parking time, process descriptors */
- if (pfds) {
- for (x = 0; x < AST_MAX_FDS; x++) {
- struct ast_frame *f;
- int y;
-
- if (!ast_channel_fd_isset(chan, x)) {
- continue; /* nothing on this descriptor */
- }
-
- for (y = 0; y < nfds; y++) {
- if (pfds[y].fd == ast_channel_fd(chan, x)) {
- /* Found poll record! */
- break;
- }
- }
- if (y == nfds) {
- /* Not found */
- continue;
- }
-
- if (!(pfds[y].revents & (POLLIN | POLLERR | POLLPRI))) {
- /* Next x */
- continue;
- }
-
- if (pfds[y].revents & POLLPRI) {
- ast_set_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
- } else {
- ast_clear_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
- }
- ast_channel_fdno_set(chan, x);
-
- /* See if they need servicing */
- f = ast_read(pu->chan);
- /* Hangup? */
- if (!f || (f->frametype == AST_FRAME_CONTROL
- && f->subclass.integer == AST_CONTROL_HANGUP)) {
- if (f) {
- ast_frfree(f);
- }
- post_manager_event("ParkedCallGiveUp", pu);
- ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallGiveUp",
- NULL);
-
- /* There's a problem, hang them up */
- ast_verb(2, "%s got tired of being parked\n", ast_channel_name(chan));
- ast_hangup(chan);
-
- /* And take them out of the parking lot */
- return 1;
- } else {
- /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
- ast_frfree(f);
- if (pu->hold_method == AST_CONTROL_HOLD
- && pu->moh_trys < 3
- && !ast_channel_generatordata(chan)) {
- ast_debug(1,
- "MOH on parked call stopped by outside source. Restarting on channel %s.\n",
- ast_channel_name(chan));
- ast_indicate_data(chan, AST_CONTROL_HOLD,
- S_OR(pu->parkinglot->cfg.mohclass, NULL),
- (!ast_strlen_zero(pu->parkinglot->cfg.mohclass)
- ? strlen(pu->parkinglot->cfg.mohclass) + 1 : 0));
- pu->moh_trys++;
- }
- break;
- }
- } /* End for */
- }
-
- /* mark fds for next round */
- for (x = 0; x < AST_MAX_FDS; x++) {
- if (ast_channel_fd_isset(chan, x)) {
- void *tmp = ast_realloc(*new_pfds,
- (*new_nfds + 1) * sizeof(struct pollfd));
-
- if (!tmp) {
- continue;
- }
- *new_pfds = tmp;
- (*new_pfds)[*new_nfds].fd = ast_channel_fd(chan, x);
- (*new_pfds)[*new_nfds].events = POLLIN | POLLERR | POLLPRI;
- (*new_pfds)[*new_nfds].revents = 0;
- (*new_nfds)++;
- }
- }
- /* Keep track of our shortest wait */
- if (tms < *ms || *ms < 0) {
- *ms = tms;
- }
-
- /* Stay in the parking lot. */
- return 0;
-}
-
-/*! \brief Run management on parkinglots, called once per parkinglot */
-static void manage_parkinglot(struct ast_parkinglot *curlot, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
-{
- struct parkeduser *pu;
- struct ast_context *con;
-
- /* Lock parkings list */
- AST_LIST_LOCK(&curlot->parkings);
- AST_LIST_TRAVERSE_SAFE_BEGIN(&curlot->parkings, pu, list) {
- if (pu->notquiteyet) { /* Pretend this one isn't here yet */
- continue;
- }
- if (manage_parked_call(pu, pfds, nfds, new_pfds, new_nfds, ms)) {
- /* Parking is complete for this call so remove it from the parking lot. */
- con = ast_context_find(pu->parkinglot->cfg.parking_con);
- if (con) {
- if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
- ast_log(LOG_WARNING,
- "Whoa, failed to remove the parking extension %s@%s!\n",
- pu->parkingexten, pu->parkinglot->cfg.parking_con);
- }
- notify_metermaids(pu->parkingexten, pu->parkinglot->cfg.parking_con,
- AST_DEVICE_NOT_INUSE);
- } else {
- ast_log(LOG_WARNING,
- "Whoa, parking lot '%s' context '%s' does not exist.\n",
- pu->parkinglot->name, pu->parkinglot->cfg.parking_con);
- }
- AST_LIST_REMOVE_CURRENT(list);
- parkinglot_unref(pu->parkinglot);
- ast_free(pu);
}
}
- AST_LIST_TRAVERSE_SAFE_END;
- AST_LIST_UNLOCK(&curlot->parkings);
}
-/*!
- * \brief Take care of parked calls and unpark them if needed
- * \param ignore unused var.
- *
- * Start inf loop, lock parking lot, check if any parked channels have gone above timeout
- * if so, remove channel from parking lot and return it to the extension that parked it.
- * Check if parked channel decided to hangup, wait until next FD via select().
- */
-static void *do_parking_thread(void *ignore)
+static void add_features_datastores(struct ast_channel *caller, struct ast_channel *callee, struct ast_bridge_config *config)
{
- struct pollfd *pfds = NULL, *new_pfds = NULL;
- int nfds = 0, new_nfds = 0;
-
- for (;;) {
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
- int ms = -1; /* poll2 timeout, uninitialized */
-
- iter = ao2_iterator_init(parkinglots, 0);
- while ((curlot = ao2_iterator_next(&iter))) {
- manage_parkinglot(curlot, pfds, nfds, &new_pfds, &new_nfds, &ms);
- ao2_ref(curlot, -1);
- }
- ao2_iterator_destroy(&iter);
-
- /* Recycle */
- ast_free(pfds);
- pfds = new_pfds;
- nfds = new_nfds;
- new_pfds = NULL;
- new_nfds = 0;
-
- /* Wait for something to happen */
- ast_poll(pfds, nfds, ms);
- pthread_testcancel();
+ if (add_features_datastore(caller, &config->features_caller, &config->features_callee)) {
+ /*
+ * If we don't return here, then when we do a builtin_atxfer we
+ * will copy the disconnect flags over from the atxfer to the
+ * callee (Party C).
+ */
+ return;
}
- /* If this WERE reached, we'd need to free(pfds) */
- return NULL; /* Never reached */
+
+ add_features_datastore(callee, &config->features_callee, &config->features_caller);
}
-/*! \brief Find parkinglot by name */
-static struct ast_parkinglot *find_parkinglot(const char *name)
+static void clear_dialed_interfaces(struct ast_channel *chan)
{
- struct ast_parkinglot *parkinglot;
-
- if (ast_strlen_zero(name)) {
- return NULL;
- }
+ struct ast_datastore *di_datastore;
- parkinglot = ao2_find(parkinglots, (void *) name, 0);
- if (parkinglot) {
- ast_debug(1, "Found Parking lot: %s\n", parkinglot->name);
+ ast_channel_lock(chan);
+ if ((di_datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL))) {
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "Removing dialed interfaces datastore on %s since we're bridging\n", ast_channel_name(chan));
+ }
+ if (!ast_channel_datastore_remove(chan, di_datastore)) {
+ ast_datastore_free(di_datastore);
+ }
}
-
- return parkinglot;
+ ast_channel_unlock(chan);
}
-/*! \brief Copy parkinglot and store it with new name */
-static struct ast_parkinglot *copy_parkinglot(const char *name, const struct ast_parkinglot *parkinglot)
+void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval start, const char *why)
{
- struct ast_parkinglot *copylot;
-
- if ((copylot = find_parkinglot(name))) { /* Parkinglot with that name already exists */
- ao2_ref(copylot, -1);
- return NULL;
- }
+ int dead;
+ long duration;
- copylot = create_parkinglot(name);
- if (!copylot) {
- return NULL;
+ ast_channel_lock(chan);
+ dead = ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
+ || (ast_channel_softhangup_internal_flag(chan)
+ & ~(AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE));
+ ast_channel_unlock(chan);
+ if (dead) {
+ /* Channel is a zombie or a real hangup. */
+ return;
}
- ast_debug(1, "Building parking lot %s\n", name);
-
- /* Copy the source parking lot configuration. */
- copylot->cfg = parkinglot->cfg;
-
- return copylot;
+ duration = ast_tvdiff_ms(ast_tvnow(), start);
+ ast_senddigit_end(chan, digit, duration);
+ ast_log(LOG_DTMF, "DTMF end '%c' simulated on %s due to %s, duration %ld ms\n",
+ digit, ast_channel_name(chan), why, duration);
}
-AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
- AST_APP_OPTION('r', AST_PARK_OPT_RINGING),
- AST_APP_OPTION('R', AST_PARK_OPT_RANDOMIZE),
- AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
-END_OPTIONS );
-
/*!
- * \brief Unreference parkinglot object.
+ * \internal
+ * \brief Setup bridge builtin features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
*/
-static void parkinglot_unref(struct ast_parkinglot *parkinglot)
-{
- ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name,
- ao2_ref(parkinglot, 0) - 1);
- ao2_ref(parkinglot, -1);
-}
-
-static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot)
-{
- int refcount;
-
- refcount = ao2_ref(parkinglot, +1);
- ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name, refcount + 1);
- return parkinglot;
-}
-
-/*! \brief Destroy a parking lot */
-static void parkinglot_destroy(void *obj)
-{
- struct ast_parkinglot *doomed = obj;
-
- /*
- * No need to destroy parked calls here because any parked call
- * holds a parking lot reference. Therefore the parkings list
- * must be empty.
- */
- ast_assert(AST_LIST_EMPTY(&doomed->parkings));
- AST_LIST_HEAD_DESTROY(&doomed->parkings);
-}
-
-/*! \brief Allocate parking lot structure */
-static struct ast_parkinglot *create_parkinglot(const char *name)
+static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
{
- struct ast_parkinglot *newlot;
+ struct ast_flags *flags;
+ char dtmf[AST_FEATURE_MAX_LEN];
+ int res;
- if (ast_strlen_zero(name)) { /* No name specified */
- return NULL;
+ ast_channel_lock(chan);
+ flags = ast_bridge_features_ds_get(chan);
+ ast_channel_unlock(chan);
+ if (!flags) {
+ return 0;
}
- newlot = ao2_alloc(sizeof(*newlot), parkinglot_destroy);
- if (!newlot)
- return NULL;
-
- ast_copy_string(newlot->name, name, sizeof(newlot->name));
- newlot->cfg.is_invalid = 1;/* No config is set yet. */
- AST_LIST_HEAD_INIT(&newlot->parkings);
-
- return newlot;
-}
-
-/*!
- * \brief Add parking hints for all defined parking spaces.
- * \param context Dialplan context to add the hints.
- * \param start Starting space in parkinglot.
- * \param stop Ending space in parkinglot.
- */
-static void park_add_hints(const char *context, int start, int stop)
-{
- int numext;
- char device[AST_MAX_EXTENSION];
- char exten[10];
-
- for (numext = start; numext <= stop; numext++) {
- snprintf(exten, sizeof(exten), "%d", numext);
- snprintf(device, sizeof(device), "park:%s@%s", exten, context);
- ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+ res = 0;
+ if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
+ /* Add atxfer and blind transfer. */
+ if (!builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf))
+ && !ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+ }
+ if (!builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+ }
+ }
+ if (ast_test_flag(flags, AST_FEATURE_DISCONNECT) &&
+ !builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+ }
+ if (ast_test_flag(flags, AST_FEATURE_PARKCALL) &&
+ !builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+ }
+ if (ast_test_flag(flags, AST_FEATURE_AUTOMON) &&
+ !builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+ }
+ if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON) &&
+ !builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf)) &&
+ !ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
+ NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
-}
-/*! Default configuration for default parking lot. */
-static const struct parkinglot_cfg parkinglot_cfg_default_default = {
- .mohclass = "default",
- .parkext = DEFAULT_PARK_EXTENSION,
- .parking_con = "parkedcalls",
- .parking_start = 701,
- .parking_stop = 750,
- .parkingtime = DEFAULT_PARK_TIME,
- .comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
- .comebackcontext = DEFAULT_COMEBACK_CONTEXT,
- .comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
-};
+#if 0 /* BUGBUG don't report errors untill all of the builtin features are supported. */
+ return res ? -1 : 0;
+#else
+ return 0;
+#endif
+}
-/*! Default configuration for normal parking lots. */
-static const struct parkinglot_cfg parkinglot_cfg_default = {
- .parkext = DEFAULT_PARK_EXTENSION,
- .parkingtime = DEFAULT_PARK_TIME,
- .comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
- .comebackcontext = DEFAULT_COMEBACK_CONTEXT,
- .comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
+struct dtmf_hook_run_app {
+ /*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
+ unsigned int flags;
+ /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */
+ int moh_offset;
+ /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+ int app_args_offset;
+ /*! Application name to run. */
+ char app_name[0];
};
/*!
* \internal
- * \brief Set parking lot feature flag configuration value.
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
*
- * \param pl_name Parking lot name for diagnostic messages.
- * \param param Parameter value to set.
- * \param var Current configuration variable item.
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
*
- * \return Nothing
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
*/
-static void parkinglot_feature_flag_cfg(const char *pl_name, int *param, struct ast_variable *var)
+static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
- ast_debug(1, "Setting parking lot %s %s to %s\n", pl_name, var->name, var->value);
- if (!strcasecmp(var->value, "both")) {
- *param = AST_FEATURE_FLAG_BYBOTH;
- } else if (!strcasecmp(var->value, "caller")) {
- *param = AST_FEATURE_FLAG_BYCALLER;
- } else if (!strcasecmp(var->value, "callee")) {
- *param = AST_FEATURE_FLAG_BYCALLEE;
+ struct dtmf_hook_run_app *pvt = hook_pvt;
+ void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+ if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
+ run_it = ast_bridge_channel_write_app;
+ } else {
+ run_it = ast_bridge_channel_run_app;
}
+
+/*
+ * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
+ *
+ * This would replace DYNAMIC_PEERNAME which is redundant with
+ * BRIDGEPEER anyway. The value of DYNAMIC_WHO_TRIGGERED is
+ * really useful in the case of a multi-party bridge.
+ */
+ run_it(bridge_channel, pvt->app_name,
+ pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
+ pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
+ return 0;
}
/*!
* \internal
- * \brief Read parking lot configuration.
+ * \brief Add a dynamic DTMF feature hook to the bridge features.
+ * \since 12.0.0
*
- * \param pl_name Parking lot name for diagnostic messages.
- * \param cfg Parking lot config to update that is already initialized with defaults.
- * \param var Config variable list.
+ * \param features Bridge features to setup.
+ * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
+ * \param dtmf DTMF trigger sequence.
+ * \param app_name Dialplan application name to run.
+ * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
+ * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
*
* \retval 0 on success.
* \retval -1 on error.
*/
-static int parkinglot_config_read(const char *pl_name, struct parkinglot_cfg *cfg, struct ast_variable *var)
+static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
{
- int error = 0;
-
- while (var) {
- if (!strcasecmp(var->name, "context")) {
- ast_copy_string(cfg->parking_con, var->value, sizeof(cfg->parking_con));
- } else if (!strcasecmp(var->name, "parkext")) {
- ast_copy_string(cfg->parkext, var->value, sizeof(cfg->parkext));
- } else if (!strcasecmp(var->name, "parkext_exclusive")) {
- cfg->parkext_exclusive = ast_true(var->value);
- } else if (!strcasecmp(var->name, "parkinghints")) {
- cfg->parkaddhints = ast_true(var->value);
- } else if (!strcasecmp(var->name, "parkedmusicclass")) {
- ast_copy_string(cfg->mohclass, var->value, sizeof(cfg->mohclass));
- } else if (!strcasecmp(var->name, "parkingtime")) {
- unsigned int parkingtime = 0;
-
- if ((sscanf(var->value, "%30u", &parkingtime) != 1) || parkingtime < 1) {
- ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
- error = -1;
- } else {
- cfg->parkingtime = parkingtime * 1000;
- }
- } else if (!strcasecmp(var->name, "parkpos")) {
- int start = 0;
- int end = 0;
-
- if (sscanf(var->value, "%30d-%30d", &start, &end) != 2) {
- ast_log(LOG_WARNING,
- "Format for parking positions is a-b, where a and b are numbers at line %d of %s\n",
- var->lineno, var->file);
- error = -1;
- } else if (end < start || start <= 0 || end <= 0) {
- ast_log(LOG_WARNING, "Parking range is invalid. Must be a <= b, at line %d of %s\n",
- var->lineno, var->file);
- error = -1;
- } else {
- cfg->parking_start = start;
- cfg->parking_stop = end;
- }
- } else if (!strcasecmp(var->name, "findslot")) {
- cfg->parkfindnext = (!strcasecmp(var->value, "next"));
- } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcalltransfers, var);
- } else if (!strcasecmp(var->name, "parkedcallreparking")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcallreparking, var);
- } else if (!strcasecmp(var->name, "parkedcallhangup")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcallhangup, var);
- } else if (!strcasecmp(var->name, "parkedcallrecording")) {
- parkinglot_feature_flag_cfg(pl_name, &cfg->parkedcallrecording, var);
- } else if (!strcasecmp(var->name, "comebackcontext")) {
- ast_copy_string(cfg->comebackcontext, var->value, sizeof(cfg->comebackcontext));
- } else if (!strcasecmp(var->name, "comebacktoorigin")) {
- cfg->comebacktoorigin = ast_true(var->value);
- } else if (!strcasecmp(var->name, "comebackdialtime")) {
- if ((sscanf(var->value, "%30u", &cfg->comebackdialtime) != 1)
- || (cfg->comebackdialtime < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid comebackdialtime\n", var->value);
- cfg->parkingtime = DEFAULT_COMEBACK_DIAL_TIME;
- }
- }
- var = var->next;
- }
+ struct dtmf_hook_run_app *app_data;
+ size_t len_name = strlen(app_name) + 1;
+ size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+ size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
+ size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
- /* Check for configuration errors */
- if (ast_strlen_zero(cfg->parking_con)) {
- ast_log(LOG_WARNING, "Parking lot %s needs context\n", pl_name);
- error = -1;
- }
- if (ast_strlen_zero(cfg->parkext)) {
- ast_log(LOG_WARNING, "Parking lot %s needs parkext\n", pl_name);
- error = -1;
- }
- if (!cfg->parking_start) {
- ast_log(LOG_WARNING, "Parking lot %s needs parkpos\n", pl_name);
- error = -1;
+ /* Fill in application run hook data. */
+ app_data = ast_malloc(len_data);
+ if (!app_data) {
+ return -1;
}
- if (!cfg->comebacktoorigin && ast_strlen_zero(cfg->comebackcontext)) {
- ast_log(LOG_WARNING, "Parking lot %s has comebacktoorigin set false"
- "but has no comebackcontext.\n",
- pl_name);
- error = -1;
+ app_data->flags = flags;
+ app_data->app_args_offset = len_args ? len_name : 0;
+ app_data->moh_offset = len_moh ? len_name + len_args : 0;
+ strcpy(app_data->app_name, app_name);/* Safe */
+ if (len_args) {
+ strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
}
- if (error) {
- cfg->is_invalid = 1;
+ if (len_moh) {
+ strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
}
- return error;
+ return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
+ app_data, ast_free_ptr, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+}
+
+static int setup_dynamic_feature(void *obj, void *arg, void *data, int flags)
+{
+ struct ast_applicationmap_item *item = obj;
+ struct ast_bridge_features *features = arg;
+ int *res = data;
+
+ /* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (item->name) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
+ *res |= add_dynamic_dtmf_hook(features, item->activate_on_self ? AST_FEATURE_FLAG_ONSELF :
+ AST_FEATURE_FLAG_ONPEER, item->dtmf, item->app, item->app_data,
+ item->moh_class);
+
+ return 0;
}
/*!
* \internal
- * \brief Activate the given parkinglot.
- *
- * \param parkinglot Parking lot to activate.
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
*
- * \details
- * Insert into the dialplan the context, parking lot access
- * extension, and optional dialplan hints.
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
*
* \retval 0 on success.
* \retval -1 on error.
*/
-static int parkinglot_activate(struct ast_parkinglot *parkinglot)
+static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
{
- int disabled = 0;
- char app_data[5 + AST_MAX_CONTEXT];
+ RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
+ int res = 0;
- /* Create Park option list. Must match with struct park_app_args options. */
- if (parkinglot->cfg.parkext_exclusive) {
- /* Specify the parking lot this parking extension parks calls. */
- snprintf(app_data, sizeof(app_data), ",,,,,%s", parkinglot->name);
- } else {
- /* The dialplan must specify which parking lot to use. */
- app_data[0] = '\0';
+ ast_channel_lock(chan);
+ applicationmap = ast_get_chan_applicationmap(chan);
+ ast_channel_unlock(chan);
+ if (!applicationmap) {
+ return 0;
}
- /* Create context */
- if (!ast_context_find_or_create(NULL, NULL, parkinglot->cfg.parking_con, registrar)) {
- ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n",
- parkinglot->cfg.parking_con);
- disabled = 1;
+ ao2_callback_data(applicationmap, 0, setup_dynamic_feature, features, &res);
- /* Add a parking extension into the context */
- } else if (ast_add_extension(parkinglot->cfg.parking_con, 1, parkinglot->cfg.parkext,
- 1, NULL, NULL, parkcall, ast_strdup(app_data), ast_free_ptr, registrar)) {
- ast_log(LOG_ERROR, "Could not create parking lot %s access exten %s@%s\n",
- parkinglot->name, parkinglot->cfg.parkext, parkinglot->cfg.parking_con);
- disabled = 1;
- } else {
- /* Add parking hints */
- if (parkinglot->cfg.parkaddhints) {
- park_add_hints(parkinglot->cfg.parking_con, parkinglot->cfg.parking_start,
- parkinglot->cfg.parking_stop);
- }
+ return res;
+}
- /*
- * XXX Not sure why we should need to notify the metermaids for
- * this exten. It was originally done for the default parking
- * lot entry exten only but should be done for all entry extens
- * if we do it for one.
- */
- /* Notify metermaids about parking lot entry exten state. */
- notify_metermaids(parkinglot->cfg.parkext, parkinglot->cfg.parking_con,
- AST_DEVICE_INUSE);
- }
+/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
+/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
+{
+ int res = 0;
- parkinglot->disabled = disabled;
- return disabled ? -1 : 0;
+ /* Always pass through any DTMF digits. */
+ bridge_channel->features->dtmf_passthrough = 1;
+
+ res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
+ res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
+
+ return res;
}
-/*! \brief Build parkinglot from configuration and chain it in if it doesn't already exist */
-static struct ast_parkinglot *build_parkinglot(const char *pl_name, struct ast_variable *var)
+static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
{
- struct ast_parkinglot *parkinglot;
- const struct parkinglot_cfg *cfg_defaults;
- struct parkinglot_cfg new_cfg;
- int cfg_error;
- int oldparkinglot = 0;
-
- parkinglot = find_parkinglot(pl_name);
- if (parkinglot) {
- oldparkinglot = 1;
- } else {
- parkinglot = create_parkinglot(pl_name);
- if (!parkinglot) {
- return NULL;
- }
- }
- if (!strcmp(parkinglot->name, DEFAULT_PARKINGLOT)) {
- cfg_defaults = &parkinglot_cfg_default_default;
- } else {
- cfg_defaults = &parkinglot_cfg_default;
+ if (config->end_sound) {
+ ast_string_field_set(limits, duration_sound, config->end_sound);
}
- new_cfg = *cfg_defaults;
-
- ast_debug(1, "Building parking lot %s\n", parkinglot->name);
- ao2_lock(parkinglot);
+ if (config->warning_sound) {
+ ast_string_field_set(limits, warning_sound, config->warning_sound);
+ }
- /* Do some config stuff */
- cfg_error = parkinglot_config_read(parkinglot->name, &new_cfg, var);
- if (oldparkinglot) {
- if (cfg_error) {
- /* Bad configuration read. Keep using the original config. */
- ast_log(LOG_WARNING, "Changes to parking lot %s are discarded.\n",
- parkinglot->name);
- cfg_error = 0;
- } else if (!AST_LIST_EMPTY(&parkinglot->parkings)
- && memcmp(&new_cfg, &parkinglot->cfg, sizeof(parkinglot->cfg))) {
- /* Try reloading later when parking lot is empty. */
- ast_log(LOG_WARNING,
- "Parking lot %s has parked calls. Parking lot changes discarded.\n",
- parkinglot->name);
- force_reload_load = 1;
- } else {
- /* Accept the new config */
- parkinglot->cfg = new_cfg;
- }
- } else {
- /* Load the initial parking lot config. */
- parkinglot->cfg = new_cfg;
+ if (config->start_sound) {
+ ast_string_field_set(limits, connect_sound, config->start_sound);
}
- parkinglot->the_mark = 0;
- ao2_unlock(parkinglot);
+ limits->frequency = config->warning_freq;
+ limits->warning = config->play_warning;
+}
- if (cfg_error) {
- /* Only new parking lots could have config errors here. */
- ast_log(LOG_WARNING, "New parking lot %s is discarded.\n", parkinglot->name);
- parkinglot_unref(parkinglot);
- return NULL;
+/*!
+ * \internal brief Setup limit hook structures on calls that need limits
+ *
+ * \param config ast_bridge_config which provides the limit data
+ * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
+ * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
+ */
+static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
+{
+ if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
+ bridge_config_set_limits_warning_values(config, caller_limits);
}
- /* Move it into the list, if it wasn't already there */
- if (!oldparkinglot) {
- ao2_link(parkinglots, parkinglot);
+ if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
+ bridge_config_set_limits_warning_values(config, callee_limits);
}
- parkinglot_unref(parkinglot);
- return parkinglot;
+ caller_limits->duration = config->timelimit;
+ callee_limits->duration = config->timelimit;
}
/*!
* \internal
- * \brief Process an applicationmap section config line.
+ * \brief Check if Monitor needs to be started on a channel.
+ * \since 12.0.0
*
- * \param var Config variable line.
+ * \param chan The bridge considers this channel the caller.
+ * \param peer The bridge considers this channel the callee.
*
* \return Nothing
*/
-static void process_applicationmap_line(struct ast_variable *var)
+static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *peer)
{
- char *tmp_val = ast_strdupa(var->value);
- char *activateon, *new_syn;
- struct ast_call_feature *feature;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(exten);
- AST_APP_ARG(activatedby);
- AST_APP_ARG(app);
- AST_APP_ARG(app_args);
- AST_APP_ARG(moh_class);
- );
+ const char *value;
+ const char *monitor_args = NULL;
+ struct ast_channel *monitor_chan = NULL;
- AST_STANDARD_APP_ARGS(args, tmp_val);
- if ((new_syn = strchr(args.app, '('))) {
- /* New syntax */
- args.moh_class = args.app_args;
- args.app_args = new_syn;
- *args.app_args++ = '\0';
- if (args.app_args[strlen(args.app_args) - 1] == ')') {
- args.app_args[strlen(args.app_args) - 1] = '\0';
+ ast_channel_lock(chan);
+ value = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR");
+ if (!ast_strlen_zero(value)) {
+ monitor_args = ast_strdupa(value);
+ monitor_chan = chan;
+ }
+ ast_channel_unlock(chan);
+ if (!monitor_chan) {
+ ast_channel_lock(peer);
+ value = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR");
+ if (!ast_strlen_zero(value)) {
+ monitor_args = ast_strdupa(value);
+ monitor_chan = peer;
}
+ ast_channel_unlock(peer);
}
+ if (monitor_chan) {
+ struct ast_app *monitor_app;
- activateon = strsep(&args.activatedby, "/");
-
- /*! \todo XXX var_name or app_args ? */
- if (ast_strlen_zero(args.app)
- || ast_strlen_zero(args.exten)
- || ast_strlen_zero(activateon)
- || ast_strlen_zero(var->name)) {
- ast_log(LOG_NOTICE,
- "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",
- args.app, args.exten, activateon, var->name);
- return;
+ monitor_app = pbx_findapp("Monitor");
+ if (monitor_app) {
+ pbx_exec(monitor_chan, monitor_app, monitor_args);
+ }
}
+}
- ast_rdlock_call_features();
- if (find_dynamic_feature(var->name)) {
- ast_unlock_call_features();
- ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n",
- var->name);
- return;
+/*!
+ * \internal
+ * \brief Send the peer channel on its way on bridge start failure.
+ * \since 12.0.0
+ *
+ * \param chan Chan to put into autoservice.
+ * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
+ *
+ * \return Nothing
+ */
+static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
+{
+ if (ast_after_bridge_goto_setup(peer)
+ || ast_pbx_start(peer)) {
+ ast_autoservice_chan_hangup_peer(chan, peer);
}
- ast_unlock_call_features();
+}
- if (!(feature = ast_calloc(1, sizeof(*feature)))) {
- return;
+static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config,
+ struct ast_bridge_features *chan_features, struct ast_bridge_features *peer_features)
+{
+ int res;
+
+/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
+ pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
+ pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
+
+/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
+ /* Clear any BLINDTRANSFER since the transfer has completed. */
+ pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
+ pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
+
+ set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
+ add_features_datastores(chan, peer, config);
+
+ /*
+ * This is an interesting case. One example is if a ringing
+ * channel gets redirected to an extension that picks up a
+ * parked call. This will make sure that the call taken out of
+ * parking gets told that the channel it just got bridged to is
+ * still ringing.
+ */
+ if (ast_channel_state(chan) == AST_STATE_RINGING
+ && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
+ ast_indicate(peer, AST_CONTROL_RINGING);
}
- ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN);
- ast_copy_string(feature->app, args.app, FEATURE_APP_LEN);
- ast_copy_string(feature->exten, args.exten, FEATURE_EXTEN_LEN);
+ bridge_check_monitor(chan, peer);
- if (args.app_args) {
- ast_copy_string(feature->app_args, args.app_args, FEATURE_APP_ARGS_LEN);
- }
+ set_config_flags(chan, config);
- if (args.moh_class) {
- ast_copy_string(feature->moh_class, args.moh_class, FEATURE_MOH_LEN);
+ /* Answer if need be */
+ if (ast_channel_state(chan) != AST_STATE_UP) {
+ if (ast_raw_answer(chan, 1)) {
+ return -1;
+ }
}
- ast_copy_string(feature->exten, args.exten, sizeof(feature->exten));
- feature->operation = feature_exec_app;
- ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF);
-
- /* Allow caller and callee to be specified for backwards compatability */
- if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF);
- } else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER);
- } else {
- ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s',"
- " must be 'self', or 'peer'\n", var->name);
- ast_free(feature);
- return;
- }
+#ifdef FOR_DEBUG
+ /* show the two channels and cdrs involved in the bridge for debug & devel purposes */
+ ast_channel_log("Pre-bridge CHAN Channel info", chan);
+ ast_channel_log("Pre-bridge PEER Channel info", peer);
+#endif
+ /* two channels are being marked as linked here */
+ ast_channel_set_linkgroup(chan, peer);
/*
- * We will parse and require correct syntax for the ActivatedBy
- * option, but the ActivatedBy option is not honored anymore.
+ * If we are bridging a call, stop worrying about forwarding
+ * loops. We presume that if a call is being bridged, that the
+ * humans in charge know what they're doing. If they don't,
+ * well, what can we do about that?
*/
- if (ast_strlen_zero(args.activatedby)) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
- } else if (!strcasecmp(args.activatedby, "caller")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER);
- } else if (!strcasecmp(args.activatedby, "callee")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE);
- } else if (!strcasecmp(args.activatedby, "both")) {
- ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
- } else {
- ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s',"
- " must be 'caller', or 'callee', or 'both'\n", var->name);
- ast_free(feature);
- return;
- }
+ clear_dialed_interfaces(chan);
+ clear_dialed_interfaces(peer);
- ast_register_feature(feature);
+ res = 0;
+ ast_channel_lock(chan);
+ res |= ast_bridge_features_ds_set(chan, &config->features_caller);
+ ast_channel_unlock(chan);
+ ast_channel_lock(peer);
+ res |= ast_bridge_features_ds_set(peer, &config->features_callee);
+ ast_channel_unlock(peer);
- ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n",
- var->name, args.app, args.app_args, args.exten);
-}
+ if (res) {
+ return -1;
+ }
-static int process_config(struct ast_config *cfg)
-{
- int i;
- struct ast_variable *var = NULL;
- struct feature_group *fg = NULL;
- char *ctg;
- static const char * const categories[] = {
- /* Categories in features.conf that are not
- * to be parsed as group categories
- */
- "general",
- "featuremap",
- "applicationmap"
- };
+ if (config->timelimit) {
+ struct ast_bridge_features_limits call_duration_limits_chan;
+ struct ast_bridge_features_limits call_duration_limits_peer;
+ int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
- /* Set general features global defaults. */
- featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
-
- /* Set global call pickup defaults. */
- strcpy(pickup_ext, "*8");
- pickupsound[0] = '\0';
- pickupfailsound[0] = '\0';
-
- /* Set global call transfer defaults. */
- strcpy(xfersound, "beep");
- strcpy(xferfailsound, "beeperr");
- transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
- atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
- atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
- atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
- atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
-
- /* Set global call parking defaults. */
- courtesytone[0] = '\0';
- parkedplay = 0;
- adsipark = 0;
- parkeddynamic = 0;
-
- var = ast_variable_browse(cfg, "general");
- build_parkinglot(DEFAULT_PARKINGLOT, var);
- for (; var; var = var->next) {
- if (!strcasecmp(var->name, "parkeddynamic")) {
- parkeddynamic = ast_true(var->value);
- } else if (!strcasecmp(var->name, "adsipark")) {
- adsipark = ast_true(var->value);
- } else if (!strcasecmp(var->name, "transferdigittimeout")) {
- if ((sscanf(var->value, "%30d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
- transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
- } else {
- transferdigittimeout = transferdigittimeout * 1000;
- }
- } else if (!strcasecmp(var->name, "featuredigittimeout")) {
- if ((sscanf(var->value, "%30d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
- featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
- }
- } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) {
- if ((sscanf(var->value, "%30d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) {
- ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value);
- atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
- } else {
- atxfernoanswertimeout = atxfernoanswertimeout * 1000;
- }
- } else if (!strcasecmp(var->name, "atxferloopdelay")) {
- if ((sscanf(var->value, "%30u", &atxferloopdelay) != 1)) {
- ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value);
- atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
- } else {
- atxferloopdelay *= 1000;
- }
- } else if (!strcasecmp(var->name, "atxferdropcall")) {
- atxferdropcall = ast_true(var->value);
- } else if (!strcasecmp(var->name, "atxfercallbackretries")) {
- if ((sscanf(var->value, "%30u", &atxfercallbackretries) != 1)) {
- ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value);
- atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
- }
- } else if (!strcasecmp(var->name, "courtesytone")) {
- ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
- } else if (!strcasecmp(var->name, "parkedplay")) {
- if (!strcasecmp(var->value, "both")) {
- parkedplay = 2;
- } else if (!strcasecmp(var->value, "parked")) {
- parkedplay = 1;
- } else {
- parkedplay = 0;
- }
- } else if (!strcasecmp(var->name, "xfersound")) {
- ast_copy_string(xfersound, var->value, sizeof(xfersound));
- } else if (!strcasecmp(var->name, "xferfailsound")) {
- ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound));
- } else if (!strcasecmp(var->name, "pickupexten")) {
- ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext));
- } else if (!strcasecmp(var->name, "pickupsound")) {
- ast_copy_string(pickupsound, var->value, sizeof(pickupsound));
- } else if (!strcasecmp(var->name, "pickupfailsound")) {
- ast_copy_string(pickupfailsound, var->value, sizeof(pickupfailsound));
- }
- }
+ if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
+ ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
- unmap_features();
- for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) {
- if (remap_feature(var->name, var->value)) {
- ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+ return -1;
}
- }
-
- /* Map a key combination to an application */
- ast_unregister_features();
- for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) {
- process_applicationmap_line(var);
- }
- ast_unregister_groups();
- AST_RWLIST_WRLOCK(&feature_groups);
+ if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
+ ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
+ ast_bridge_features_limits_destroy(&call_duration_limits_chan);
- ctg = NULL;
- while ((ctg = ast_category_browse(cfg, ctg))) {
- /* Is this a parkinglot definition ? */
- if (!strncasecmp(ctg, "parkinglot_", strlen("parkinglot_"))) {
- ast_debug(2, "Found configuration section %s, assume parking context\n", ctg);
- if (!build_parkinglot(ctg, ast_variable_browse(cfg, ctg))) {
- ast_log(LOG_ERROR, "Could not build parking lot %s. Configuration error.\n", ctg);
- } else {
- ast_debug(1, "Configured parking context %s\n", ctg);
- }
- continue;
+ return -1;
}
- /* No, check if it's a group */
- for (i = 0; i < ARRAY_LEN(categories); i++) {
- if (!strcasecmp(categories[i], ctg)) {
- break;
- }
- }
- if (i < ARRAY_LEN(categories)) {
- continue;
- }
+ bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
- if (!(fg = register_group(ctg))) {
- continue;
+ if (ast_bridge_features_set_limits(chan_features, &call_duration_limits_chan, 0)) {
+ abandon_call = 1;
+ }
+ if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
+ abandon_call = 1;
}
- for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
- struct ast_call_feature *feature;
-
- ast_rdlock_call_features();
- feature = ast_find_call_feature(var->name);
- ast_unlock_call_features();
- if (!feature) {
- ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
- continue;
- }
+ /* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
+ ast_bridge_features_limits_destroy(&call_duration_limits_chan);
+ ast_bridge_features_limits_destroy(&call_duration_limits_peer);
- register_group_feature(fg, var->value, feature);
+ if (abandon_call) {
+ ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
+ return -1;
}
}
- AST_RWLIST_UNLOCK(&feature_groups);
-
return 0;
}
/*!
- * \internal
- * \brief Destroy the given dialplan usage context.
+ * \brief bridge the call and set CDR
*
- * \param doomed Parking lot usage context to destroy.
+ * \param chan The bridge considers this channel the caller.
+ * \param peer The bridge considers this channel the callee.
+ * \param config Configuration for this bridge.
*
- * \return Nothing
+ * Set start time, check for two channels,check if monitor on
+ * check for feature activation, create new CDR
+ * \retval res on success.
+ * \retval -1 on failure to bridge.
*/
-static void destroy_dialplan_usage_context(struct parking_dp_context *doomed)
+int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
{
- struct parking_dp_ramp *ramp;
- struct parking_dp_spaces *spaces;
+ int res;
+ struct ast_bridge *bridge;
+ struct ast_bridge_features chan_features;
+ struct ast_bridge_features *peer_features;
- while ((ramp = AST_LIST_REMOVE_HEAD(&doomed->access_extens, node))) {
- ast_free(ramp);
+ /* Setup features. */
+ res = ast_bridge_features_init(&chan_features);
+ peer_features = ast_bridge_features_new();
+ if (res || !peer_features) {
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- while ((spaces = AST_LIST_REMOVE_HEAD(&doomed->spaces, node))) {
- ast_free(spaces);
+
+ if (pre_bridge_setup(chan, peer, config, &chan_features, peer_features)) {
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- while ((spaces = AST_LIST_REMOVE_HEAD(&doomed->hints, node))) {
- ast_free(spaces);
+
+ /* Create bridge */
+ bridge = ast_bridge_basic_new();
+ if (!bridge) {
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- ast_free(doomed);
-}
-/*!
- * \internal
- * \brief Destroy the given dialplan usage map.
- *
- * \param doomed Parking lot usage map to destroy.
- *
- * \return Nothing
- */
-static void destroy_dialplan_usage_map(struct parking_dp_map *doomed)
-{
- struct parking_dp_context *item;
+ /* Put peer into the bridge */
+ if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
+ ast_bridge_destroy(bridge);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
+ }
+
+ /* Join bridge */
+ ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
- while ((item = AST_LIST_REMOVE_HEAD(doomed, node))) {
- destroy_dialplan_usage_context(item);
+ /*
+ * If the bridge was broken for a hangup that isn't real, then
+ * don't run the h extension, because the channel isn't really
+ * hung up. This should really only happen with
+ * AST_SOFTHANGUP_ASYNCGOTO.
+ */
+ res = -1;
+ ast_channel_lock(chan);
+ if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+ res = 0;
}
-}
+ ast_channel_unlock(chan);
-/*!
- * \internal
- * \brief Create a new parking lot ramp dialplan usage node.
- *
- * \param exten Parking lot access ramp extension.
- * \param exclusive TRUE if the parking lot access ramp extension is exclusive.
- *
- * \retval New usage ramp node on success.
- * \retval NULL on error.
- */
-static struct parking_dp_ramp *build_dialplan_useage_ramp(const char *exten, int exclusive)
-{
- struct parking_dp_ramp *ramp_node;
+ ast_bridge_features_cleanup(&chan_features);
- ramp_node = ast_calloc(1, sizeof(*ramp_node) + strlen(exten));
- if (!ramp_node) {
- return NULL;
+/* BUGBUG this is used by Dial and FollowMe for CDR information. By Queue for Queue stats like CDRs. */
+ if (res && config->end_bridge_callback) {
+ config->end_bridge_callback(config->end_bridge_callback_data);
}
- ramp_node->exclusive = exclusive;
- strcpy(ramp_node->exten, exten);
- return ramp_node;
+
+ return res;
}
-/*!
- * \internal
- * \brief Add parking lot access ramp to the context ramp usage map.
- *
- * \param ramp_map Current parking lot context ramp usage map.
- * \param exten Parking lot access ramp extension to add.
- * \param exclusive TRUE if the parking lot access ramp extension is exclusive.
- * \param lot Parking lot supplying reference data.
- * \param complain TRUE if to complain of parking lot ramp conflicts.
- *
- * \retval 0 on success. The ramp_map is updated.
- * \retval -1 on failure.
- */
-static int usage_context_add_ramp(struct parking_dp_ramp_map *ramp_map, const char *exten, int exclusive, struct ast_parkinglot *lot, int complain)
+/*! \brief Output parking event to manager */
+static void post_manager_event(const char *s, struct parkeduser *pu)
{
- struct parking_dp_ramp *cur_ramp;
- struct parking_dp_ramp *new_ramp;
- int cmp;
+ manager_event(EVENT_FLAG_CALL, s,
+ "Exten: %s\r\n"
+ "Channel: %s\r\n"
+ "Parkinglot: %s\r\n"
+ "CallerIDNum: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "ConnectedLineNum: %s\r\n"
+ "ConnectedLineName: %s\r\n"
+ "UniqueID: %s\r\n",
+ pu->parkingexten,
+ ast_channel_name(pu->chan),
+ pu->parkinglot->name,
+ S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
+ S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
+ S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
+ S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
+ ast_channel_uniqueid(pu->chan)
+ );
+}
+
+static char *callback_dialoptions(struct ast_flags *features_callee, struct ast_flags *features_caller, char *options, size_t len)
+{
+ int i = 0;
+ enum {
+ OPT_CALLEE_REDIRECT = 't',
+ OPT_CALLER_REDIRECT = 'T',
+ OPT_CALLEE_AUTOMON = 'w',
+ OPT_CALLER_AUTOMON = 'W',
+ OPT_CALLEE_DISCONNECT = 'h',
+ OPT_CALLER_DISCONNECT = 'H',
+ OPT_CALLEE_PARKCALL = 'k',
+ OPT_CALLER_PARKCALL = 'K',
+ };
- /* Make sure that exclusive is only 0 or 1 */
- if (exclusive) {
- exclusive = 1;
+ memset(options, 0, len);
+ if (ast_test_flag(features_caller, AST_FEATURE_REDIRECT) && i < len) {
+ options[i++] = OPT_CALLER_REDIRECT;
+ }
+ if (ast_test_flag(features_caller, AST_FEATURE_AUTOMON) && i < len) {
+ options[i++] = OPT_CALLER_AUTOMON;
+ }
+ if (ast_test_flag(features_caller, AST_FEATURE_DISCONNECT) && i < len) {
+ options[i++] = OPT_CALLER_DISCONNECT;
+ }
+ if (ast_test_flag(features_caller, AST_FEATURE_PARKCALL) && i < len) {
+ options[i++] = OPT_CALLER_PARKCALL;
}
- AST_LIST_TRAVERSE_SAFE_BEGIN(ramp_map, cur_ramp, node) {
- cmp = strcmp(exten, cur_ramp->exten);
- if (cmp > 0) {
- /* The parking lot ramp goes after this node. */
- continue;
- }
- if (cmp == 0) {
- /* The ramp is already in the map. */
- if (complain && (cur_ramp->exclusive || exclusive)) {
- ast_log(LOG_WARNING,
- "Parking lot '%s' parkext %s@%s used by another parking lot.\n",
- lot->name, exten, lot->cfg.parking_con);
- }
- return 0;
- }
- /* The new parking lot ramp goes before this node. */
- new_ramp = build_dialplan_useage_ramp(exten, exclusive);
- if (!new_ramp) {
- return -1;
- }
- AST_LIST_INSERT_BEFORE_CURRENT(new_ramp, node);
- return 0;
+ if (ast_test_flag(features_callee, AST_FEATURE_REDIRECT) && i < len) {
+ options[i++] = OPT_CALLEE_REDIRECT;
+ }
+ if (ast_test_flag(features_callee, AST_FEATURE_AUTOMON) && i < len) {
+ options[i++] = OPT_CALLEE_AUTOMON;
+ }
+ if (ast_test_flag(features_callee, AST_FEATURE_DISCONNECT) && i < len) {
+ options[i++] = OPT_CALLEE_DISCONNECT;
+ }
+ if (ast_test_flag(features_callee, AST_FEATURE_PARKCALL) && i < len) {
+ options[i++] = OPT_CALLEE_PARKCALL;
}
- AST_LIST_TRAVERSE_SAFE_END;
- /* New parking lot access ramp goes on the end. */
- new_ramp = build_dialplan_useage_ramp(exten, exclusive);
- if (!new_ramp) {
- return -1;
- }
- AST_LIST_INSERT_TAIL(ramp_map, new_ramp, node);
- return 0;
+ return options;
}
/*!
* \internal
- * \brief Create a new parking lot spaces dialplan usage node.
+ * \brief Run management on a parked call.
*
- * \param start First parking lot space to add.
- * \param stop Last parking lot space to add.
+ * \note The parkinglot parkings list is locked on entry.
*
- * \retval New usage ramp node on success.
- * \retval NULL on error.
+ * \retval TRUE if the parking completed.
*/
-static struct parking_dp_spaces *build_dialplan_useage_spaces(int start, int stop)
+static int manage_parked_call(struct parkeduser *pu, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
{
- struct parking_dp_spaces *spaces_node;
-
- spaces_node = ast_calloc(1, sizeof(*spaces_node));
- if (!spaces_node) {
- return NULL;
- }
- spaces_node->start = start;
- spaces_node->stop = stop;
- return spaces_node;
-}
+ struct ast_channel *chan = pu->chan; /* shorthand */
+ int tms; /* timeout for this item */
+ int x; /* fd index in channel */
-/*!
- * \internal
- * \brief Add parking lot spaces to the context space usage map.
- *
- * \param space_map Current parking lot context space usage map.
- * \param start First parking lot space to add.
- * \param stop Last parking lot space to add.
- * \param lot Parking lot supplying reference data.
- * \param complain TRUE if to complain of parking lot spaces conflicts.
- *
- * \retval 0 on success. The space_map is updated.
- * \retval -1 on failure.
- */
-static int usage_context_add_spaces(struct parking_dp_space_map *space_map, int start, int stop, struct ast_parkinglot *lot, int complain)
-{
- struct parking_dp_spaces *cur_node;
- struct parking_dp_spaces *expand_node;
- struct parking_dp_spaces *new_node;
-
- expand_node = NULL;
- AST_LIST_TRAVERSE_SAFE_BEGIN(space_map, cur_node, node) {
- /* NOTE: stop + 1 to combine immediately adjacent nodes into one. */
- if (expand_node) {
- /* The previous node is expanding to possibly eat following nodes. */
- if (expand_node->stop + 1 < cur_node->start) {
- /* Current node is completely after expanding node. */
- return 0;
- }
+ tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
+ if (tms > pu->parkingtime) {
+ /*
+ * Call has been parked too long.
+ * Stop entertaining the caller.
+ */
+ switch (pu->hold_method) {
+ case AST_CONTROL_HOLD:
+ ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
+ break;
+ case AST_CONTROL_RINGING:
+ ast_indicate(pu->chan, -1);
+ break;
+ default:
+ break;
+ }
+ pu->hold_method = 0;
- if (complain
- && ((cur_node->start <= start && start <= cur_node->stop)
- || (cur_node->start <= stop && stop <= cur_node->stop)
- || (start < cur_node->start && cur_node->stop < stop))) {
- /* Only complain once per range add. */
- complain = 0;
- ast_log(LOG_WARNING,
- "Parking lot '%s' parkpos %d-%d@%s overlaps another parking lot.\n",
- lot->name, start, stop, lot->cfg.parking_con);
- }
+ /* Get chan, exten from derived kludge */
+ if (pu->peername[0]) {
+ char *peername;
+ char *dash;
+ char *peername_flat; /* using something like DAHDI/52 for an extension name is NOT a good idea */
+ char parkingslot[AST_MAX_EXTENSION]; /* buffer for parkinglot slot number */
+ int i;
- /* Current node is eaten by the expanding node. */
- if (expand_node->stop < cur_node->stop) {
- expand_node->stop = cur_node->stop;
+ peername = ast_strdupa(pu->peername);
+ dash = strrchr(peername, '-');
+ if (dash) {
+ *dash = '\0';
}
- AST_LIST_REMOVE_CURRENT(node);
- ast_free(cur_node);
- continue;
- }
- if (cur_node->stop + 1 < start) {
- /* New range is completely after current node. */
- continue;
- }
- if (stop + 1 < cur_node->start) {
- /* New range is completely before current node. */
- new_node = build_dialplan_useage_spaces(start, stop);
- if (!new_node) {
- return -1;
+ peername_flat = ast_strdupa(peername);
+ for (i = 0; peername_flat[i]; i++) {
+ if (peername_flat[i] == '/') {
+ peername_flat[i] = '_';
+ }
}
- AST_LIST_INSERT_BEFORE_CURRENT(new_node, node);
- return 0;
- }
- if (complain
- && ((cur_node->start <= start && start <= cur_node->stop)
- || (cur_node->start <= stop && stop <= cur_node->stop)
- || (start < cur_node->start && cur_node->stop < stop))) {
- /* Only complain once per range add. */
- complain = 0;
- ast_log(LOG_WARNING,
- "Parking lot '%s' parkpos %d-%d@%s overlaps another parking lot.\n",
- lot->name, start, stop, lot->cfg.parking_con);
- }
+ if (!ast_context_find_or_create(NULL, NULL, parking_con_dial, registrar)) {
+ ast_log(LOG_ERROR,
+ "Parking dial context '%s' does not exist and unable to create\n",
+ parking_con_dial);
+ } else {
+ char returnexten[AST_MAX_EXTENSION];
+ char comebackdialtime[AST_MAX_EXTENSION];
+ struct ast_datastore *features_datastore;
+ struct ast_dial_features *dialfeatures;
- /* Current node range overlaps or is immediately adjacent to new range. */
- if (start < cur_node->start) {
- /* Expand the current node in the front. */
- cur_node->start = start;
- }
- if (stop <= cur_node->stop) {
- /* Current node is not expanding in the rear. */
- return 0;
- }
- cur_node->stop = stop;
- expand_node = cur_node;
- }
- AST_LIST_TRAVERSE_SAFE_END;
+ if (!strncmp(peername, "Parked/", 7)) {
+ peername += 7;
+ }
- if (expand_node) {
- /*
- * The previous node expanded and either ate all following nodes
- * or it was the last node.
- */
- return 0;
- }
+ ast_channel_lock(chan);
+ features_datastore = ast_channel_datastore_find(chan, &dial_features_info,
+ NULL);
+ if (features_datastore && (dialfeatures = features_datastore->data)) {
+ char buf[MAX_DIAL_FEATURE_OPTIONS] = {0,};
- /* New range goes on the end. */
- new_node = build_dialplan_useage_spaces(start, stop);
- if (!new_node) {
- return -1;
- }
- AST_LIST_INSERT_TAIL(space_map, new_node, node);
- return 0;
-}
+ snprintf(returnexten, sizeof(returnexten), "%s,%u,%s", peername,
+ pu->parkinglot->cfg.comebackdialtime,
+ callback_dialoptions(&dialfeatures->peer_features,
+ &dialfeatures->my_features, buf, sizeof(buf)));
+ } else { /* Existing default */
+ ast_log(LOG_NOTICE, "Dial features not found on %s, using default!\n",
+ ast_channel_name(chan));
+ snprintf(returnexten, sizeof(returnexten), "%s,%u,t", peername,
+ pu->parkinglot->cfg.comebackdialtime);
+ }
+ ast_channel_unlock(chan);
-/*!
- * \internal
- * \brief Add parking lot spaces to the context dialplan usage node.
- *
- * \param ctx_node Usage node to add parking lot spaces.
- * \param lot Parking lot to add data to ctx_node.
- * \param complain TRUE if to complain of parking lot ramp and spaces conflicts.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int dialplan_usage_add_parkinglot_data(struct parking_dp_context *ctx_node, struct ast_parkinglot *lot, int complain)
-{
- if (usage_context_add_ramp(&ctx_node->access_extens, lot->cfg.parkext,
- lot->cfg.parkext_exclusive, lot, complain)) {
- return -1;
- }
- if (usage_context_add_spaces(&ctx_node->spaces, lot->cfg.parking_start,
- lot->cfg.parking_stop, lot, complain)) {
- return -1;
- }
- if (lot->cfg.parkaddhints
- && usage_context_add_spaces(&ctx_node->hints, lot->cfg.parking_start,
- lot->cfg.parking_stop, lot, 0)) {
- return -1;
- }
- return 0;
-}
+ snprintf(comebackdialtime, sizeof(comebackdialtime), "%u",
+ pu->parkinglot->cfg.comebackdialtime);
+ pbx_builtin_setvar_helper(chan, "COMEBACKDIALTIME", comebackdialtime);
-/*!
- * \internal
- * \brief Create a new parking lot context dialplan usage node.
- *
- * \param lot Parking lot to create a new dialplan usage from.
- *
- * \retval New usage context node on success.
- * \retval NULL on error.
- */
-static struct parking_dp_context *build_dialplan_useage_context(struct ast_parkinglot *lot)
-{
- struct parking_dp_context *ctx_node;
+ pbx_builtin_setvar_helper(chan, "PARKER", peername);
- ctx_node = ast_calloc(1, sizeof(*ctx_node) + strlen(lot->cfg.parking_con));
- if (!ctx_node) {
- return NULL;
- }
- if (dialplan_usage_add_parkinglot_data(ctx_node, lot, 0)) {
- destroy_dialplan_usage_context(ctx_node);
- return NULL;
- }
- strcpy(ctx_node->context, lot->cfg.parking_con);
- return ctx_node;
-}
+ if (ast_add_extension(parking_con_dial, 1, peername_flat, 1, NULL, NULL,
+ "Dial", ast_strdup(returnexten), ast_free_ptr, registrar)) {
+ ast_log(LOG_ERROR,
+ "Could not create parking return dial exten: %s@%s\n",
+ peername_flat, parking_con_dial);
+ }
+ }
-/*!
- * \internal
- * \brief Add the given parking lot dialplan usage to the dialplan usage map.
- *
- * \param usage_map Parking lot usage map to add the given parking lot.
- * \param lot Parking lot to add dialplan usage.
- * \param complain TRUE if to complain of parking lot ramp and spaces conflicts.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int dialplan_usage_add_parkinglot(struct parking_dp_map *usage_map, struct ast_parkinglot *lot, int complain)
-{
- struct parking_dp_context *cur_ctx;
- struct parking_dp_context *new_ctx;
- int cmp;
+ snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
+ pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parkingslot);
+ pbx_builtin_setvar_helper(chan, "PARKEDLOT", pu->parkinglot->name);
- AST_LIST_TRAVERSE_SAFE_BEGIN(usage_map, cur_ctx, node) {
- cmp = strcmp(lot->cfg.parking_con, cur_ctx->context);
- if (cmp > 0) {
- /* The parking lot context goes after this node. */
- continue;
- }
- if (cmp == 0) {
- /* This is the node we will add parking lot spaces to the map. */
- return dialplan_usage_add_parkinglot_data(cur_ctx, lot, complain);
- }
- /* The new parking lot context goes before this node. */
- new_ctx = build_dialplan_useage_context(lot);
- if (!new_ctx) {
- return -1;
+ if (pu->options_specified) {
+ /*
+ * Park() was called with overriding return arguments, respect
+ * those arguments.
+ */
+ set_c_e_p(chan, pu->context, pu->exten, pu->priority);
+ } else if (pu->parkinglot->cfg.comebacktoorigin) {
+ set_c_e_p(chan, parking_con_dial, peername_flat, 1);
+ } else {
+ /* Handle fallback when extensions don't exist here since that logic was removed from pbx */
+ if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1, NULL)) {
+ set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, peername_flat, 1);
+ } else if (ast_exists_extension(chan, pu->parkinglot->cfg.comebackcontext, "s", 1, NULL)) {
+ ast_verb(2, "Can not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
+ pu->parkinglot->cfg.comebackcontext, peername_flat, pu->parkinglot->cfg.comebackcontext);
+ set_c_e_p(chan, pu->parkinglot->cfg.comebackcontext, "s", 1);
+ } else {
+ ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
+ ast_channel_name(chan),
+ pu->parkinglot->cfg.comebackcontext, peername_flat,
+ pu->parkinglot->cfg.comebackcontext);
+ set_c_e_p(chan, "default", "s", 1);
+ }
+ }
+ } else {
+ /*
+ * They've been waiting too long, send them back to where they
+ * came. Theoretically they should have their original
+ * extensions and such, but we copy to be on the safe side.
+ */
+ set_c_e_p(chan, pu->context, pu->exten, pu->priority);
}
- AST_LIST_INSERT_BEFORE_CURRENT(new_ctx, node);
- return 0;
- }
- AST_LIST_TRAVERSE_SAFE_END;
+ post_manager_event("ParkedCallTimeOut", pu);
+ ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallTimeOut", NULL);
- /* New parking lot context goes on the end. */
- new_ctx = build_dialplan_useage_context(lot);
- if (!new_ctx) {
- return -1;
- }
- AST_LIST_INSERT_TAIL(usage_map, new_ctx, node);
- return 0;
-}
+ ast_verb(2, "Timeout for %s parked on %d (%s). Returning to %s,%s,%d\n",
+ ast_channel_name(pu->chan), pu->parkingnum, pu->parkinglot->name, ast_channel_context(pu->chan),
+ ast_channel_exten(pu->chan), ast_channel_priority(pu->chan));
-/*!
- * \internal
- * \brief Build the dialplan usage map of the current parking lot container.
- *
- * \param usage_map Parking lot usage map. Must already be initialized.
- * \param complain TRUE if to complain of parking lot ramp and spaces conflicts.
- *
- * \retval 0 on success. The usage_map is filled in.
- * \retval -1 on failure. Built usage_map is incomplete.
- */
-static int build_dialplan_useage_map(struct parking_dp_map *usage_map, int complain)
-{
- int status = 0;
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
-
- /* For all parking lots */
- iter = ao2_iterator_init(parkinglots, 0);
- for (; (curlot = ao2_iterator_next(&iter)); ao2_ref(curlot, -1)) {
- /* Add the parking lot to the map. */
- if (dialplan_usage_add_parkinglot(usage_map, curlot, complain)) {
- ao2_ref(curlot, -1);
- status = -1;
- break;
+ /* Start up the PBX, or hang them up */
+ if (ast_pbx_start(chan)) {
+ ast_log(LOG_WARNING,
+ "Unable to restart the PBX for user on '%s', hanging them up...\n",
+ ast_channel_name(pu->chan));
+ ast_hangup(chan);
}
+
+ /* And take them out of the parking lot */
+ return 1;
}
- ao2_iterator_destroy(&iter);
- return status;
-}
+ /* still within parking time, process descriptors */
+ if (pfds) {
+ for (x = 0; x < AST_MAX_FDS; x++) {
+ struct ast_frame *f;
+ int y;
+
+ if (!ast_channel_fd_isset(chan, x)) {
+ continue; /* nothing on this descriptor */
+ }
+
+ for (y = 0; y < nfds; y++) {
+ if (pfds[y].fd == ast_channel_fd(chan, x)) {
+ /* Found poll record! */
+ break;
+ }
+ }
+ if (y == nfds) {
+ /* Not found */
+ continue;
+ }
+
+ if (!(pfds[y].revents & (POLLIN | POLLERR | POLLPRI))) {
+ /* Next x */
+ continue;
+ }
+
+ if (pfds[y].revents & POLLPRI) {
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
+ } else {
+ ast_clear_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);
+ }
+ ast_channel_fdno_set(chan, x);
-/*!
- * \internal
- * \brief Remove the given extension if it exists.
- *
- * \param context Dialplan database context name.
- * \param exten Extension to remove.
- * \param priority Extension priority to remove.
- *
- * \return Nothing
- */
-static void remove_exten_if_exist(const char *context, const char *exten, int priority)
-{
- struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+ /* See if they need servicing */
+ f = ast_read(pu->chan);
+ /* Hangup? */
+ if (!f || (f->frametype == AST_FRAME_CONTROL
+ && f->subclass.integer == AST_CONTROL_HANGUP)) {
+ if (f) {
+ ast_frfree(f);
+ }
+ post_manager_event("ParkedCallGiveUp", pu);
+ ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallGiveUp",
+ NULL);
- if (pbx_find_extension(NULL, NULL, &q, context, exten, priority, NULL, NULL,
- E_MATCH)) {
- ast_debug(1, "Removing unneeded parking lot exten: %s@%s priority:%d\n",
- context, exten, priority);
- ast_context_remove_extension(context, exten, priority, registrar);
- }
-}
+ /* There's a problem, hang them up */
+ ast_verb(2, "%s got tired of being parked\n", ast_channel_name(chan));
+ ast_hangup(chan);
-/*!
- * \internal
- * \brief Remove unused parking lot access ramp items.
- *
- * \param context Dialplan database context name.
- * \param old_ramps Before configuration reload access ramp usage map.
- * \param new_ramps After configuration reload access ramp usage map.
- *
- * \details
- * Remove access ramp items that were in the old context but not in the
- * new context.
- *
- * \return Nothing
- */
-static void remove_dead_ramp_usage(const char *context, struct parking_dp_ramp_map *old_ramps, struct parking_dp_ramp_map *new_ramps)
-{
- struct parking_dp_ramp *old_ramp;
- struct parking_dp_ramp *new_ramp;
- int cmp;
+ /* And take them out of the parking lot */
+ return 1;
+ } else {
+ /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
+ ast_frfree(f);
+ if (pu->hold_method == AST_CONTROL_HOLD
+ && pu->moh_trys < 3
+ && !ast_channel_generatordata(chan)) {
+ ast_debug(1,
+ "MOH on parked call stopped by outside source. Restarting on channel %s.\n",
+ ast_channel_name(chan));
+ ast_indicate_data(chan, AST_CONTROL_HOLD,
+ S_OR(pu->parkinglot->cfg.mohclass, NULL),
+ (!ast_strlen_zero(pu->parkinglot->cfg.mohclass)
+ ? strlen(pu->parkinglot->cfg.mohclass) + 1 : 0));
+ pu->moh_trys++;
+ }
+ break;
+ }
+ } /* End for */
+ }
- old_ramp = AST_LIST_FIRST(old_ramps);
- new_ramp = AST_LIST_FIRST(new_ramps);
+ /* mark fds for next round */
+ for (x = 0; x < AST_MAX_FDS; x++) {
+ if (ast_channel_fd_isset(chan, x)) {
+ void *tmp = ast_realloc(*new_pfds,
+ (*new_nfds + 1) * sizeof(struct pollfd));
- while (new_ramp) {
- if (!old_ramp) {
- /* No old ramps left, so no dead ramps can remain. */
- return;
- }
- cmp = strcmp(old_ramp->exten, new_ramp->exten);
- if (cmp < 0) {
- /* New map does not have old ramp. */
- remove_exten_if_exist(context, old_ramp->exten, 1);
- old_ramp = AST_LIST_NEXT(old_ramp, node);
- continue;
- }
- if (cmp == 0) {
- /* Old and new map have this ramp. */
- old_ramp = AST_LIST_NEXT(old_ramp, node);
- } else {
- /* Old map does not have new ramp. */
+ if (!tmp) {
+ continue;
+ }
+ *new_pfds = tmp;
+ (*new_pfds)[*new_nfds].fd = ast_channel_fd(chan, x);
+ (*new_pfds)[*new_nfds].events = POLLIN | POLLERR | POLLPRI;
+ (*new_pfds)[*new_nfds].revents = 0;
+ (*new_nfds)++;
}
- new_ramp = AST_LIST_NEXT(new_ramp, node);
}
-
- /* Any old ramps left must be dead. */
- for (; old_ramp; old_ramp = AST_LIST_NEXT(old_ramp, node)) {
- remove_exten_if_exist(context, old_ramp->exten, 1);
+ /* Keep track of our shortest wait */
+ if (tms < *ms || *ms < 0) {
+ *ms = tms;
}
-}
-
-/*!
- * \internal
- * \brief Destroy the given parking space.
- *
- * \param context Dialplan database context name.
- * \param space Parking space.
- *
- * \return Nothing
- */
-static void destroy_space(const char *context, int space)
-{
- char exten[AST_MAX_EXTENSION];
- /* Destroy priorities of the parking space that we registered. */
- snprintf(exten, sizeof(exten), "%d", space);
- remove_exten_if_exist(context, exten, PRIORITY_HINT);
- remove_exten_if_exist(context, exten, 1);
+ /* Stay in the parking lot. */
+ return 0;
}
-/*!
- * \internal
- * \brief Remove unused parking lot space items.
- *
- * \param context Dialplan database context name.
- * \param old_spaces Before configuration reload parking space usage map.
- * \param new_spaces After configuration reload parking space usage map.
- * \param destroy_space Function to destroy parking space item.
- *
- * \details
- * Remove parking space items that were in the old context but
- * not in the new context.
- *
- * \return Nothing
- */
-static void remove_dead_spaces_usage(const char *context,
- struct parking_dp_space_map *old_spaces, struct parking_dp_space_map *new_spaces,
- void (*destroy_space)(const char *context, int space))
+/*! \brief Run management on parkinglots, called once per parkinglot */
+static void manage_parkinglot(struct ast_parkinglot *curlot, const struct pollfd *pfds, int nfds, struct pollfd **new_pfds, int *new_nfds, int *ms)
{
- struct parking_dp_spaces *old_range;
- struct parking_dp_spaces *new_range;
- int space;/*!< Current position in the current old range. */
- int stop;
-
- old_range = AST_LIST_FIRST(old_spaces);
- new_range = AST_LIST_FIRST(new_spaces);
- space = -1;
+ struct parkeduser *pu;
+ struct ast_context *con;
- while (old_range) {
- if (space < old_range->start) {
- space = old_range->start;
+ /* Lock parkings list */
+ AST_LIST_LOCK(&curlot->parkings);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&curlot->parkings, pu, list) {
+ if (pu->notquiteyet) { /* Pretend this one isn't here yet */
+ continue;
}
- if (new_range) {
- if (space < new_range->start) {
- /* Current position in old range starts before new range. */
- if (old_range->stop < new_range->start) {
- /* Old range ends before new range. */
- stop = old_range->stop;
- old_range = AST_LIST_NEXT(old_range, node);
- } else {
- /* Tail of old range overlaps new range. */
- stop = new_range->start - 1;
- }
- } else if (/* new_range->start <= space && */ space <= new_range->stop) {
- /* Current position in old range overlaps new range. */
- if (old_range->stop <= new_range->stop) {
- /* Old range ends at or before new range. */
- old_range = AST_LIST_NEXT(old_range, node);
- } else {
- /* Old range extends beyond end of new range. */
- space = new_range->stop + 1;
- new_range = AST_LIST_NEXT(new_range, node);
+ if (manage_parked_call(pu, pfds, nfds, new_pfds, new_nfds, ms)) {
+ /* Parking is complete for this call so remove it from the parking lot. */
+ con = ast_context_find(pu->parkinglot->cfg.parking_con);
+ if (con) {
+ if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
+ ast_log(LOG_WARNING,
+ "Whoa, failed to remove the parking extension %s@%s!\n",
+ pu->parkingexten, pu->parkinglot->cfg.parking_con);
}
- continue;
- } else /* if (new_range->stop < space) */ {
- /* Current position in old range starts after new range. */
- new_range = AST_LIST_NEXT(new_range, node);
- continue;
+ notify_metermaids(pu->parkingexten, pu->parkinglot->cfg.parking_con,
+ AST_DEVICE_NOT_INUSE);
+ } else {
+ ast_log(LOG_WARNING,
+ "Whoa, parking lot '%s' context '%s' does not exist.\n",
+ pu->parkinglot->name, pu->parkinglot->cfg.parking_con);
}
- } else {
- /* No more new ranges. All remaining old spaces are dead. */
- stop = old_range->stop;
- old_range = AST_LIST_NEXT(old_range, node);
- }
-
- /* Destroy dead parking spaces. */
- for (; space <= stop; ++space) {
- destroy_space(context, space);
+ AST_LIST_REMOVE_CURRENT(list);
+ parkinglot_unref(pu->parkinglot);
+ ast_free(pu);
}
}
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&curlot->parkings);
}
/*!
- * \internal
- * \brief Remove unused parking lot context items.
- *
- * \param context Dialplan database context name.
- * \param old_ctx Before configuration reload context usage map.
- * \param new_ctx After configuration reload context usage map.
- *
- * \details
- * Remove context usage items that were in the old context but not in the
- * new context.
- *
- * \return Nothing
- */
-static void remove_dead_context_usage(const char *context, struct parking_dp_context *old_ctx, struct parking_dp_context *new_ctx)
-{
- remove_dead_ramp_usage(context, &old_ctx->access_extens, &new_ctx->access_extens);
- remove_dead_spaces_usage(context, &old_ctx->spaces, &new_ctx->spaces, destroy_space);
-#if 0
- /* I don't think we should destroy hints if the parking space still exists. */
- remove_dead_spaces_usage(context, &old_ctx->hints, &new_ctx->hints, destroy_space_hint);
-#endif
-}
-
-/*!
- * \internal
- * \brief Remove unused parking lot dialplan items.
- *
- * \param old_map Before configuration reload dialplan usage map.
- * \param new_map After configuration reload dialplan usage map.
- *
- * \details
- * Remove dialplan items that were in the old map but not in the
- * new map.
+ * \brief Take care of parked calls and unpark them if needed
+ * \param ignore unused var.
*
- * \return Nothing
+ * Start inf loop, lock parking lot, check if any parked channels have gone above timeout
+ * if so, remove channel from parking lot and return it to the extension that parked it.
+ * Check if parked channel decided to hangup, wait until next FD via select().
*/
-static void remove_dead_dialplan_useage(struct parking_dp_map *old_map, struct parking_dp_map *new_map)
+static void *do_parking_thread(void *ignore)
{
- struct parking_dp_context *old_ctx;
- struct parking_dp_context *new_ctx;
- struct ast_context *con;
- int cmp;
+ struct pollfd *pfds = NULL, *new_pfds = NULL;
+ int nfds = 0, new_nfds = 0;
- old_ctx = AST_LIST_FIRST(old_map);
- new_ctx = AST_LIST_FIRST(new_map);
+ for (;;) {
+ struct ao2_iterator iter;
+ struct ast_parkinglot *curlot;
+ int ms = -1; /* poll2 timeout, uninitialized */
- while (new_ctx) {
- if (!old_ctx) {
- /* No old contexts left, so no dead stuff can remain. */
- return;
- }
- cmp = strcmp(old_ctx->context, new_ctx->context);
- if (cmp < 0) {
- /* New map does not have old map context. */
- con = ast_context_find(old_ctx->context);
- if (con) {
- ast_context_destroy(con, registrar);
- }
- old_ctx = AST_LIST_NEXT(old_ctx, node);
- continue;
- }
- if (cmp == 0) {
- /* Old and new map have this context. */
- remove_dead_context_usage(old_ctx->context, old_ctx, new_ctx);
- old_ctx = AST_LIST_NEXT(old_ctx, node);
- } else {
- /* Old map does not have new map context. */
+ iter = ao2_iterator_init(parkinglots, 0);
+ while ((curlot = ao2_iterator_next(&iter))) {
+ manage_parkinglot(curlot, pfds, nfds, &new_pfds, &new_nfds, &ms);
+ ao2_ref(curlot, -1);
}
- new_ctx = AST_LIST_NEXT(new_ctx, node);
- }
+ ao2_iterator_destroy(&iter);
- /* Any old contexts left must be dead. */
- for (; old_ctx; old_ctx = AST_LIST_NEXT(old_ctx, node)) {
- con = ast_context_find(old_ctx->context);
- if (con) {
- ast_context_destroy(con, registrar);
- }
+ /* Recycle */
+ ast_free(pfds);
+ pfds = new_pfds;
+ nfds = new_nfds;
+ new_pfds = NULL;
+ new_nfds = 0;
+
+ /* Wait for something to happen */
+ ast_poll(pfds, nfds, ms);
+ pthread_testcancel();
}
+ /* If this WERE reached, we'd need to free(pfds) */
+ return NULL; /* Never reached */
}
-static int parkinglot_markall_cb(void *obj, void *arg, int flags)
+/*! \brief Find parkinglot by name */
+static struct ast_parkinglot *find_parkinglot(const char *name)
{
- struct ast_parkinglot *parkinglot = obj;
-
- parkinglot->the_mark = 1;
- return 0;
-}
+ struct ast_parkinglot *parkinglot;
-static int parkinglot_is_marked_cb(void *obj, void *arg, int flags)
-{
- struct ast_parkinglot *parkinglot = obj;
+ if (ast_strlen_zero(name)) {
+ return NULL;
+ }
- if (parkinglot->the_mark) {
- if (AST_LIST_EMPTY(&parkinglot->parkings)) {
- /* This parking lot can actually be deleted. */
- return CMP_MATCH;
- }
- /* Try reloading later when parking lot is empty. */
- ast_log(LOG_WARNING,
- "Parking lot %s has parked calls. Could not remove.\n",
- parkinglot->name);
- parkinglot->disabled = 1;
- force_reload_load = 1;
+ parkinglot = ao2_find(parkinglots, (void *) name, 0);
+ if (parkinglot) {
+ ast_debug(1, "Found Parking lot: %s\n", parkinglot->name);
}
- return 0;
+ return parkinglot;
}
-static int parkinglot_activate_cb(void *obj, void *arg, int flags)
+/*! \brief Copy parkinglot and store it with new name */
+static struct ast_parkinglot *copy_parkinglot(const char *name, const struct ast_parkinglot *parkinglot)
{
- struct ast_parkinglot *parkinglot = obj;
+ struct ast_parkinglot *copylot;
- if (parkinglot->the_mark) {
- /*
- * Don't activate a parking lot that still bears the_mark since
- * it is effectively deleted.
- */
- return 0;
+ if ((copylot = find_parkinglot(name))) { /* Parkinglot with that name already exists */
+ ao2_ref(copylot, -1);
+ return NULL;
}
- if (parkinglot_activate(parkinglot)) {
- /*
- * The parking lot failed to activate. Allow reloading later to
- * see if that fixes it.
- */
- force_reload_load = 1;
- ast_log(LOG_WARNING, "Parking lot %s not open for business.\n", parkinglot->name);
- } else {
- ast_debug(1, "Parking lot %s now open for business. (parkpos %d-%d)\n",
- parkinglot->name, parkinglot->cfg.parking_start,
- parkinglot->cfg.parking_stop);
+ copylot = create_parkinglot(name);
+ if (!copylot) {
+ return NULL;
}
- return 0;
+ ast_debug(1, "Building parking lot %s\n", name);
+
+ /* Copy the source parking lot configuration. */
+ copylot->cfg = parkinglot->cfg;
+
+ return copylot;
+}
+
+AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
+ AST_APP_OPTION('r', AST_PARK_OPT_RINGING),
+ AST_APP_OPTION('R', AST_PARK_OPT_RANDOMIZE),
+ AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
+END_OPTIONS );
+
+/*!
+ * \brief Unreference parkinglot object.
+ */
+static void parkinglot_unref(struct ast_parkinglot *parkinglot)
+{
+ ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name,
+ ao2_ref(parkinglot, 0) - 1);
+ ao2_ref(parkinglot, -1);
}
-static int load_config(int reload)
+static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot)
{
- struct ast_flags config_flags = {
- reload && !force_reload_load ? CONFIG_FLAG_FILEUNCHANGED : 0 };
- struct ast_config *cfg;
- struct parking_dp_map old_usage_map = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
- struct parking_dp_map new_usage_map = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
-
- /* We are reloading now and have already determined if we will force the reload. */
- force_reload_load = 0;
-
- if (!default_parkinglot) {
- /* Must create the default default parking lot */
- default_parkinglot = build_parkinglot(DEFAULT_PARKINGLOT, NULL);
- if (!default_parkinglot) {
- ast_log(LOG_ERROR, "Configuration of default default parking lot failed.\n");
- return -1;
- }
- ast_debug(1, "Configuration of default default parking lot done.\n");
- }
+ int refcount;
- cfg = ast_config_load2("features.conf", "features", config_flags);
- if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
- /* No sense in asking for reload trouble if nothing changed. */
- ast_debug(1, "features.conf did not change.\n");
- return 0;
- }
- if (cfg == CONFIG_STATUS_FILEMISSING
- || cfg == CONFIG_STATUS_FILEINVALID) {
- ast_log(LOG_WARNING, "Could not load features.conf\n");
- return 0;
- }
+ refcount = ao2_ref(parkinglot, +1);
+ ast_debug(3, "Multiparking: %s refcount now %d\n", parkinglot->name, refcount + 1);
+ return parkinglot;
+}
- /* Save current parking lot dialplan needs. */
- if (build_dialplan_useage_map(&old_usage_map, 0)) {
- destroy_dialplan_usage_map(&old_usage_map);
+/*! \brief Destroy a parking lot */
+static void parkinglot_destroy(void *obj)
+{
+ struct ast_parkinglot *doomed = obj;
- /* Allow reloading later to see if conditions have improved. */
- force_reload_load = 1;
- return -1;
- }
+ /*
+ * No need to destroy parked calls here because any parked call
+ * holds a parking lot reference. Therefore the parkings list
+ * must be empty.
+ */
+ ast_assert(AST_LIST_EMPTY(&doomed->parkings));
+ AST_LIST_HEAD_DESTROY(&doomed->parkings);
+}
- ao2_t_callback(parkinglots, OBJ_NODATA, parkinglot_markall_cb, NULL,
- "callback to mark all parking lots");
- process_config(cfg);
- ast_config_destroy(cfg);
- ao2_t_callback(parkinglots, OBJ_NODATA | OBJ_UNLINK, parkinglot_is_marked_cb, NULL,
- "callback to remove marked parking lots");
+/*! \brief Allocate parking lot structure */
+static struct ast_parkinglot *create_parkinglot(const char *name)
+{
+ struct ast_parkinglot *newlot;
- /* Save updated parking lot dialplan needs. */
- if (build_dialplan_useage_map(&new_usage_map, 1)) {
- /*
- * Yuck, if this failure caused any parking lot dialplan items
- * to be lost, they will likely remain lost until Asterisk is
- * restarted.
- */
- destroy_dialplan_usage_map(&old_usage_map);
- destroy_dialplan_usage_map(&new_usage_map);
- return -1;
+ if (ast_strlen_zero(name)) { /* No name specified */
+ return NULL;
}
- /* Remove no longer needed parking lot dialplan usage. */
- remove_dead_dialplan_useage(&old_usage_map, &new_usage_map);
-
- destroy_dialplan_usage_map(&old_usage_map);
- destroy_dialplan_usage_map(&new_usage_map);
+ newlot = ao2_alloc(sizeof(*newlot), parkinglot_destroy);
+ if (!newlot)
+ return NULL;
- ao2_t_callback(parkinglots, OBJ_NODATA, parkinglot_activate_cb, NULL,
- "callback to activate all parking lots");
+ ast_copy_string(newlot->name, name, sizeof(newlot->name));
+ newlot->cfg.is_invalid = 1;/* No config is set yet. */
+ AST_LIST_HEAD_INIT(&newlot->parkings);
- return 0;
+ return newlot;
}
/*!
- * \brief CLI command to list configured features
- * \param e
- * \param cmd
- * \param a
- *
- * \retval CLI_SUCCESS on success.
- * \retval NULL when tab completion is used.
+ * \brief Add parking hints for all defined parking spaces.
+ * \param context Dialplan context to add the hints.
+ * \param start Starting space in parkinglot.
+ * \param stop Ending space in parkinglot.
*/
-static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static void park_add_hints(const char *context, int start, int stop)
{
- int i;
- struct ast_call_feature *feature;
-#define HFS_FORMAT "%-25s %-7s %-7s\n"
-
- switch (cmd) {
+ int numext;
+ char device[AST_MAX_EXTENSION];
+ char exten[10];
- case CLI_INIT:
- e->command = "features show";
- e->usage =
- "Usage: features show\n"
- " Lists configured features\n";
- return NULL;
- case CLI_GENERATE:
- return NULL;
+ for (numext = start; numext <= stop; numext++) {
+ snprintf(exten, sizeof(exten), "%d", numext);
+ snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+ ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
}
+}
- ast_cli(a->fd, HFS_FORMAT, "Builtin Feature", "Default", "Current");
- ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
+/*! Default configuration for default parking lot. */
+static const struct parkinglot_cfg parkinglot_cfg_default_default = {
+ .mohclass = "default",
+ .parkext = DEFAULT_PARK_EXTENSION,
+ .parking_con = "parkedcalls",
+ .parking_start = 701,
+ .parking_stop = 750,
+ .parkingtime = DEFAULT_PARK_TIME,
+ .comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
+ .comebackcontext = DEFAULT_COMEBACK_CONTEXT,
+ .comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
+};
- ast_cli(a->fd, HFS_FORMAT, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
+/*! Default configuration for normal parking lots. */
+static const struct parkinglot_cfg parkinglot_cfg_default = {
+ .parkext = DEFAULT_PARK_EXTENSION,
+ .parkingtime = DEFAULT_PARK_TIME,
+ .comebackdialtime = DEFAULT_COMEBACK_DIAL_TIME,
+ .comebackcontext = DEFAULT_COMEBACK_CONTEXT,
+ .comebacktoorigin = DEFAULT_COMEBACK_TO_ORIGIN,
+};
- ast_rdlock_call_features();
- for (i = 0; i < FEATURES_COUNT; i++)
- ast_cli(a->fd, HFS_FORMAT, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
- ast_unlock_call_features();
+/*!
+ * \internal
+ * \brief Activate the given parkinglot.
+ *
+ * \param parkinglot Parking lot to activate.
+ *
+ * \details
+ * Insert into the dialplan the context, parking lot access
+ * extension, and optional dialplan hints.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int parkinglot_activate(struct ast_parkinglot *parkinglot)
+{
+ int disabled = 0;
+ char app_data[5 + AST_MAX_CONTEXT];
- ast_cli(a->fd, "\n");
- ast_cli(a->fd, HFS_FORMAT, "Dynamic Feature", "Default", "Current");
- ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
- ast_rdlock_call_features();
- if (AST_LIST_EMPTY(&feature_list)) {
- ast_cli(a->fd, "(none)\n");
+ /* Create Park option list. Must match with struct park_app_args options. */
+ if (parkinglot->cfg.parkext_exclusive) {
+ /* Specify the parking lot this parking extension parks calls. */
+ snprintf(app_data, sizeof(app_data), ",,,,,%s", parkinglot->name);
} else {
- AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
- ast_cli(a->fd, HFS_FORMAT, feature->sname, "no def", feature->exten);
- }
+ /* The dialplan must specify which parking lot to use. */
+ app_data[0] = '\0';
}
- ast_unlock_call_features();
- ast_cli(a->fd, "\nFeature Groups:\n");
- ast_cli(a->fd, "---------------\n");
- AST_RWLIST_RDLOCK(&feature_groups);
- if (AST_RWLIST_EMPTY(&feature_groups)) {
- ast_cli(a->fd, "(none)\n");
- } else {
- struct feature_group *fg;
- struct feature_group_exten *fge;
+ /* Create context */
+ if (!ast_context_find_or_create(NULL, NULL, parkinglot->cfg.parking_con, registrar)) {
+ ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n",
+ parkinglot->cfg.parking_con);
+ disabled = 1;
- AST_RWLIST_TRAVERSE(&feature_groups, fg, entry) {
- ast_cli(a->fd, "===> Group: %s\n", fg->gname);
- AST_LIST_TRAVERSE(&fg->features, fge, entry) {
- ast_cli(a->fd, "===> --> %s (%s)\n", fge->feature->sname, fge->exten);
- }
+ /* Add a parking extension into the context */
+ } else if (ast_add_extension(parkinglot->cfg.parking_con, 1, parkinglot->cfg.parkext,
+ 1, NULL, NULL, parkcall, ast_strdup(app_data), ast_free_ptr, registrar)) {
+ ast_log(LOG_ERROR, "Could not create parking lot %s access exten %s@%s\n",
+ parkinglot->name, parkinglot->cfg.parkext, parkinglot->cfg.parking_con);
+ disabled = 1;
+ } else {
+ /* Add parking hints */
+ if (parkinglot->cfg.parkaddhints) {
+ park_add_hints(parkinglot->cfg.parking_con, parkinglot->cfg.parking_start,
+ parkinglot->cfg.parking_stop);
}
- }
- AST_RWLIST_UNLOCK(&feature_groups);
- ast_cli(a->fd, "\n");
+ /*
+ * XXX Not sure why we should need to notify the metermaids for
+ * this exten. It was originally done for the default parking
+ * lot entry exten only but should be done for all entry extens
+ * if we do it for one.
+ */
+ /* Notify metermaids about parking lot entry exten state. */
+ notify_metermaids(parkinglot->cfg.parkext, parkinglot->cfg.parking_con,
+ AST_DEVICE_INUSE);
+ }
- return CLI_SUCCESS;
+ parkinglot->disabled = disabled;
+ return disabled ? -1 : 0;
}
int ast_features_reload(void)
ast_context_destroy(con, registrar);
}
- res = load_config(1);
+ res = ast_features_config_reload();
ast_mutex_unlock(&features_reload_lock);
return res;
struct ast_bridge_features *features, int play_tone)
{
RAII_VAR(struct ast_bridge *, chan_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
struct ast_channel *bridge_chan = NULL;
+ const char *tone = NULL;
ast_channel_lock(chan);
chan_bridge = ast_channel_get_bridge(chan);
+ xfer_cfg = ast_get_chan_features_xfer_config(chan);
+ if (!xfer_cfg) {
+ ast_log(LOG_ERROR, "Unable to determine what tone to play to channel.\n");
+ } else {
+ tone = ast_strdupa(xfer_cfg->xfersound);
+ }
ast_channel_unlock(chan);
if (chan_bridge) {
}
}
- if (play_tone && !ast_strlen_zero(xfersound)) {
+ if (play_tone && !ast_strlen_zero(tone)) {
struct ast_channel *play_chan = bridge_chan ?: chan;
RAII_VAR(struct ast_bridge_channel *, play_bridge_channel, NULL, ao2_cleanup);
ast_log(LOG_WARNING, "Unable to play tone for channel %s. Unable to get bridge channel\n",
ast_channel_name(play_chan));
} else {
- ast_bridge_channel_queue_playfile(play_bridge_channel, NULL, xfersound, NULL);
+ ast_bridge_channel_queue_playfile(play_bridge_channel, NULL, tone, NULL);
}
}
return 0;
}
static struct ast_cli_entry cli_features[] = {
- AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"),
};
{
struct ast_channel *target;/*!< Potential pickup target */
int res = -1;
+ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
+ const char *pickup_sound;
+ const char *fail_sound;
ast_debug(1, "pickup attempt by %s\n", ast_channel_name(chan));
+ ast_channel_lock(chan);
+ pickup_cfg = ast_get_chan_features_pickup_config(chan);
+ if (!pickup_cfg) {
+ ast_log(LOG_ERROR, "Unable to retrieve pickup configuration. Unable to play pickup sounds\n");
+ }
+ pickup_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupsound : "");
+ fail_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupfailsound : "");
+ ast_channel_unlock(chan);
/* The found channel is already locked. */
target = ast_pickup_find_by_group(chan);
res = ast_do_pickup(chan, target);
ast_channel_unlock(target);
if (!res) {
- if (!ast_strlen_zero(pickupsound)) {
- pbx_builtin_setvar_helper(target, "BRIDGE_PLAY_SOUND", pickupsound);
+ if (!ast_strlen_zero(pickup_sound)) {
+ pbx_builtin_setvar_helper(target, "BRIDGE_PLAY_SOUND", pickup_sound);
}
} else {
ast_log(LOG_WARNING, "pickup %s failed by %s\n", ast_channel_name(target), ast_channel_name(chan));
if (res < 0) {
ast_debug(1, "No call pickup possible... for %s\n", ast_channel_name(chan));
- if (!ast_strlen_zero(pickupfailsound)) {
+ if (!ast_strlen_zero(fail_sound)) {
ast_answer(chan);
- ast_stream_and_wait(chan, pickupfailsound, "");
+ ast_stream_and_wait(chan, fail_sound, "");
}
}
}
#endif /* defined(TEST_FRAMEWORK) */
-/*!
- * \internal
- * \brief Get parkingtime for a channel
- */
-static unsigned int get_parkingtime(struct ast_channel *chan, struct ast_parkinglot *parkinglot)
-{
- const char *parkinglot_name;
- struct feature_datastore *feature_ds;
- unsigned int parkingtime;
-
- ast_channel_lock(chan);
-
- feature_ds = get_feature_ds(chan);
- if (feature_ds && feature_ds->parkingtime_is_set) {
- parkingtime = feature_ds->parkingtime;
- ast_channel_unlock(chan);
- return parkingtime;
- }
-
- parkinglot_name = ast_strdupa(S_OR(ast_channel_parkinglot(chan), ""));
-
- ast_channel_unlock(chan);
-
- if (!parkinglot) {
- if (!ast_strlen_zero(parkinglot_name)) {
- parkinglot = find_parkinglot(parkinglot_name);
- }
-
- if