diff --git a/apps/app_dial.c b/apps/app_dial.c index b6f1ce8723a30d53c65b4a7d92f225acc3fc07d9..b0caa5c6be4535bce95ed68a8f66bf3852f1e29a 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -69,6 +69,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/dial.h" #include "asterisk/stasis_channels.h" #include "asterisk/bridging.h" +#include "asterisk/features_config.h" /*** DOCUMENTATION <application name="Dial" language="en_US"> @@ -1074,7 +1075,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, int caller_entertained = outgoing && ast_test_flag64(outgoing, OPT_MUSICBACK | OPT_RINGBACK); struct ast_party_connected_line connected_caller; - struct ast_str *featurecode = ast_str_alloca(FEATURE_MAX_LEN + 1); + struct ast_str *featurecode = ast_str_alloca(AST_FEATURE_MAX_LEN + 1); int cc_recall_core_id; int is_cc_recall; int cc_frame_received = 0; @@ -1701,22 +1702,31 @@ skip_frame:; static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str **featurecode) { - struct ast_flags features = { AST_FEATURE_DISCONNECT }; /* only concerned with disconnect feature */ - struct ast_call_feature feature = { 0, }; + char disconnect_code[AST_FEATURE_MAX_LEN]; int res; ast_str_append(featurecode, 1, "%c", code); - res = ast_feature_detect(chan, &features, ast_str_buffer(*featurecode), &feature); - - if (res != AST_FEATURE_RETURN_STOREDIGITS) { + res = ast_get_builtin_feature(chan, "disconnect", disconnect_code, sizeof(disconnect_code)); + if (res) { ast_str_reset(*featurecode); + return 0; } - if (feature.feature_mask & AST_FEATURE_DISCONNECT) { - return 1; + + if (strlen(disconnect_code) > ast_str_strlen(*featurecode)) { + /* Could be a partial match, anyway */ + if (strncmp(disconnect_code, ast_str_buffer(*featurecode), ast_str_strlen(*featurecode))) { + ast_str_reset(*featurecode); + } + return 0; } - return 0; + if (strcmp(disconnect_code, ast_str_buffer(*featurecode))) { + ast_str_reset(*featurecode); + return 0; + } + + return 1; } /* returns true if there is a valid privacy reply */ diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c index 9574bb844587200d1ec22c9d75d9824a9587f640..e11b280cdb67dc77f8ae2166fb6fb655185c0b1f 100644 --- a/bridges/bridge_builtin_features.c +++ b/bridges/bridge_builtin_features.c @@ -49,6 +49,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/astobj2.h" #include "asterisk/pbx.h" #include "asterisk/parking.h" +#include "asterisk/features_config.h" /*! * \brief Helper function that presents dialtone and grabs extension @@ -59,6 +60,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context) { int res; + int digit_timeout; + RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup); + + ast_channel_lock(chan); + xfer_cfg = ast_get_chan_features_xfer_config(chan); + if (!xfer_cfg) { + ast_log(LOG_ERROR, "Unable to get transfer configuration\n"); + ast_channel_unlock(chan); + return -1; + } + digit_timeout = xfer_cfg->transferdigittimeout; + ast_channel_unlock(chan); /* Play the simple "transfer" prompt out and wait */ res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY); @@ -73,8 +86,7 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len } /* Drop to dialtone so they can enter the extension they want to transfer to */ -/* BUGBUG the timeout needs to be configurable from features.conf. */ - res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, 3000); + res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, digit_timeout); if (res < 0) { /* Hangup or error */ res = -1; @@ -265,6 +277,11 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt; const char *context; enum atxfer_code transfer_code = ATXFER_INCOMPLETE; + const char *atxfer_abort; + const char *atxfer_threeway; + const char *atxfer_complete; + const char *fail_sound; + RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup); ast_bridge_channel_write_hold(bridge_channel, NULL); @@ -273,6 +290,22 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg ast_channel_lock(bridge_channel->chan); context = ast_strdupa(get_transfer_context(bridge_channel->chan, attended_transfer ? attended_transfer->context : NULL)); + xfer_cfg = ast_get_chan_features_xfer_config(bridge_channel->chan); + if (!xfer_cfg) { + ast_log(LOG_ERROR, "Unable to get transfer configuration options\n"); + ast_channel_unlock(bridge_channel->chan); + return 0; + } + if (attended_transfer) { + atxfer_abort = ast_strdupa(S_OR(attended_transfer->abort, xfer_cfg->atxferabort)); + atxfer_threeway = ast_strdupa(S_OR(attended_transfer->threeway, xfer_cfg->atxferthreeway)); + atxfer_complete = ast_strdupa(S_OR(attended_transfer->complete, xfer_cfg->atxfercomplete)); + } else { + atxfer_abort = ast_strdupa(xfer_cfg->atxferabort); + atxfer_threeway = ast_strdupa(xfer_cfg->atxferthreeway); + atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete); + } + fail_sound = ast_strdupa(xfer_cfg->xferfailsound); ast_channel_unlock(bridge_channel->chan); /* Grab the extension to transfer to */ @@ -288,36 +321,27 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg if (!peer) { ast_bridge_merge_inhibit(bridge, -1); ao2_ref(bridge, -1); -/* BUGBUG beeperr needs to be configurable from features.conf */ - ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE); + ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); ast_bridge_channel_write_unhold(bridge_channel); return 0; } -/* BUGBUG bridging API features does not support features.conf featuremap */ /* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */ /* Setup a DTMF menu to control the transfer. */ if (ast_bridge_features_init(&caller_features) || ast_bridge_hangup_hook(&caller_features, attended_transfer_complete, &transfer_code, NULL, 0) - || ast_bridge_dtmf_hook(&caller_features, - attended_transfer && !ast_strlen_zero(attended_transfer->abort) - ? attended_transfer->abort : "*1", + || ast_bridge_dtmf_hook(&caller_features, atxfer_abort, attended_transfer_abort, &transfer_code, NULL, 0) - || ast_bridge_dtmf_hook(&caller_features, - attended_transfer && !ast_strlen_zero(attended_transfer->complete) - ? attended_transfer->complete : "*2", + || ast_bridge_dtmf_hook(&caller_features, atxfer_complete, attended_transfer_complete, &transfer_code, NULL, 0) - || ast_bridge_dtmf_hook(&caller_features, - attended_transfer && !ast_strlen_zero(attended_transfer->threeway) - ? attended_transfer->threeway : "*3", + || ast_bridge_dtmf_hook(&caller_features, atxfer_threeway, attended_transfer_threeway, &transfer_code, NULL, 0)) { ast_bridge_features_cleanup(&caller_features); ast_hangup(peer); ast_bridge_merge_inhibit(bridge, -1); ao2_ref(bridge, -1); -/* BUGBUG beeperr needs to be configurable from features.conf */ - ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE); + ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); ast_bridge_channel_write_unhold(bridge_channel); return 0; } @@ -330,8 +354,7 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg ast_hangup(peer); ast_bridge_merge_inhibit(bridge, -1); ao2_ref(bridge, -1); -/* BUGBUG beeperr needs to be configurable from features.conf */ - ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE); + ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); ast_bridge_channel_write_unhold(bridge_channel); return 0; } @@ -345,8 +368,7 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg ast_hangup(peer); ast_bridge_merge_inhibit(bridge, -1); ao2_ref(bridge, -1); -/* BUGBUG beeperr needs to be configurable from features.conf */ - ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE); + ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); ast_bridge_channel_write_unhold(bridge_channel); return 0; } @@ -415,7 +437,7 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg if (xfer_failed) { ast_hangup(peer); if (!ast_check_hangup_locked(bridge_channel->chan)) { - ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE); + ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE); } ast_bridge_channel_write_unhold(bridge_channel); } diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c index 00c25b9c12ce1991440a7ac554bf7992f49bc274..92de023919906d38740d451cca59f40980ac05dc 100644 --- a/channels/chan_dahdi.c +++ b/channels/chan_dahdi.c @@ -130,6 +130,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/paths.h" #include "asterisk/ccss.h" #include "asterisk/data.h" +#include "asterisk/features_config.h" /*** DOCUMENTATION <application name="DAHDISendKeypadFacility" language="en_US"> @@ -10137,15 +10138,15 @@ static int dahdi_dnd(struct dahdi_pvt *dahdichan, int flag) return 0; } -static int canmatch_featurecode(const char *exten) +static int canmatch_featurecode(const char *pickupexten, const char *exten) { int extlen = strlen(exten); - const char *pickup_ext; + if (!extlen) { return 1; } - pickup_ext = ast_pickup_ext(); - if (extlen < strlen(pickup_ext) && !strncmp(pickup_ext, exten, extlen)) { + + if (extlen < strlen(pickupexten) && !strncmp(pickupexten, exten, extlen)) { return 1; } /* hardcoded features are *60, *67, *69, *70, *72, *73, *78, *79, *82, *0 */ @@ -10191,6 +10192,8 @@ static void *analog_ss_thread(void *data) int res; int idx; struct ast_format tmpfmt; + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickupexten; ast_mutex_lock(&ss_thread_lock); ss_thread_count++; @@ -10210,6 +10213,17 @@ static void *analog_ss_thread(void *data) ast_hangup(chan); goto quit; } + + 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 options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + ast_channel_unlock(chan); + if (p->dsp) ast_dsp_digitreset(p->dsp); switch (p->sig) { @@ -10576,7 +10590,7 @@ static void *analog_ss_thread(void *data) memset(exten, 0, sizeof(exten)); timeout = firstdigittimeout; - } else if (!strcmp(exten,ast_pickup_ext())) { + } else if (!strcmp(exten, pickupexten)) { /* Scan all channels and see if there are any * ringing channels that have call groups * that equal this channels pickup group @@ -10708,7 +10722,7 @@ static void *analog_ss_thread(void *data) } } else if (!ast_canmatch_extension(chan, ast_channel_context(chan), exten, 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) - && !canmatch_featurecode(exten)) { + && !canmatch_featurecode(pickupexten, exten)) { ast_debug(1, "Can't match %s from '%s' in context %s\n", exten, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<Unknown Caller>"), ast_channel_context(chan)); diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c index df82061b8e59d4aa2eb1c2bc63a0116a6a861536..9080dbaf0380741fd371ab52195b2a0997eed7b4 100644 --- a/channels/chan_mgcp.c +++ b/channels/chan_mgcp.c @@ -84,6 +84,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/pktccops.h" #include "asterisk/stasis.h" #include "asterisk/bridging.h" +#include "asterisk/features_config.h" /* * Define to work around buggy dlink MGCP phone firmware which @@ -2971,9 +2972,21 @@ static void *mgcp_ss(void *data) int res= 0; int getforward = 0; int loop_pause = 100; + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickupexten; len = strlen(p->dtmf_buf); + 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 options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + ast_channel_unlock(chan); + while (len < AST_MAX_EXTENSION - 1) { ast_debug(1, "Dtmf buffer '%s' for '%s@%s'\n", p->dtmf_buf, p->name, p->parent->name); res = 1; /* Assume that we will get a digit */ @@ -3065,7 +3078,7 @@ static void *mgcp_ss(void *data) len = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); timeout = firstdigittimeout; - } else if (!strcmp(p->dtmf_buf,ast_pickup_ext())) { + } else if (!strcmp(p->dtmf_buf, pickupexten)) { /* Scan all channels and see if any there * ringing channqels with that have call groups * that equal this channels pickup group diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c index c68538e662864db621c7d43ba2e9c89cd565c25c..1a8f0d9802ed3700334a741a1ff01fdfe5430ca6 100644 --- a/channels/chan_misdn.c +++ b/channels/chan_misdn.c @@ -102,6 +102,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/causes.h" #include "asterisk/format.h" #include "asterisk/format_cap.h" +#include "asterisk/features_config.h" #include "chan_misdn_config.h" #include "isdn_lib.h" @@ -10071,6 +10072,9 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) } if (ch->state == MISDN_WAITING4DIGS) { + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickupexten; + /* Ok, incomplete Setup, waiting till extension exists */ if (ast_strlen_zero(bc->info_dad) && ! ast_strlen_zero(bc->keypad)) { chan_misdn_log(1, bc->port, " --> using keypad as info\n"); @@ -10080,8 +10084,18 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) strncat(bc->dialed.number, bc->info_dad, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1); ast_channel_exten_set(ch->ast, bc->dialed.number); + ast_channel_lock(ch->ast); + pickup_cfg = ast_get_chan_features_pickup_config(ch->ast); + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + ast_channel_unlock(ch->ast); + /* Check for Pickup Request first */ - if (!strcmp(ast_channel_exten(ch->ast), ast_pickup_ext())) { + if (!strcmp(ast_channel_exten(ch->ast), pickupexten)) { if (ast_pickup_call(ch->ast)) { hangup_chan(ch, bc); } else { @@ -10169,6 +10183,8 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) int ai; int im; int append_msn = 0; + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickupexten; if (ch) { switch (ch->state) { @@ -10224,6 +10240,16 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) return RESPONSE_RELEASE_SETUP; } + 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 options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + ast_channel_unlock(chan); + if ((exceed = add_in_calls(bc->port))) { char tmp[16]; snprintf(tmp, sizeof(tmp), "%d", exceed); @@ -10315,7 +10341,7 @@ cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) } /* Check for Pickup Request first */ - if (!strcmp(ast_channel_exten(chan), ast_pickup_ext())) { + if (!strcmp(ast_channel_exten(chan), pickupexten)) { if (!ch->noautorespond_on_setup) { /* Sending SETUP_ACK */ misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE); diff --git a/channels/chan_sip.c b/channels/chan_sip.c index fe67d28c7672abff564996515dba3af46e0effe0..eb79d237ef8c9dd2be84e7f317398189e629d41b 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -296,6 +296,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/app.h" #include "asterisk/bridging.h" #include "asterisk/stasis_endpoints.h" +#include "asterisk/features_config.h" /*** DOCUMENTATION <application name="SIPDtmfMode" language="en_US"> @@ -17662,6 +17663,16 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re char tmpf[256] = "", *from = NULL; struct sip_request *req; char *decoded_uri; + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(p->owner), ao2_cleanup); + const char *pickupexten; + + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + /* Don't need to duplicate since channel is locked for the duration of this function */ + pickupexten = pickup_cfg->pickupexten; + } req = oreq; if (!req) { @@ -17772,7 +17783,7 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re return SIP_GET_DEST_EXTEN_FOUND; } if (ast_exists_extension(NULL, p->context, decoded_uri, 1, S_OR(p->cid_num, from)) - || !strcmp(decoded_uri, ast_pickup_ext())) { + || !strcmp(decoded_uri, pickupexten)) { if (!oreq) { ast_string_field_set(p, exten, decoded_uri); } @@ -17800,7 +17811,7 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re if (ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP) && (ast_canmatch_extension(NULL, p->context, uri, 1, S_OR(p->cid_num, from)) || ast_canmatch_extension(NULL, p->context, decoded_uri, 1, S_OR(p->cid_num, from)) - || !strncmp(decoded_uri, ast_pickup_ext(), strlen(decoded_uri)))) { + || !strncmp(decoded_uri, pickupexten, strlen(decoded_uri)))) { /* Overlap dialing is enabled and we need more digits to match an extension. */ return SIP_GET_DEST_EXTEN_MATCHMORE; } @@ -21699,7 +21710,8 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req) * on phone calls. */ - struct ast_call_feature *feat = NULL; + char feat[AST_FEATURE_MAX_LEN]; + int feat_res = -1; int j; struct ast_frame f = { AST_FRAME_DTMF, }; int suppress_warning = 0; /* Supress warning if the feature is blank */ @@ -21711,43 +21723,40 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req) } /* first, get the feature string, if it exists */ - ast_rdlock_call_features(); if (p->relatedpeer) { if (!strcasecmp(c, "on")) { if (ast_strlen_zero(p->relatedpeer->record_on_feature)) { suppress_warning = 1; } else { - feat = ast_find_call_feature(p->relatedpeer->record_on_feature); + feat_res = ast_get_feature(p->owner, p->relatedpeer->record_on_feature, feat, sizeof(feat)); } } else if (!strcasecmp(c, "off")) { if (ast_strlen_zero(p->relatedpeer->record_off_feature)) { suppress_warning = 1; } else { - feat = ast_find_call_feature(p->relatedpeer->record_off_feature); + feat_res = ast_get_feature(p->owner, p->relatedpeer->record_off_feature, feat, sizeof(feat)); } } else { ast_log(LOG_ERROR, "Received INFO requesting to record with invalid value: %s\n", c); } } - if (!feat || ast_strlen_zero(feat->exten)) { + if (feat_res || ast_strlen_zero(feat)) { if (!suppress_warning) { ast_log(LOG_WARNING, "Recording requested, but no One Touch Monitor registered. (See features.conf)\n"); } /* 403 means that we don't support this feature, so don't request it again */ transmit_response(p, "403 Forbidden", req); - ast_unlock_call_features(); return; } /* Send the feature code to the PBX as DTMF, just like the handset had sent it */ f.len = 100; - for (j = 0; j < strlen(feat->exten); j++) { - f.subclass.integer = feat->exten[j]; + for (j = 0; j < strlen(feat); j++) { + f.subclass.integer = feat[j]; ast_queue_frame(p->owner, &f); if (sipdebug) { ast_verbose("* DTMF-relay event faked: %c\n", f.subclass.integer); } } - ast_unlock_call_features(); ast_debug(1, "Got a Request to Record the channel, state %s\n", c); transmit_response(p, "200 OK", req); @@ -25650,6 +25659,15 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str if (c) { /* We have a call -either a new call or an old one (RE-INVITE) */ enum ast_channel_state c_state = ast_channel_state(c); + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(c), ao2_cleanup); + const char *pickupexten; + + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } if (c_state != AST_STATE_UP && reinvite && (p->invitestate == INV_TERMINATED || p->invitestate == INV_CONFIRMED)) { @@ -25671,7 +25689,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str transmit_provisional_response(p, "100 Trying", req, 0); p->invitestate = INV_PROCEEDING; ast_setstate(c, AST_STATE_RING); - if (strcmp(p->exten, ast_pickup_ext())) { /* Call to extension -start pbx on this call */ + if (strcmp(p->exten, pickupexten)) { /* Call to extension -start pbx on this call */ enum ast_pbx_result result; result = ast_pbx_start(c); diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c index 426e6ab10b351175e03ea157c2d0fd28663eb804..28d332f62507016219ee9902bd871e2e16afc343 100644 --- a/channels/chan_unistim.c +++ b/channels/chan_unistim.c @@ -76,6 +76,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/features.h" #include "asterisk/astobj2.h" #include "asterisk/astdb.h" +#include "asterisk/features_config.h" #define DEFAULTCONTEXT "default" @@ -3086,11 +3087,25 @@ static void handle_call_outgoing(struct unistimsession *s) send_favorite_short(sub->softkey, FAV_ICON_OFFHOOK_BLACK, s); s->device->selected = -1; if (!sub->owner) { /* A call is already in progress ? */ + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickupexten; + c = unistim_new(sub, AST_STATE_DOWN, NULL); /* No, starting a new one */ if (!sub->rtp) { /* Need to start RTP before calling ast_pbx_run */ start_rtp(sub); } - if (c && !strcmp(s->device->phone_number, ast_pickup_ext())) { + if (c) { + ast_channel_lock(c); + pickup_cfg = ast_get_chan_features_pickup_config(c); + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + ast_channel_unlock(c); + } + if (c && !strcmp(s->device->phone_number, pickupexten)) { if (unistimdebug) { ast_verb(0, "Try to pickup in unistim_new\n"); } @@ -4099,8 +4114,17 @@ static void key_main_page(struct unistimsession *pte, char keycode) ast_mutex_unlock(&devicelock); show_extension_page(pte); } else { /* Pickup function */ + /* XXX Is there a way to get a specific channel here? */ + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, + ast_get_chan_features_pickup_config(NULL), ao2_cleanup); + + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + break; + } + pte->device->selected = -1; - ast_copy_string(pte->device->phone_number, ast_pickup_ext(), + ast_copy_string(pte->device->phone_number, pickup_cfg->pickupexten, sizeof(pte->device->phone_number)); handle_call_outgoing(pte); } diff --git a/channels/sig_analog.c b/channels/sig_analog.c index e3bd1d0c895766929c39ac67fc2257b224878124..956c4a654a86636ed25cb1cac2d2bc44dc447854 100644 --- a/channels/sig_analog.c +++ b/channels/sig_analog.c @@ -43,6 +43,7 @@ #include "asterisk/features.h" #include "asterisk/cel.h" #include "asterisk/causes.h" +#include "asterisk/features_config.h" #include "sig_analog.h" @@ -1708,15 +1709,13 @@ static int analog_get_sub_fd(struct analog_pvt *p, enum analog_sub sub) #define ANALOG_NEED_MFDETECT(p) (((p)->sig == ANALOG_SIG_FEATDMF) || ((p)->sig == ANALOG_SIG_FEATDMF_TA) || ((p)->sig == ANALOG_SIG_E911) || ((p)->sig == ANALOG_SIG_FGC_CAMA) || ((p)->sig == ANALOG_SIG_FGC_CAMAMF) || ((p)->sig == ANALOG_SIG_FEATB)) -static int analog_canmatch_featurecode(const char *exten) +static int analog_canmatch_featurecode(const char *pickupexten, const char *exten) { int extlen = strlen(exten); - const char *pickup_ext; if (!extlen) { return 1; } - pickup_ext = ast_pickup_ext(); - if (extlen < strlen(pickup_ext) && !strncmp(pickup_ext, exten, extlen)) { + if (extlen < strlen(pickupexten) && !strncmp(pickupexten, exten, extlen)) { return 1; } /* hardcoded features are *60, *67, *69, *70, *72, *73, *78, *79, *82, *0 */ @@ -1756,6 +1755,8 @@ static void *__analog_ss_thread(void *data) int res; int idx; struct ast_callid *callid; + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickupexten; analog_increase_ss_count(); @@ -1786,6 +1787,17 @@ static void *__analog_ss_thread(void *data) ast_hangup(chan); goto quit; } + + 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 options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + ast_channel_unlock(chan); + analog_dsp_reset_and_flush_digits(p); switch (p->sig) { case ANALOG_SIG_FEATD: @@ -2190,7 +2202,7 @@ static void *__analog_ss_thread(void *data) memset(exten, 0, sizeof(exten)); timeout = analog_firstdigittimeout; - } else if (!strcmp(exten,ast_pickup_ext())) { + } else if (!strcmp(exten, pickupexten)) { /* Scan all channels and see if there are any * ringing channels that have call groups * that equal this channels pickup group @@ -2334,7 +2346,7 @@ static void *__analog_ss_thread(void *data) } } else if (!ast_canmatch_extension(chan, ast_channel_context(chan), exten, 1, ast_channel_caller(chan)->id.number.valid ? ast_channel_caller(chan)->id.number.str : NULL) - && !analog_canmatch_featurecode(exten)) { + && !analog_canmatch_featurecode(pickupexten, exten)) { ast_debug(1, "Can't match %s from '%s' in context %s\n", exten, ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str ? ast_channel_caller(chan)->id.number.str : "<Unknown Caller>", diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 185f3935d058f21cfb32e6fa5f744211e8c9e095..0adde37f2024e6a8c91d4beb935b13b5624f565b 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -38,6 +38,7 @@ #include "asterisk/http_websocket.h" #include "asterisk/rtp_engine.h" #include "asterisk/netsock2.h" +#include "asterisk/features_config.h" #ifndef FALSE #define FALSE 0 @@ -762,8 +763,8 @@ struct sip_settings { struct sip_proxy outboundproxy; /*!< Outbound proxy */ char default_context[AST_MAX_CONTEXT]; char default_subscribecontext[AST_MAX_CONTEXT]; - char default_record_on_feature[FEATURE_MAX_LEN]; - char default_record_off_feature[FEATURE_MAX_LEN]; + char default_record_on_feature[AST_FEATURE_MAX_LEN]; + char default_record_off_feature[AST_FEATURE_MAX_LEN]; struct ast_acl_list *contact_acl; /*! \brief Global list of addresses dynamic peers are not allowed to use */ struct ast_format_cap *caps; /*!< Supported codecs */ int tcp_enabled; diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index ec39ecf82322891c7f2479b34efea0744b033b00..c2edc18e7a2ff2a9cc73c0c8e28a78bf475ea24c 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -927,6 +927,9 @@ enum { AST_FEATURE_AUTOMIXMON = (1 << 6), }; +#define AST_FEATURE_DTMF_MASK (AST_FEATURE_REDIRECT | AST_FEATURE_DISCONNECT |\ + AST_FEATURE_ATXFER | AST_FEATURE_AUTOMON | AST_FEATURE_PARKCALL | AST_FEATURE_AUTOMIXMON) + /*! \brief bridge configuration */ struct ast_bridge_config { struct ast_flags features_caller; diff --git a/include/asterisk/features.h b/include/asterisk/features.h index 4f556138138bc8aade90d189a9d5473b6c50adee..9b586506fe9ed68025d762c4b4f45b342e4f06a8 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -62,20 +62,6 @@ enum { AST_FEATURE_FLAG_BYBOTH = (3 << 3), }; -struct ast_call_feature { - int feature_mask; - char *fname; - char sname[FEATURE_SNAME_LEN]; - char exten[FEATURE_MAX_LEN]; - char default_exten[FEATURE_MAX_LEN]; - ast_feature_operation operation; - unsigned int flags; - char app[FEATURE_APP_LEN]; - char app_args[FEATURE_APP_ARGS_LEN]; - char moh_class[FEATURE_MOH_LEN]; - AST_LIST_ENTRY(ast_call_feature) feature_entry; -}; - /*! * \brief Park a call and read back parked location * @@ -166,9 +152,6 @@ int ast_masq_park_call_exten(struct ast_channel *park_me, struct ast_channel *pa */ int ast_parking_ext_valid(const char *exten_str, struct ast_channel *chan, const char *context); -/*! \brief Determine system call pickup extension */ -const char *ast_pickup_ext(void); - /*! * \brief Simulate a DTMF end on a broken bridge channel. * @@ -221,39 +204,6 @@ int ast_pickup_call(struct ast_channel *chan); */ int ast_do_pickup(struct ast_channel *chan, struct ast_channel *target); -/*! - * \brief register new feature into feature_set - * \param feature an ast_call_feature object which contains a keysequence - * and a callback function which is called when this keysequence is pressed - * during a call. -*/ -void ast_register_feature(struct ast_call_feature *feature); - -/*! - * \brief unregister feature from feature_set - * \param feature the ast_call_feature object which was registered before -*/ -void ast_unregister_feature(struct ast_call_feature *feature); - -/*! - * \brief detect a feature before bridging - * \param chan - * \param features an ast_flags ptr - * \param code ptr of input code - * \param feature - * \retval ast_call_feature ptr to be set if found -*/ -int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature); - -/*! - * \brief look for a call feature entry by its sname - * \param name a string ptr, should match "automon", "blindxfer", "atxfer", etc. -*/ -struct ast_call_feature *ast_find_call_feature(const char *name); - -void ast_rdlock_call_features(void); -void ast_unlock_call_features(void); - /*! \brief Reload call features from features.conf */ int ast_features_reload(void); diff --git a/include/asterisk/features_config.h b/include/asterisk/features_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a80fa7968d15e625395cae4a36fe8dca2cf1bd80 --- /dev/null +++ b/include/asterisk/features_config.h @@ -0,0 +1,234 @@ +/* +* Asterisk -- An open source telephony toolkit. +* +* Copyright (C) 2013, Digium, Inc. +* +* Mark Michelson <mmichelson@digium.com> +* +* See http://www.asterisk.org for more information about +* the Asterisk project. Please do not directly contact +* any of the maintainers of this project for assistance; +* the project provides a web site, mailing lists and IRC +* channels for your use. +* +* This program is free software, distributed under the terms of +* the GNU General Public License Version 2. See the LICENSE file +* at the top of the source tree. +*/ + +#ifndef _FEATURES_CONFIG_H +#define _FEATURES_CONFIG_H + +#include "asterisk/stringfields.h" + +struct ast_channel; + +/*! + * \brief General features configuration items + */ +struct ast_features_general_config { + AST_DECLARE_STRING_FIELDS( + /*! Sound played when automon or automixmon features are used */ + AST_STRING_FIELD(courtesytone); + ); + /*! Milliseconds allowed between digit presses when entering feature code */ + unsigned int featuredigittimeout; +}; + +/*! + * \brief Get the general configuration options for a channel + * + * \note The channel should be locked before calling this function. + * \note The returned value has its reference count incremented. + * + * If no channel is provided, then the global features configuration is returned. + * + * \param chan The channel to get configuration options for + * \retval NULL Failed to get configuration + * \retval non-NULL The general features configuration + */ +struct ast_features_general_config *ast_get_chan_features_general_config(struct ast_channel *chan); + +/*! + * \brief Feature configuration relating to transfers + */ +struct ast_features_xfer_config { + AST_DECLARE_STRING_FIELDS ( + /*! Sound to play when transfer succeeds */ + AST_STRING_FIELD(xfersound); + /*! Sound to play when transfer fails */ + AST_STRING_FIELD(xferfailsound); + /*! DTMF sequence used to abort an attempted atxfer */ + AST_STRING_FIELD(atxferabort); + /*! DTMF sequence used to complete an attempted atxfer */ + AST_STRING_FIELD(atxfercomplete); + /*! DTMF sequence used to turn an attempted atxfer into a three-way call */ + AST_STRING_FIELD(atxferthreeway); + ); + /*! Milliseconds allowed between digit presses when dialing transfer destination */ + unsigned int transferdigittimeout; + /*! Milliseconds to wait for the transfer target to answer a transferred call */ + unsigned int atxfernoanswertimeout; + /*! Milliseconds to wait before attempting to re-dial the transfer target */ + unsigned int atxferloopdelay; + /*! Number of times to re-attempt dialing the transfer target */ + unsigned int atxfercallbackretries; + /*! Determines if the call is dropped on attended transfer failure */ + unsigned int atxferdropcall; +}; + +/*! + * \brief Get the transfer configuration options for a channel + * + * \note The channel should be locked before calling this function. + * \note The returned value has its reference count incremented. + * + * If no channel is provided, then the global transfer configuration is returned. + * + * \param chan The channel to get configuration options for + * \retval NULL Failed to get configuration + * \retval non-NULL The transfer features configuration + */ +struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan); + +/*! + * \brief Configuration relating to call pickup + */ +struct ast_features_pickup_config { + AST_DECLARE_STRING_FIELDS ( + /*! Digit sequence to press to pick up a ringing call */ + AST_STRING_FIELD(pickupexten); + /*! Sound to play to picker when pickup succeeds */ + AST_STRING_FIELD(pickupsound); + /*! Sound to play to picker when pickup fails */ + AST_STRING_FIELD(pickupfailsound); + ); +}; + +/*! + * \brief Get the pickup configuration options for a channel + * + * \note The channel should be locked before calling this function. + * \note The returned value has its reference count incremented. + * + * If no channel is provided, then the global pickup configuration is returned. + * + * \param chan The channel to get configuration options for + * \retval NULL Failed to get configuration + * \retval non-NULL The pickup features configuration + */ +struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan); + +/*! + * \brief Configuration for the builtin features + */ +struct ast_featuremap_config { + AST_DECLARE_STRING_FIELDS ( + /*! Blind transfer DTMF code */ + AST_STRING_FIELD(blindxfer); + /*! Disconnect DTMF code */ + AST_STRING_FIELD(disconnect); + /*! Automon DTMF code */ + AST_STRING_FIELD(automon); + /*! Attended Transfer DTMF code */ + AST_STRING_FIELD(atxfer); + /*! One-touch parking DTMF code */ + AST_STRING_FIELD(parkcall); + /*! Automixmon DTMF code */ + AST_STRING_FIELD(automixmon); + ); +}; + +/*! + * \brief Get the featuremap configuration options for a channel + * + * \note The channel should be locked before calling this function. + * \note The returned value has its reference count incremented. + * + * If no channel is provided, then the global featuremap configuration is returned. + * + * \param chan The channel to get configuration options for + * \retval NULL Failed to get configuration + * \retval non-NULL The pickup features configuration + */ +struct ast_featuremap_config *ast_get_chan_featuremap_config(struct ast_channel *chan); + +/*! + * \brief Get the DTMF code for a builtin feature + * + * \note The channel should be locked before calling this function + * + * If no channel is provided, then the global setting for the option is returned. + * + * \param chan The channel to get the option from + * \param feature The short name of the feature (as it appears in features.conf) + * \param[out] buf The buffer to write the DTMF value into + * \param size The size of the buffer in bytes + * \retval 0 Success + * \retval non-zero Unrecognized builtin feature name + */ +int ast_get_builtin_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len); + +/*! + * \brief Get the DTMF code for a call feature + * + * \note The channel should be locked before calling this function + * + * If no channel is provided, then the global setting for the option is returned. + * + * This function is like \ref ast_get_builtin_feature except that it will + * also check the applicationmap in addition to the builtin features. + * + * \param chan The channel to get the option from + * \param feature The short name of the feature + * \param[out] buf The buffer to write the DTMF value into + * \param size The size of the buffer in bytes + * \retval 0 Success + * \retval non-zero Unrecognized feature name + */ +int ast_get_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len); + +#define AST_FEATURE_MAX_LEN 11 + +/*! + * \brief An applicationmap configuration item + */ +struct ast_applicationmap_item { + AST_DECLARE_STRING_FIELDS ( + /* Name of the item */ + AST_STRING_FIELD(name); + /* Name Dialplan application that is invoked by the feature */ + AST_STRING_FIELD(app); + /* Data to pass to the application */ + AST_STRING_FIELD(app_data); + /* Music-on-hold class to play to party on which feature is not activated */ + AST_STRING_FIELD(moh_class); + ); + /* DTMF key sequence used to activate the feature */ + char dtmf[AST_FEATURE_MAX_LEN]; + /* If true, activate on party that input the sequence, otherwise activate on the other party */ + unsigned int activate_on_self; +}; + +/*! + * \brief Get the applicationmap for a given channel. + * + * \note The channel should be locked before calling this function. + * + * This uses the value of the DYNAMIC_FEATURES channel variable to build a + * custom applicationmap for this channel. The returned container has + * applicationmap_items inside. + * + * \param chan The channel for which applicationmap is being retrieved. + * \retval NULL An error occurred or the channel has no dynamic features. + * \retval non-NULL A container of applicationmap_items pertaining to the channel. + */ +struct ao2_container *ast_get_chan_applicationmap(struct ast_channel *chan); + +void ast_features_config_shutdown(void); + +int ast_features_config_reload(void); + +int ast_features_config_init(void); + +#endif /* _FEATURES_CONFIG_H */ diff --git a/main/bridging.c b/main/bridging.c index 93db3b0ef49cbf6fc0a54a97385e860b9a1c12df..c59638736f71b647062a4a0af06a99efe39b0cfa 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/parking.h" #include "asterisk/core_local.h" +#include "asterisk/features_config.h" /*! All bridges container. */ static struct ao2_container *bridges; @@ -1914,6 +1915,18 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel) struct ast_bridge_hook *hook = NULL; char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = ""; size_t dtmf_len = 0; + unsigned int digit_timeout; + RAII_VAR(struct ast_features_general_config *, gen_cfg, NULL, ao2_cleanup); + + ast_channel_lock(bridge_channel->chan); + gen_cfg = ast_get_chan_features_general_config(bridge_channel->chan); + if (!gen_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve features configuration.\n"); + ast_channel_unlock(bridge_channel->chan); + return; + } + digit_timeout = gen_cfg->featuredigittimeout; + ast_channel_unlock(bridge_channel->chan); /* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */ ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY); @@ -1923,7 +1936,7 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel) int res; /* If the above timed out simply exit */ - res = ast_waitfordigit(bridge_channel->chan, 3000); + res = ast_waitfordigit(bridge_channel->chan, digit_timeout); if (!res) { ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n", bridge_channel, ast_channel_name(bridge_channel->chan)); diff --git a/main/features.c b/main/features.c index 2aa49ff353c4036890425f670d72bc43a6223c6c..e0c6548a96ed4c0e597f21e2d5e04a472dc0ab0b 100644 --- a/main/features.c +++ b/main/features.c @@ -74,6 +74,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #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 @@ -299,58 +303,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <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> @@ -395,12 +347,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #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 @@ -410,24 +356,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /* 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 */ @@ -436,8 +364,6 @@ typedef enum { 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. */ @@ -606,15 +532,11 @@ static struct ao2_container *parkinglots; 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. @@ -631,14 +553,6 @@ AST_MUTEX_DEFINE_STATIC(features_reload_lock); 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 */ @@ -848,11 +762,6 @@ int ast_parking_ext_valid(const char *exten_str, struct ast_channel *chan, const 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; @@ -888,75 +797,19 @@ static void set_c_e_p(struct ast_channel *chan, const char *context, const char 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; @@ -978,7 +831,9 @@ static int set_chan_app_data(struct ast_channel *chan, const char *src_app_data) ast_channel_datastore_add(chan, datastore); return 0; } +#endif +#if 0 /*! * \brief bridge the call * \param data thread bridge. @@ -1021,7 +876,9 @@ static void *bridge_call_thread(void *data) return NULL; } +#endif +#if 0 /*! * \brief create thread for the bridging call * \param tobj @@ -1041,6 +898,7 @@ static void bridge_call_thread_launch(struct ast_bridge_thread_obj *tobj) ast_free(tobj); } } +#endif /*! * \brief Announce call parking by ADSI @@ -1456,8 +1314,6 @@ static struct parkeduser *park_space_reserve(struct ast_channel *park_me, struct 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) { @@ -1491,7 +1347,10 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, st } 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; @@ -1878,13 +1737,16 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int 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. @@ -1952,7 +1814,9 @@ static int xfer_park_call_helper(struct ast_channel *park_me, struct ast_channel 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 @@ -1970,7 +1834,9 @@ static void set_peers(struct ast_channel **caller, struct ast_channel **callee, *caller = chan; } } +#endif +#if 0 /*! * \brief support routing for one touch call parking * \param chan channel parking call @@ -2019,6 +1885,7 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, set_peers(&parker, &parkee, peer, chan, sense); return masq_park_call(parkee, parker, &args) ? AST_FEATURE_RETURN_SUCCESS : -1; } +#endif /*! * \internal @@ -2051,6 +1918,7 @@ static int play_message_on_chan(struct ast_channel *play_to, struct ast_channel return 0; } +#if 0 /*! * \internal * \brief Play file to specified channels. @@ -2081,7 +1949,9 @@ static int play_message_to_chans(struct ast_channel *left, struct ast_channel *r 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 @@ -2091,7 +1961,9 @@ static int play_message_in_bridged_call(struct ast_channel *caller_chan, struct 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 @@ -2194,7 +2066,9 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee 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; @@ -2294,13 +2168,17 @@ static int builtin_automixmonitor(struct ast_channel *chan, struct ast_channel * 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 @@ -2323,118 +2201,9 @@ static const char *real_ctx(struct ast_channel *transferer, struct ast_channel * } 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 @@ -2452,7 +2221,9 @@ static int check_compat(struct ast_channel *c, struct ast_channel *newchan) } return 0; } +#endif +#if 0 /*! * \internal * \brief Builtin attended transfer failed cleanup. @@ -2482,7 +2253,9 @@ static void atxfer_fail_cleanup(struct ast_channel *transferee, struct ast_chann } ast_party_connected_line_free(connected_line); } +#endif +#if 0 /*! * \brief Attended transfer * \param chan transfered user @@ -2521,6 +2294,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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); @@ -2540,8 +2314,16 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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; @@ -2612,7 +2394,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st /* 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)) { @@ -2629,12 +2411,12 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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; @@ -2644,7 +2426,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st } 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); @@ -2662,7 +2444,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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); @@ -2690,7 +2472,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st /* 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; @@ -2711,7 +2493,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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) { @@ -2743,16 +2525,16 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st } ++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; @@ -2764,7 +2546,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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)) { @@ -2925,3642 +2707,1615 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st 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) @@ -6583,7 +4338,7 @@ 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; @@ -6633,10 +4388,18 @@ static int add_to_bridge(struct ast_bridge *bridge, struct ast_channel *chan, 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) { @@ -6661,7 +4424,7 @@ static int add_to_bridge(struct ast_bridge *bridge, struct ast_channel *chan, } } - 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); @@ -6673,7 +4436,7 @@ static int add_to_bridge(struct ast_bridge *bridge, struct ast_channel *chan, 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; @@ -6822,7 +4585,6 @@ static int action_bridge(struct mansession *s, const struct message *m) } 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"), }; @@ -7057,8 +4819,19 @@ int ast_pickup_call(struct ast_channel *chan) { 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); @@ -7068,8 +4841,8 @@ int ast_pickup_call(struct ast_channel *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)); @@ -7079,9 +4852,9 @@ int ast_pickup_call(struct ast_channel *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, ""); } } @@ -7993,189 +5766,13 @@ exit_features_test: } #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 (!parkinglot) { - parkinglot = parkinglot_addref(default_parkinglot); - } - } else { - /* Just to balance the unref below */ - parkinglot_addref(parkinglot); - } - - parkingtime = parkinglot->cfg.parkingtime; - - parkinglot_unref(parkinglot); - - return parkingtime; -} - -static int feature_read(struct ast_channel *chan, const char *cmd, char *data, - char *buf, size_t len) -{ - int res = 0; - - if (!strcasecmp(data, "parkingtime")) { - snprintf(buf, len, "%u", get_parkingtime(chan, NULL) / 1000); - } else if (!strcasecmp(data, "inherit")) { - struct ast_datastore *ds; - unsigned int inherit; - - ast_channel_lock(chan); - ds = get_feature_chan_ds(chan); - inherit = ds ? ds->inheritance : 0; - ast_channel_unlock(chan); - - snprintf(buf, len, "%s", inherit ? "yes" : "no"); - } else { - ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data); - res = -1; - } - - return res; -} - -static int feature_write(struct ast_channel *chan, const char *cmd, char *data, - const char *value) -{ - int res = 0; - struct feature_datastore *feature_ds; - - ast_channel_lock(chan); - - if (!(feature_ds = get_feature_ds(chan))) { - res = -1; - goto return_cleanup; - } - - if (!strcasecmp(data, "parkingtime")) { - feature_ds->parkingtime_is_set = 1; - if (sscanf(value, "%30u", &feature_ds->parkingtime) == 1) { - feature_ds->parkingtime *= 1000; /* stored in ms */ - } else { - ast_log(LOG_WARNING, "'%s' is not a valid parkingtime\n", value); - feature_ds->parkingtime_is_set = 0; - res = -1; - } - } else if (!strcasecmp(data, "inherit")) { - struct ast_datastore *ds; - if ((ds = get_feature_chan_ds(chan))) { - ds->inheritance = ast_true(value) ? DATASTORE_INHERIT_FOREVER : 0; - } - } else { - ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data); - res = -1; - } - -return_cleanup: - ast_channel_unlock(chan); - - return res; -} - -static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data, - char *buf, size_t len) -{ - int res; - - ast_rdlock_call_features(); - - if ((res = builtin_feature_get_exten(chan, data, buf, len))) { - ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data); - } - - ast_unlock_call_features(); - - return res; -} - -static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data, - const char *value) -{ - struct feature_datastore *feature_ds; - struct feature_exten *fe; - struct ast_call_feature *feat; - - ast_rdlock_call_features(); - feat = ast_find_call_feature(data); - ast_unlock_call_features(); - if (!feat) { - ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data); - return -1; - } - - ast_channel_lock(chan); - - if (!(feature_ds = get_feature_ds(chan))) { - ast_channel_unlock(chan); - return -1; - } - - if (!(fe = ao2_find(feature_ds->feature_map, data, OBJ_KEY))) { - if (!(fe = ao2_alloc(sizeof(*fe), NULL))) { - ast_channel_unlock(chan); - return -1; - } - ast_copy_string(fe->sname, data, sizeof(fe->sname)); - ao2_link(feature_ds->feature_map, fe); - } - - ast_channel_unlock(chan); - - ao2_lock(fe); - ast_copy_string(fe->exten, value, sizeof(fe->exten)); - ao2_unlock(fe); - ao2_ref(fe, -1); - fe = NULL; - - return 0; -} - -static struct ast_custom_function feature_function = { - .name = "FEATURE", - .read = feature_read, - .write = feature_write -}; - -static struct ast_custom_function featuremap_function = { - .name = "FEATUREMAP", - .read = featuremap_read, - .write = featuremap_write -}; - /*! \internal \brief Clean up resources on Asterisk shutdown */ static void features_shutdown(void) { + ast_features_config_shutdown(); + ast_cli_unregister_multiple(cli_features, ARRAY_LEN(cli_features)); ast_devstate_prov_del("Park"); - ast_custom_function_unregister(&featuremap_function); - ast_custom_function_unregister(&feature_function); ast_manager_unregister("Bridge"); ast_manager_unregister("Park"); @@ -8197,28 +5794,30 @@ int ast_features_init(void) return -1; } - res = load_config(0); + res = ast_features_config_init(); if (res) { return res; } ast_cli_register_multiple(cli_features, ARRAY_LEN(cli_features)); if (ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL)) { + ast_features_config_shutdown(); + ast_cli_unregister_multiple(cli_features, ARRAY_LEN(cli_features)); return -1; } - ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL); - if (!res) { - ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park); - ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge); - } - res |= __ast_custom_function_register(&feature_function, NULL); - res |= __ast_custom_function_register(&featuremap_function, NULL); + res |= ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL); + res |= ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park); + res |= ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge); res |= ast_devstate_prov_add("Park", metermaidstate); #if defined(TEST_FRAMEWORK) res |= AST_TEST_REGISTER(features_test); #endif /* defined(TEST_FRAMEWORK) */ - ast_register_atexit(features_shutdown); + if (res) { + features_shutdown(); + } else { + ast_register_atexit(features_shutdown); + } return res; } diff --git a/main/features_config.c b/main/features_config.c new file mode 100644 index 0000000000000000000000000000000000000000..f8bdb1c3c90a71ec74d5349eeac4275f59fd750a --- /dev/null +++ b/main/features_config.c @@ -0,0 +1,1534 @@ +/* +* Asterisk -- An open source telephony toolkit. +* +* Copyright (C) 2013, Digium, Inc. +* +* Mark Michelson <mmichelson@digium.com> +* +* See http://www.asterisk.org for more information about +* the Asterisk project. Please do not directly contact +* any of the maintainers of this project for assistance; +* the project provides a web site, mailing lists and IRC +* channels for your use. +* +* This program is free software, distributed under the terms of +* the GNU General Public License Version 2. See the LICENSE file +* at the top of the source tree. +*/ + +#include "asterisk.h" + +#include "asterisk/features_config.h" +#include "asterisk/config_options.h" +#include "asterisk/datastore.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/cli.h" + +/* BUGBUG XML Documentation is still needed for configuration options */ +/*** DOCUMENTATION + <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="inherit"><para>Inherit feature settings made in FEATURE or FEATUREMAP to child channels.</para></enum> + <enum name="featuredigittimeout"><para>Milliseconds allowed between digits when entering a feature code.</para></enum> + <enum name="transferdigittimeout"><para>Milliseconds allowed between digits when dialing a transfer destination</para></enum> + <enum name="atxfernoanswertimeout"><para>Milliseconds to wait for transfer destination to answer</para></enum> + <enum name="atxferdropcall"><para>Hang up the call entirely if the attended transfer fails</para></enum> + <enum name="atxferloopdelay"><para>Milliseconds to wait between attempts to re-dial transfer destination</para></enum> + <enum name="atxfercallbackretries"><para>Number of times to re-attempt dialing a transfer destination</para></enum> + <enum name="xfersound"><para>Sound to play to a transferee when a transfer completes</para></enum> + <enum name="xferfailsound"><para>Sound to play to a transferee when a transfer fails</para></enum> + <enum name="atxferabort"><para>Digits to dial to abort an attended transfer attempt</para></enum> + <enum name="atxfercomplete"><para>Digits to dial to complete an attended transfer</para></enum> + <enum name="atxferthreeway"><para>Digits to dial to change an attended transfer into a three-way call</para></enum> + <enum name="pickupexten"><para>Digits used for picking up ringing calls</para></enum> + <enum name="pickupsound"><para>Sound to play to picker when a call is picked up</para></enum> + <enum name="pickupfailsound"><para>Sound to play to picker when a call cannot be picked up</para></enum> + <enum name="courtesytone"><para>Sound to play when automon or automixmon is activated</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> + ***/ +/*! Default general options */ +#define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000 + +/*! Default xfer options */ +#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 +#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 +#define DEFAULT_ATXFER_DROP_CALL 0 +#define DEFAULT_ATXFER_LOOP_DELAY 10000 +#define DEFAULT_ATXFER_CALLBACK_RETRIES 2 +#define DEFAULT_XFERSOUND "beep" +#define DEFAULT_XFERFAILSOUND "beeperr" +#define DEFAULT_ATXFER_ABORT "*1" +#define DEFAULT_ATXFER_COMPLETE "*2" +#define DEFAULT_ATXFER_THREEWAY "*3" + +/*! Default pickup options */ +#define DEFAULT_PICKUPEXTEN "*8" +#define DEFAULT_PICKUPSOUND "" +#define DEFAULT_PICKUPFAILSOUND "" + +/*! Default featuremap options */ +#define DEFAULT_FEATUREMAP_BLINDXFER "#" +#define DEFAULT_FEATUREMAP_DISCONNECT "*" +#define DEFAULT_FEATUREMAP_AUTOMON "" +#define DEFAULT_FEATUREMAP_ATXFER "" +#define DEFAULT_FEATUREMAP_PARKCALL "" +#define DEFAULT_FEATUREMAP_AUTOMIXMON "" + +/*! + * \brief Configuration from the "general" section of features.conf + */ +struct features_global_config { + struct ast_features_general_config *general; + struct ast_features_xfer_config *xfer; + struct ast_features_pickup_config *pickup; +}; + +static void ast_applicationmap_item_destructor(void *obj) +{ + struct ast_applicationmap_item *item = obj; + + ast_string_field_free_memory(item); +} + +static int applicationmap_sort(const void *obj, const void *arg, int flags) +{ + const struct ast_applicationmap_item *item1 = obj; + const struct ast_applicationmap_item *item2; + const char *key2; + + switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key2 = arg; + return strcasecmp(item1->name, key2); + case OBJ_PARTIAL_KEY: + key2 = arg; + return strncasecmp(item1->name, key2, strlen(key2)); + default: + case OBJ_POINTER: + item2 = arg; + return strcasecmp(item1->name, item2->name); + } +} + +/*! + * \brief Entry in the container of featuregroups + */ +struct featuregroup_item { + AST_DECLARE_STRING_FIELDS( + /*! The name of the applicationmap item that we are referring to */ + AST_STRING_FIELD(appmap_item_name); + /*! Custom DTMF override to use instead of the default for the applicationmap item */ + AST_STRING_FIELD(dtmf_override); + ); + /*! The applicationmap item that is being referred to */ + struct ast_applicationmap_item *appmap_item; +}; + +static void featuregroup_item_destructor(void *obj) +{ + struct featuregroup_item *item = obj; + + ast_string_field_free_memory(item); + ao2_cleanup(item->appmap_item); +} + +static int group_item_sort(const void *obj, const void *arg, int flags) +{ + const struct featuregroup_item *item1 = obj; + const struct featuregroup_item *item2; + const char *key2; + + switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key2 = arg; + return strcasecmp(item1->appmap_item_name, key2); + case OBJ_PARTIAL_KEY: + key2 = arg; + return strncasecmp(item1->appmap_item_name, key2, strlen(key2)); + case OBJ_POINTER: + item2 = arg; + return strcasecmp(item1->appmap_item_name, item2->appmap_item_name); + default: + return CMP_STOP; + } +} + +/*! + * \brief Featuregroup representation + */ +struct featuregroup { + /*! The name of the featuregroup */ + const char *name; + /*! A container of featuregroup_items */ + struct ao2_container *items; +}; + +static int featuregroup_hash(const void *obj, int flags) +{ + const struct featuregroup *group; + const char *key; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key = obj; + return ast_str_case_hash(key); + case OBJ_PARTIAL_KEY: + ast_assert(0); + return 0; + case OBJ_POINTER: + default: + group = obj; + return ast_str_case_hash(group->name); + } +} + +static int featuregroup_cmp(void *obj, void *arg, int flags) +{ + struct featuregroup *group1 = obj; + struct featuregroup *group2; + const char *key2; + + switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key2 = arg; + return strcasecmp(group1->name, key2) ? 0 : CMP_MATCH; + case OBJ_PARTIAL_KEY: + key2 = arg; + return strncasecmp(group1->name, key2, strlen(key2)) ? 0 : CMP_MATCH; + case OBJ_POINTER: + group2 = arg; + return strcasecmp(group1->name, group2->name) ? 0 : CMP_MATCH; + default: + return CMP_STOP; + } +} + +static void *featuregroup_find(struct ao2_container *group_container, const char *category) +{ + return ao2_find(group_container, category, OBJ_KEY); +} + +static void featuregroup_destructor(void *obj) +{ + struct featuregroup *group = obj; + + ast_free((char *) group->name); + ao2_cleanup(group->items); +} + +static void *featuregroup_alloc(const char *cat) +{ + struct featuregroup *group; + + group = ao2_alloc(sizeof(*group), featuregroup_destructor); + if (!group) { + return NULL; + } + + group->name = ast_strdup(cat); + if (!group->name) { + ao2_cleanup(group); + return NULL; + } + + group->items = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, group_item_sort, NULL); + if (!group->items) { + ao2_cleanup(group); + return NULL; + } + + return group; +} + +struct features_config { + struct features_global_config *global; + struct ast_featuremap_config *featuremap; + struct ao2_container *applicationmap; + struct ao2_container *featuregroups; +}; + +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .name = "globals", + .category_match = ACO_WHITELIST, + .category = "^general$", + .item_offset = offsetof(struct features_config, global), +}; + +static struct aco_type featuremap_option = { + .type = ACO_GLOBAL, + .name = "featuremap", + .category_match = ACO_WHITELIST, + .category = "^featuremap$", + .item_offset = offsetof(struct features_config, featuremap), +}; + +static struct aco_type applicationmap_option = { + .type = ACO_GLOBAL, + .name = "applicationmap", + .category_match = ACO_WHITELIST, + .category = "^applicationmap$", + .item_offset = offsetof(struct features_config, applicationmap), +}; + +static struct aco_type featuregroup_option = { + .type = ACO_ITEM, + .name = "featuregroup", + .category_match = ACO_BLACKLIST, + .category = "^(general|featuremap|applicationmap|parkinglot_.*)$", + .item_offset = offsetof(struct features_config, featuregroups), + .item_alloc = featuregroup_alloc, + .item_find = featuregroup_find, +}; + +static struct aco_type *global_options[] = ACO_TYPES(&global_option); +static struct aco_type *featuremap_options[] = ACO_TYPES(&featuremap_option); +static struct aco_type *applicationmap_options[] = ACO_TYPES(&applicationmap_option); +static struct aco_type *featuregroup_options[] = ACO_TYPES(&featuregroup_option); + +static struct aco_file features_conf = { + .filename = "features.conf", + .types = ACO_TYPES(&global_option, &featuremap_option, &applicationmap_option, &featuregroup_option), +}; + +AO2_GLOBAL_OBJ_STATIC(globals); + +static void features_config_destructor(void *obj) +{ + struct features_config *cfg = obj; + + ao2_cleanup(cfg->global); + ao2_cleanup(cfg->featuremap); + ao2_cleanup(cfg->applicationmap); + ao2_cleanup(cfg->featuregroups); +} + +static void featuremap_config_destructor(void *obj) +{ + struct ast_featuremap_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void global_config_destructor(void *obj) +{ + struct features_global_config *cfg = obj; + + ao2_cleanup(cfg->general); + ao2_cleanup(cfg->xfer); + ao2_cleanup(cfg->pickup); +} + +static void general_destructor(void *obj) +{ + struct ast_features_general_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void xfer_destructor(void *obj) +{ + struct ast_features_xfer_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void pickup_destructor(void *obj) +{ + struct ast_features_pickup_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static struct features_global_config *global_config_alloc(void) +{ + RAII_VAR(struct features_global_config *, cfg, NULL, ao2_cleanup); + + cfg = ao2_alloc(sizeof(*cfg), global_config_destructor); + if (!cfg) { + return NULL; + } + + cfg->general = ao2_alloc(sizeof(*cfg->general), general_destructor); + if (!cfg->general || ast_string_field_init(cfg->general, 32)) { + return NULL; + } + + cfg->xfer = ao2_alloc(sizeof(*cfg->xfer), xfer_destructor); + if (!cfg->xfer || ast_string_field_init(cfg->xfer, 32)) { + return NULL; + } + + cfg->pickup = ao2_alloc(sizeof(*cfg->pickup), pickup_destructor); + if (!cfg->pickup || ast_string_field_init(cfg->pickup, 32)) { + return NULL; + } + + ao2_ref(cfg, +1); + return cfg; +} + +static struct ao2_container *applicationmap_alloc(void) +{ + return ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, applicationmap_sort, NULL); +} + +/*! + * \internal + * \brief Allocate the major configuration structure + * + * The parameter is used to determine if the applicationmap and featuregroup + * structures should be allocated. We only want to allocate these structures for + * the global features_config structure. For the datastores on channels, we don't + * need to allocate these structures because they are not used. + * + * \param allocate_applicationmap See previous explanation + * \retval NULL Failed to alloate configuration + * \retval non-NULL Allocated configuration + */ +static struct features_config *__features_config_alloc(int allocate_applicationmap) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + cfg = ao2_alloc(sizeof(*cfg), features_config_destructor); + if (!cfg) { + return NULL; + } + + cfg->global = global_config_alloc();; + if (!cfg->global) { + return NULL; + } + + cfg->featuremap = ao2_alloc(sizeof(*cfg->featuremap), featuremap_config_destructor); + if (!cfg->featuremap || ast_string_field_init(cfg->featuremap, 32)) { + return NULL; + } + + if (allocate_applicationmap) { + cfg->applicationmap = applicationmap_alloc(); + if (!cfg->applicationmap) { + return NULL; + } + + cfg->featuregroups = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 11, featuregroup_hash, + featuregroup_cmp); + if (!cfg->featuregroups) { + return NULL; + } + } + + ao2_ref(cfg, +1); + return cfg; + +} + +static void *features_config_alloc(void) +{ + return __features_config_alloc(1); +} + +static void general_copy(struct ast_features_general_config *dest, const struct ast_features_general_config *src) +{ + ast_string_fields_copy(dest, src); + dest->featuredigittimeout = src->featuredigittimeout; +} + +static void xfer_copy(struct ast_features_xfer_config *dest, const struct ast_features_xfer_config *src) +{ + ast_string_fields_copy(dest, src); + dest->transferdigittimeout = src->transferdigittimeout; + dest->atxfernoanswertimeout = src->atxfernoanswertimeout; + dest->atxferloopdelay = src->atxferloopdelay; + dest->atxfercallbackretries = src->atxfercallbackretries; + dest->atxferdropcall = src->atxferdropcall; +} + +static void pickup_copy(struct ast_features_pickup_config *dest, const struct ast_features_pickup_config *src) +{ + ast_string_fields_copy(dest, src); +} + +static void global_copy(struct features_global_config *dest, const struct features_global_config *src) +{ + general_copy(dest->general, src->general); + xfer_copy(dest->xfer, src->xfer); + pickup_copy(dest->pickup, src->pickup); +} + +static void featuremap_copy(struct ast_featuremap_config *dest, const struct ast_featuremap_config *src) +{ + ast_string_fields_copy(dest, src); +} + +static void features_copy(struct features_config *dest, const struct features_config *src) +{ + global_copy(dest->global, src->global); + featuremap_copy(dest->featuremap, src->featuremap); + + /* applicationmap and featuregroups are purposely not copied. A channel's applicationmap + * is produced on the fly when ast_get_chan_applicationmap() is called + */ +} + +static struct features_config *features_config_dup(const struct features_config *orig) +{ + struct features_config *dup; + + dup = __features_config_alloc(0); + if (!dup) { + return NULL; + } + + features_copy(dup, orig); + + return dup; +} + +static int general_set(struct ast_features_general_config *general, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "featuredigittimeout")) { + res = ast_parse_arg(value, PARSE_INT32, &general->featuredigittimeout); + } else if (!strcasecmp(name, "courtesytone")) { + ast_string_field_set(general, courtesytone, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int general_get(struct ast_features_general_config *general, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "featuredigittimeout")) { + snprintf(buf, len, "%u", general->featuredigittimeout); + } else if (!strcasecmp(field, "courtesytone")) { + ast_copy_string(buf, general->courtesytone, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int xfer_set(struct ast_features_xfer_config *xfer, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "transferdigittimeout")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->transferdigittimeout); + } else if (!strcasecmp(name, "atxfernoanswertimeout")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfernoanswertimeout); + } else if (!strcasecmp(name, "atxferloopdelay")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->atxferloopdelay); + } else if (!strcasecmp(name, "atxfercallbackretries")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfercallbackretries); + } else if (!strcasecmp(name, "atxferdropcall")) { + xfer->atxferdropcall = ast_true(value); + } else if (!strcasecmp(name, "xfersound")) { + ast_string_field_set(xfer, xfersound, value); + } else if (!strcasecmp(name, "xferfailsound")) { + ast_string_field_set(xfer, xferfailsound, value); + } else if (!strcasecmp(name, "atxferabort")) { + ast_string_field_set(xfer, atxferabort, value); + } else if (!strcasecmp(name, "atxfercomplete")) { + ast_string_field_set(xfer, atxfercomplete, value); + } else if (!strcasecmp(name, "atxferthreeway")) { + ast_string_field_set(xfer, atxferthreeway, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int xfer_get(struct ast_features_xfer_config *xfer, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "transferdigittimeout")) { + snprintf(buf, len, "%u", xfer->transferdigittimeout); + } else if (!strcasecmp(field, "atxfernoanswertimeout")) { + snprintf(buf, len, "%u", xfer->atxfernoanswertimeout); + } else if (!strcasecmp(field, "atxferloopdelay")) { + snprintf(buf, len, "%u", xfer->atxferloopdelay); + } else if (!strcasecmp(field, "atxfercallbackretries")) { + snprintf(buf, len, "%u", xfer->atxfercallbackretries); + } else if (!strcasecmp(field, "atxferdropcall")) { + snprintf(buf, len, "%u", xfer->atxferdropcall); + } else if (!strcasecmp(field, "xfersound")) { + ast_copy_string(buf, xfer->xfersound, len); + } else if (!strcasecmp(field, "xferfailsound")) { + ast_copy_string(buf, xfer->xferfailsound, len); + } else if (!strcasecmp(field, "atxferabort")) { + ast_copy_string(buf, xfer->atxferabort, len); + } else if (!strcasecmp(field, "atxfercomplete")) { + ast_copy_string(buf, xfer->atxfercomplete, len); + } else if (!strcasecmp(field, "atxferthreeway")) { + ast_copy_string(buf, xfer->atxferthreeway, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int pickup_set(struct ast_features_pickup_config *pickup, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "pickupsound")) { + ast_string_field_set(pickup, pickupsound, value); + } else if (!strcasecmp(name, "pickupfailsound")) { + ast_string_field_set(pickup, pickupfailsound, value); + } else if (!strcasecmp(name, "pickupexten")) { + ast_string_field_set(pickup, pickupexten, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int pickup_get(struct ast_features_pickup_config *pickup, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "pickupsound")) { + ast_copy_string(buf, pickup->pickupsound, len); + } else if (!strcasecmp(field, "pickupfailsound")) { + ast_copy_string(buf, pickup->pickupfailsound, len); + } else if (!strcasecmp(field, "pickupexten")) { + ast_copy_string(buf, pickup->pickupexten, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int featuremap_set(struct ast_featuremap_config *featuremap, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "blindxfer")) { + ast_string_field_set(featuremap, blindxfer, value); + } else if (!strcasecmp(name, "disconnect")) { + ast_string_field_set(featuremap, disconnect, value); + } else if (!strcasecmp(name, "automon")) { + ast_string_field_set(featuremap, automon, value); + } else if (!strcasecmp(name, "atxfer")) { + ast_string_field_set(featuremap, atxfer, value); + } else if (!strcasecmp(name, "automixmon")) { + ast_string_field_set(featuremap, automixmon, value); + } else if (!strcasecmp(name, "parkcall")) { + ast_string_field_set(featuremap, parkcall, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int featuremap_get(struct ast_featuremap_config *featuremap, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "blindxfer")) { + ast_copy_string(buf, featuremap->blindxfer, len); + } else if (!strcasecmp(field, "disconnect")) { + ast_copy_string(buf, featuremap->disconnect, len); + } else if (!strcasecmp(field, "automon")) { + ast_copy_string(buf, featuremap->automon, len); + } else if (!strcasecmp(field, "atxfer")) { + ast_copy_string(buf, featuremap->atxfer, len); + } else if (!strcasecmp(field, "automixmon")) { + ast_copy_string(buf, featuremap->automixmon, len); + } else if (!strcasecmp(field, "parkcall")) { + ast_copy_string(buf, featuremap->parkcall, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static void feature_ds_destroy(void *data) +{ + struct features_config *cfg = data; + ao2_cleanup(cfg); +} + +static void *feature_ds_duplicate(void *data) +{ + struct features_config *old_cfg = data; + + return features_config_dup(old_cfg); +} + +static const struct ast_datastore_info feature_ds_info = { + .type = "FEATURE", + .destroy = feature_ds_destroy, + .duplicate = feature_ds_duplicate, +}; + +/*! + * \internal + * \brief Find or create feature datastore on a channel + * + * \pre chan is locked + * + * \return the data on the FEATURE datastore, or NULL on error + */ +static struct features_config *get_feature_ds(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, orig, NULL, ao2_cleanup); + struct features_config *cfg; + struct ast_datastore *ds; + + if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) { + cfg = ds->data; + ao2_ref(cfg, +1); + return cfg; + } + + orig = ao2_global_obj_ref(globals); + if (!orig) { + return NULL; + } + + cfg = features_config_dup(orig); + if (!cfg) { + return NULL; + } + + if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) { + ao2_cleanup(cfg); + return NULL; + } + + /* Give the datastore a reference to the config */ + ao2_ref(cfg, +1); + ds->data = cfg; + + ast_channel_datastore_add(chan, ds); + + return cfg; +} + +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. */ + RAII_VAR(struct features_config *, cfg, get_feature_ds(chan), ao2_cleanup); + ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL); + } + + return ds; +} + +struct ast_features_general_config *ast_get_chan_features_general_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->global && cfg->global->general); + + ao2_ref(cfg->global->general, +1); + return cfg->global->general; +} + +struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->global && cfg->global->xfer); + + ao2_ref(cfg->global->xfer, +1); + return cfg->global->xfer; +} + +struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->global && cfg->global->pickup); + + ao2_ref(cfg->global->pickup, +1); + return cfg->global->pickup; +} + +struct ast_featuremap_config *ast_get_chan_featuremap_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->featuremap != NULL); + + ao2_ref(cfg->featuremap, +1); + return cfg->featuremap; +} + +int ast_get_builtin_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return -1; + } + + return featuremap_get(cfg->featuremap, feature, buf, len); +} + +int ast_get_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len) +{ + RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup); + RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup); + if (!ast_get_builtin_feature(chan, feature, buf, len)) { + return 0; + } + + /* Dang, must be in the application map */ + applicationmap = ast_get_chan_applicationmap(chan); + + if (!applicationmap) { + return -1; + } + + item = ao2_find(applicationmap, feature, OBJ_KEY); + if (!item) { + return -1; + } + + ast_copy_string(buf, item->dtmf, len); + return 0; +} + +static struct ast_applicationmap_item *applicationmap_item_alloc(const char *name, + const char *app, const char *app_data, const char *moh_class, const char *dtmf, + unsigned int activate_on_self) +{ + struct ast_applicationmap_item *item; + + item = ao2_alloc(sizeof(*item), ast_applicationmap_item_destructor); + + if (!item || ast_string_field_init(item, 64)) { + return NULL; + } + + ast_string_field_set(item, name, name); + ast_string_field_set(item, app, app); + ast_string_field_set(item, app_data, app_data); + ast_string_field_set(item, moh_class, moh_class); + ast_copy_string(item->dtmf, dtmf, sizeof(item->dtmf)); + item->activate_on_self = activate_on_self; + + return item; +} + +static int add_item(void *obj, void *arg, int flags) +{ + struct featuregroup_item *fg_item = obj; + struct ao2_container *applicationmap = arg; + RAII_VAR(struct ast_applicationmap_item *, appmap_item, NULL, ao2_cleanup); + + /* If there's no DTMF override, then we can just link + * the applicationmap item directly. Otherwise, we need + * to create a copy with the DTMF override in place and + * link that instead + */ + if (ast_strlen_zero(fg_item->dtmf_override)) { + ao2_ref(fg_item->appmap_item, +1); + appmap_item = fg_item->appmap_item; + } else { + appmap_item = applicationmap_item_alloc(fg_item->appmap_item_name, + fg_item->appmap_item->app, fg_item->appmap_item->app_data, + fg_item->appmap_item->moh_class, fg_item->dtmf_override, + fg_item->appmap_item->activate_on_self); + } + + if (!appmap_item) { + return 0; + } + + if (!ao2_link(applicationmap, appmap_item)) { + ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate\n", + fg_item->appmap_item_name); + } + return 0; +} + +struct ao2_container *ast_get_chan_applicationmap(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + struct ao2_container *applicationmap; + char *group_names; + char *name; + + if (!cfg) { + return NULL; + } + + if (!chan) { + if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) { + return NULL; + } + ao2_ref(cfg->applicationmap, +1); + return cfg->applicationmap; + } + + group_names = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"), "")); + if (ast_strlen_zero(group_names)) { + return NULL; + } + + applicationmap = applicationmap_alloc(); + if (!applicationmap) { + return NULL; + } + + while ((name = strsep(&group_names, "#"))) { + RAII_VAR(struct featuregroup *, group, ao2_find(cfg->featuregroups, name, OBJ_KEY), ao2_cleanup); + if (!group) { + RAII_VAR(struct ast_applicationmap_item *, item, ao2_find(cfg->applicationmap, name, OBJ_KEY), ao2_cleanup); + if (item && !ao2_link(applicationmap, item)) { + ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate.\n", item->name); + } + } else { + ao2_callback(group->items, 0, add_item, applicationmap); + } + } + + if (ao2_container_count(applicationmap) == 0) { + ao2_cleanup(applicationmap); + return NULL; + } + + return applicationmap; +} + +static int applicationmap_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup); + struct ao2_container *applicationmap = obj; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(dtmf); + AST_APP_ARG(activate_on); + AST_APP_ARG(app); + AST_APP_ARG(app_data); + AST_APP_ARG(moh_class); + ); + char *parse = ast_strdupa(var->value); + char *slash; + char *paren; + unsigned int activate_on_self; + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.dtmf) || + ast_strlen_zero(args.activate_on) || + ast_strlen_zero(args.app)) { + ast_log(LOG_WARNING, "Invalid applicationmap syntax for '%s'. Missing required argument\n", var->name); + return -1; + } + + /* features.conf used to have an "activated_by" portion + * in addition to activate_on. Get rid of whatever may be + * there + */ + slash = strchr(args.activate_on, '/'); + if (slash) { + *slash = '\0'; + } + + /* Two syntaxes allowed for applicationmap: + * Old: foo = *1,self,NoOp,Boo!,default + * New: foo = *1,self,NoOp(Boo!),default + * + * We need to handle both + */ + paren = strchr(args.app, '('); + if (paren) { + /* New syntax */ + char *close_paren; + + args.moh_class = args.app_data; + *paren++ = '\0'; + close_paren = strrchr(paren, ')'); + if (close_paren) { + *close_paren = '\0'; + } + args.app_data = paren; + + /* Re-check that the application is not empty */ + if (ast_strlen_zero(args.app)) { + ast_log(LOG_WARNING, "Applicationmap item '%s' does not contain an application name.\n", var->name); + return -1; + } + } else if (strchr(args.app_data, '"')) { + args.app_data = ast_strip_quoted(args.app_data, "\"", "\""); + } + + /* Allow caller and callee to be specified for backwards compatibility */ + if (!strcasecmp(args.activate_on, "self") || !strcasecmp(args.activate_on, "caller")) { + activate_on_self = 1; + } else if (!strcasecmp(args.activate_on, "peer") || !strcasecmp(args.activate_on, "callee")) { + activate_on_self = 0; + } else { + ast_log(LOG_WARNING, "Invalid 'activate_on' value %s for applicationmap item %s\n", + args.activate_on, var->name); + return -1; + } + + ast_debug(1, "Allocating applicationmap item: dtmf = %s, app = %s, app_data = %s, moh_class = %s\n", + args.dtmf, args.app, args.app_data, args.moh_class); + + item = applicationmap_item_alloc(var->name, args.app, args.app_data, + args.moh_class, args.dtmf, activate_on_self); + + if (!item) { + return -1; + } + + if (!ao2_link(applicationmap, item)) { + ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate\n", item->name); + } + + return 0; +} + +static int featuregroup_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + RAII_VAR(struct featuregroup_item *, item, NULL, ao2_cleanup); + struct featuregroup *group = obj; + + item = ao2_alloc(sizeof(*item), featuregroup_item_destructor); + if (!item || ast_string_field_init(item, 32)) { + return -1; + } + + ast_string_field_set(item, appmap_item_name, var->name); + ast_string_field_set(item, dtmf_override, var->value); + + if (!ao2_link(group->items, item)) { + ast_log(LOG_WARNING, "Unable to add featuregroup item %s. Possible duplicate\n", item->appmap_item_name); + } + + /* We wait to look up the application map item in the preapply callback */ + + return 0; +} + +static int general_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct features_global_config *global = obj; + struct ast_features_general_config *general = global->general; + + return general_set(general, var->name, var->value); +} + +static int xfer_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct features_global_config *global = obj; + struct ast_features_xfer_config *xfer = global->xfer; + + return xfer_set(xfer, var->name, var->value); +} + +static int pickup_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct features_global_config *global = obj; + struct ast_features_pickup_config *pickup = global->pickup; + + return pickup_set(pickup, var->name, var->value); +} + +static int featuremap_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_featuremap_config *featuremap = obj; + + return featuremap_set(featuremap, var->name, var->value); +} + +static int check_featuregroup_item(void *obj, void *arg, void *data, int flags) +{ + struct ast_applicationmap_item *appmap_item; + struct featuregroup_item *fg_item = obj; + int *err = arg; + struct ao2_container *applicationmap = data; + + appmap_item = ao2_find(applicationmap, fg_item->appmap_item_name, OBJ_KEY); + if (!appmap_item) { + *err = 1; + return CMP_STOP; + } + + fg_item->appmap_item = appmap_item; + + return 0; +} + +static int check_featuregroup(void *obj, void *arg, void *data, int flags) +{ + struct featuregroup *group = obj; + int *err = arg; + + ao2_callback_data(group->items, 0, check_featuregroup_item, arg, data); + + if (*err) { + ast_log(LOG_WARNING, "Featuregroup %s refers to non-existent applicationmap item\n", + group->name); + } + + return *err ? CMP_STOP : 0; +} + +static int features_pre_apply_config(void); + +CONFIG_INFO_CORE("features", cfg_info, globals, features_config_alloc, + .files = ACO_FILES(&features_conf), + .pre_apply_config = features_pre_apply_config, +); + +static int features_pre_apply_config(void) +{ + struct features_config *cfg = aco_pending_config(&cfg_info); + int err = 0; + + /* Now that the entire config has been processed, we can check that the featuregroup + * items refer to actual applicationmap items. + */ + + ao2_callback_data(cfg->featuregroups, 0, check_featuregroup, &err, cfg->applicationmap); + + return err; +} + +static int feature_read(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + int res; + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + SCOPED_CHANNELLOCK(lock, chan); + + if (!strcasecmp(data, "inherit")) { + struct ast_datastore *ds = get_feature_chan_ds(chan); + unsigned int inherit = ds ? ds->inheritance : 0; + + snprintf(buf, len, "%s", inherit ? "yes" : "no"); + return 0; + } + + cfg = get_feature_ds(chan); + if (!cfg) { + return -1; + } + + res = general_get(cfg->global->general, data, buf, len) && + xfer_get(cfg->global->xfer, data, buf, len) && + pickup_get(cfg->global->pickup, data, buf, len); + + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data); + } + + return res; +} + +static int feature_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + int res; + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + SCOPED_CHANNELLOCK(lock, chan); + + if (!strcasecmp(data, "inherit")) { + struct ast_datastore *ds = get_feature_chan_ds(chan); + if (ds) { + ds->inheritance = ast_true(value) ? DATASTORE_INHERIT_FOREVER : 0; + } + return 0; + } + + if (!(cfg = get_feature_ds(chan))) { + return -1; + } + + res = general_set(cfg->global->general, data, value) && + xfer_set(cfg->global->xfer, data, value) && + pickup_set(cfg->global->pickup, data, value); + + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data); + } + + return res; +} + +static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + int res; + SCOPED_CHANNELLOCK(lock, chan); + + res = ast_get_builtin_feature(chan, data, buf, len); + + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data); + } + + return res; +} + +static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + int res; + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + SCOPED_CHANNELLOCK(lock, chan); + + if (!(cfg = get_feature_ds(chan))) { + return -1; + } + + res = featuremap_set(cfg->featuremap, data, value); + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data); + return -1; + } + + return 0; +} + +static struct ast_custom_function feature_function = { + .name = "FEATURE", + .read = feature_read, + .write = feature_write +}; + +static struct ast_custom_function featuremap_function = { + .name = "FEATUREMAP", + .read = featuremap_read, + .write = featuremap_write +}; + +static int load_config(int reload) +{ + if (!reload && aco_info_init(&cfg_info)) { + ast_log(LOG_ERROR, "Unable to initialize configuration info for features\n"); + return -1; + } + + aco_option_register_custom(&cfg_info, "featuredigittimeout", ACO_EXACT, global_options, + __stringify(DEFAULT_FEATURE_DIGIT_TIMEOUT), general_handler, 0); + aco_option_register_custom(&cfg_info, "courtesytone", ACO_EXACT, global_options, + __stringify(DEFAULT_COURTESY_TONE), general_handler, 0); + + aco_option_register_custom(&cfg_info, "transferdigittimeout", ACO_EXACT, global_options, + __stringify(DEFAULT_TRANSFER_DIGIT_TIMEOUT), xfer_handler, 0) + aco_option_register_custom(&cfg_info, "atxfernoanswertimeout", ACO_EXACT, global_options, + __stringify(DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferdropcall", ACO_EXACT, global_options, + __stringify(DEFAULT_ATXFER_DROP_CALL), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferloopdelay", ACO_EXACT, global_options, + __stringify(DEFAULT_ATXFER_LOOP_DELAY), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxfercallbackretries", ACO_EXACT, global_options, + __stringify(DEFAULT_ATXFER_CALLBACK_RETRIES), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "xfersound", ACO_EXACT, global_options, + DEFAULT_XFERSOUND, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "xferfailsound", ACO_EXACT, global_options, + DEFAULT_XFERFAILSOUND, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferabort", ACO_EXACT, global_options, + DEFAULT_ATXFER_ABORT, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxfercomplete", ACO_EXACT, global_options, + DEFAULT_ATXFER_COMPLETE, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferthreeway", ACO_EXACT, global_options, + DEFAULT_ATXFER_THREEWAY, xfer_handler, 0); + + aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options, + DEFAULT_PICKUPEXTEN, pickup_handler, 0); + aco_option_register_custom(&cfg_info, "pickupsound", ACO_EXACT, global_options, + DEFAULT_PICKUPSOUND, pickup_handler, 0); + aco_option_register_custom(&cfg_info, "pickupfailsound", ACO_EXACT, global_options, + DEFAULT_PICKUPFAILSOUND, pickup_handler, 0); + + aco_option_register_custom(&cfg_info, "blindxfer", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_BLINDXFER, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "disconnect", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_DISCONNECT, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "automon", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_AUTOMON, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "atxfer", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_ATXFER, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "parkcall", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_PARKCALL, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "automixmon", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_AUTOMIXMON, featuremap_handler, 0); + + aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, applicationmap_options, + "", applicationmap_handler, 0); + + aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, featuregroup_options, + "", featuregroup_handler, 0); + + if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { + ast_log(LOG_ERROR, "Failed to process features.conf configuration!\n"); + if (!reload) { + aco_info_destroy(&cfg_info); + ao2_global_obj_release(globals); + } + return -1; + } + + return 0; +} + +static int print_featuregroup(void *obj, void *arg, int flags) +{ + struct featuregroup_item *item = obj; + struct ast_cli_args *a = arg; + + ast_cli(a->fd, "===> --> %s (%s)\n", item->appmap_item_name, + S_OR(item->dtmf_override, item->appmap_item->dtmf)); + + return 0; +} + +static int print_featuregroups(void *obj, void *arg, int flags) +{ + struct featuregroup *group = obj; + struct ast_cli_args *a = arg; + + ast_cli(a->fd, "===> Group: %s\n", group->name); + + ao2_callback(group->items, 0, print_featuregroup, a); + return 0; +} + +#define HFS_FORMAT "%-25s %-7s %-7s\n" + +static int print_applicationmap(void *obj, void *arg, int flags) +{ + struct ast_applicationmap_item *item = obj; + struct ast_cli_args *a = arg; + + ast_cli(a->fd, HFS_FORMAT, item->name, "no def", item->dtmf); + return 0; +} + +/*! + * \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. + */ +static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + switch (cmd) { + + case CLI_INIT: + e->command = "features show"; + e->usage = + "Usage: features show\n" + " Lists configured features\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + cfg = ao2_global_obj_ref(globals); + + ast_cli(a->fd, HFS_FORMAT, "Builtin Feature", "Default", "Current"); + ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------"); + + ast_cli(a->fd, HFS_FORMAT, "Pickup", DEFAULT_PICKUPEXTEN, cfg->global->pickup->pickupexten); + ast_cli(a->fd, HFS_FORMAT, "Blind Transfer", DEFAULT_FEATUREMAP_BLINDXFER, cfg->featuremap->blindxfer); + ast_cli(a->fd, HFS_FORMAT, "Attended Transfer", DEFAULT_FEATUREMAP_ATXFER, cfg->featuremap->atxfer); + ast_cli(a->fd, HFS_FORMAT, "One Touch Monitor", DEFAULT_FEATUREMAP_AUTOMON, cfg->featuremap->automon); + ast_cli(a->fd, HFS_FORMAT, "Disconnect Call", DEFAULT_FEATUREMAP_DISCONNECT, cfg->featuremap->disconnect); + ast_cli(a->fd, HFS_FORMAT, "Park Call", DEFAULT_FEATUREMAP_PARKCALL, cfg->featuremap->parkcall); + ast_cli(a->fd, HFS_FORMAT, "One Touch MixMonitor", DEFAULT_FEATUREMAP_AUTOMIXMON, cfg->featuremap->automixmon); + + ast_cli(a->fd, "\n"); + ast_cli(a->fd, HFS_FORMAT, "Dynamic Feature", "Default", "Current"); + ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------"); + if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) { + ast_cli(a->fd, "(none)\n"); + } else { + ao2_callback(cfg->applicationmap, 0, print_applicationmap, a); + } + + ast_cli(a->fd, "\nFeature Groups:\n"); + ast_cli(a->fd, "---------------\n"); + if (!cfg->featuregroups || ao2_container_count(cfg->featuregroups) == 0) { + ast_cli(a->fd, "(none)\n"); + } else { + ao2_callback(cfg->featuregroups, 0, print_featuregroups, a); + } + + ast_cli(a->fd, "\n"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_features_config[] = { + AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), +}; + +void ast_features_config_shutdown(void) +{ + ast_custom_function_unregister(&featuremap_function); + ast_custom_function_unregister(&feature_function); + ast_cli_unregister_multiple(cli_features_config, ARRAY_LEN(cli_features_config)); + aco_info_destroy(&cfg_info); + ao2_global_obj_release(globals); +} + +int ast_features_config_reload(void) +{ + return load_config(1); +} + +int ast_features_config_init(void) +{ + int res; + + res = load_config(0); + res |= __ast_custom_function_register(&feature_function, NULL); + res |= __ast_custom_function_register(&featuremap_function, NULL); + res |= ast_cli_register_multiple(cli_features_config, ARRAY_LEN(cli_features_config)); + + if (res) { + ast_features_config_shutdown(); + } + + return res; +} diff --git a/main/manager.c b/main/manager.c index 229b83b4e7c76ec4989e7fc558207f828bf817fa..da5a98490cad03f2e985c0b155753b9c6d738217 100644 --- a/main/manager.c +++ b/main/manager.c @@ -96,6 +96,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/test.h" #include "asterisk/json.h" #include "asterisk/bridging.h" +#include "asterisk/features_config.h" /*** DOCUMENTATION <manager name="Ping" language="en_US"> @@ -4048,8 +4049,8 @@ static int action_atxfer(struct mansession *s, const struct message *m) const char *exten = astman_get_header(m, "Exten"); const char *context = astman_get_header(m, "Context"); struct ast_channel *chan = NULL; - struct ast_call_feature *atxfer_feature = NULL; - char *feature_code = NULL; + char feature_code[AST_FEATURE_MAX_LEN]; + const char *digit; if (ast_strlen_zero(name)) { astman_send_error(s, m, "No channel specified"); @@ -4060,31 +4061,33 @@ static int action_atxfer(struct mansession *s, const struct message *m) return 0; } - ast_rdlock_call_features(); - atxfer_feature = ast_find_call_feature("atxfer"); - ast_unlock_call_features(); - if (!atxfer_feature) { - astman_send_error(s, m, "No attended transfer feature found"); + if (!(chan = ast_channel_get_by_name(name))) { + astman_send_error(s, m, "Channel specified does not exist"); return 0; } - if (!(chan = ast_channel_get_by_name(name))) { - astman_send_error(s, m, "Channel specified does not exist"); + ast_channel_lock(chan); + if (ast_get_builtin_feature(chan, "atxfer", feature_code, sizeof(feature_code)) || + ast_strlen_zero(feature_code)) { + ast_channel_unlock(chan); + astman_send_error(s, m, "No attended transfer feature code found"); + ast_channel_unref(chan); return 0; } + ast_channel_unlock(chan); if (!ast_strlen_zero(context)) { pbx_builtin_setvar_helper(chan, "TRANSFER_CONTEXT", context); } /* BUGBUG action_atxfer() is broken because the bridge DTMF hooks need both begin and end events to match correctly. */ - for (feature_code = atxfer_feature->exten; feature_code && *feature_code; ++feature_code) { - struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *feature_code }; + for (digit = feature_code; *digit; ++digit) { + struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit }; ast_queue_frame(chan, &f); } - for (feature_code = (char *)exten; feature_code && *feature_code; ++feature_code) { - struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *feature_code }; + for (digit = exten; *digit; ++digit) { + struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit }; ast_queue_frame(chan, &f); }