summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configs/features.conf.sample9
-rw-r--r--res/res_features.c302
2 files changed, 219 insertions, 92 deletions
diff --git a/configs/features.conf.sample b/configs/features.conf.sample
index a35453f8c..85a0d9d5f 100644
--- a/configs/features.conf.sample
+++ b/configs/features.conf.sample
@@ -33,7 +33,14 @@ context => parkedcalls ; Which context parked calls are in
;pickupexten = *8 ; Configure the pickup extension. (default is *8)
;featuredigittimeout = 500 ; Max time (ms) between digits for
; feature activation (default is 500 ms)
-;atxfernoanswertimeout = 15 ; Timeout for answer on attended transfer default is 15 seconds.
+;atxfernoanswertimeout = 15 ; Timeout for answer on attended transfer default is 15 seconds.
+;atxferdropcall = no ; If someone does an attended transfer, then hangs up before the transferred
+ ; caller is connected, then by default, the system will try to call back the
+ ; person that did the transfer. If this is set to "yes", the callback will
+ ; not be attempted and the transfer will just fail.
+;atxferloopdelay = 10 ; Number of seconds to sleep between retries (if atxferdropcall = no)
+;atxfercallbackretries = 2 ; Number of times to attempt to send the call back to the transferer.
+ ; By default, this is 2.
[featuremap]
;blindxfer => #1 ; Blind transfer (default is #)
diff --git a/res/res_features.c b/res/res_features.c
index 80cd787e1..25aa622bd 100644
--- a/res/res_features.c
+++ b/res/res_features.c
@@ -63,6 +63,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
#define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
#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 AST_MAX_WATCHERS 256
@@ -104,6 +107,9 @@ static int featuredigittimeout;
static int comebacktoorigin = 1;
static int atxfernoanswertimeout;
+static unsigned int atxferdropcall;
+static unsigned int atxferloopdelay;
+static unsigned int atxfercallbackretries;
static char *registrar = "res_features"; /*!< Registrar for operations */
@@ -219,7 +225,7 @@ static void check_goto_on_transfer(struct ast_channel *chan)
}
}
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name);
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate);
static void *ast_bridge_call_thread(void *data)
@@ -770,6 +776,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
struct ast_channel *transferee;
const char *transferer_real_context;
char xferto[256] = "";
+ char callbackto[256] = "";
int res;
int outstate=0;
struct ast_channel *newchan;
@@ -821,87 +828,181 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
l = strlen(xferto);
snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context); /* append context */
- newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats),
- xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name);
- ast_indicate(transferer, -1);
- if (!newchan) {
- finishup(transferee);
- /* any reason besides user requested cancel and busy triggers the failed sound */
- if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
+ newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+ xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+
+ if (!ast_check_hangup(transferer)) {
+ /* Transferer is up - old behaviour */
+ ast_indicate(transferer, -1);
+ if (!newchan) {
+ finishup(transferee);
+ /* any reason besides user requested cancel and busy triggers the failed sound */
+ if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
ast_stream_and_wait(transferer, xferfailsound, ""))
+ return -1;
+ if (ast_stream_and_wait(transferer, xfersound, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ if (check_compat(transferer, newchan))
return -1;
- return FEATURE_RETURN_SUCCESS;
- }
+ memset(&bconfig,0,sizeof(struct ast_bridge_config));
+ ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
+ ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
+ res = ast_bridge_call(transferer, newchan, &bconfig);
+ if (newchan->_softhangup || !transferer->_softhangup) {
+ ast_hangup(newchan);
+ if (ast_stream_and_wait(transferer, xfersound, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ finishup(transferee);
+ transferer->_softhangup = 0;
+ return FEATURE_RETURN_SUCCESS;
+ }
+ if (check_compat(transferee, newchan))
+ return -1;
+ ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+ if ((ast_autoservice_stop(transferee) < 0)
+ || (ast_waitfordigit(transferee, 100) < 0)
+ || (ast_waitfordigit(newchan, 100) < 0)
+ || ast_check_hangup(transferee)
+ || ast_check_hangup(newchan)) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ if (!xferchan) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ /* Make formats okay */
+ xferchan->readformat = transferee->readformat;
+ xferchan->writeformat = transferee->writeformat;
+ ast_channel_masquerade(xferchan, transferee);
+ ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+ xferchan->_state = AST_STATE_UP;
+ ast_clear_flag(xferchan, AST_FLAGS_ALL);
+ xferchan->_softhangup = 0;
+ if ((f = ast_read(xferchan)))
+ ast_frfree(f);
+ newchan->_state = AST_STATE_UP;
+ ast_clear_flag(newchan, AST_FLAGS_ALL);
+ newchan->_softhangup = 0;
+ if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+ ast_hangup(xferchan);
+ ast_hangup(newchan);
+ return -1;
+ }
+ tobj->chan = xferchan;
+ tobj->peer = newchan;
+ tobj->bconfig = *config;
- if (check_compat(transferer, newchan))
- return -1;
- memset(&bconfig,0,sizeof(struct ast_bridge_config));
- ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
- ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
- res = ast_bridge_call(transferer, newchan, &bconfig);
- if (newchan->_softhangup || !transferer->_softhangup) {
- ast_hangup(newchan);
- if (ast_stream_and_wait(transferer, xfersound, ""))
+ if (ast_stream_and_wait(newchan, xfersound, ""))
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
- finishup(transferee);
- transferer->_softhangup = 0;
- return FEATURE_RETURN_SUCCESS;
- }
-
- if (check_compat(transferee, newchan))
- return -1;
+ ast_bridge_call_thread_launch(tobj);
+ return -1; /* XXX meaning the channel is bridged ? */
+ } else if (!ast_check_hangup(transferee)) {
+ /* act as blind transfer */
+ if (ast_autoservice_stop(transferee) < 0) {
+ ast_hangup(newchan);
+ return -1;
+ }
- ast_indicate(transferee, AST_CONTROL_UNHOLD);
-
- if ((ast_autoservice_stop(transferee) < 0)
- || (ast_waitfordigit(transferee, 100) < 0)
- || (ast_waitfordigit(newchan, 100) < 0)
- || ast_check_hangup(transferee)
- || ast_check_hangup(newchan)) {
- ast_hangup(newchan);
- return -1;
- }
+ if (!newchan) {
+ unsigned int tries = 0;
- xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
- if (!xferchan) {
- ast_hangup(newchan);
- return -1;
- }
- /* Make formats okay */
- xferchan->readformat = transferee->readformat;
- xferchan->writeformat = transferee->writeformat;
- ast_channel_masquerade(xferchan, transferee);
- ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
- xferchan->_state = AST_STATE_UP;
- ast_clear_flag(xferchan, AST_FLAGS_ALL);
- xferchan->_softhangup = 0;
+ /* newchan wasn't created - we should callback to transferer */
+ if (!ast_exists_extension(transferer, transferer_real_context, transferer->cid.cid_num, 1, transferee->cid.cid_num)) {
+ ast_log(LOG_WARNING, "Extension %s does not exist in context %s - callback failed\n",transferer->cid.cid_num,transferer_real_context);
+ if (ast_stream_and_wait(transferee, "beeperr", ""))
+ return -1;
+ return FEATURE_RETURN_SUCCESS;
+ }
+ snprintf(callbackto, sizeof(callbackto), "%s@%s/n", transferer->cid.cid_num, transferer_real_context); /* append context */
+
+ newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+ callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+ while (!newchan && !atxferdropcall && tries < atxfercallbackretries) {
+ /* Trying to transfer again */
+ ast_autoservice_start(transferee);
+ ast_indicate(transferee, AST_CONTROL_HOLD);
+
+ newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+ xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+ if (ast_autoservice_stop(transferee) < 0) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ if (!newchan) {
+ /* Transfer failed, sleeping */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Sleeping for %d ms before callback.\n", atxferloopdelay);
+ ast_safe_sleep(transferee, atxferloopdelay);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Trying to callback...\n");
+ newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+ callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+ }
+ tries++;
+ }
+ }
+ if (!newchan)
+ return -1;
- if ((f = ast_read(xferchan)))
- ast_frfree(f);
+ /* newchan is up, we should prepare transferee and bridge them */
+ if (check_compat(transferee, newchan))
+ return -1;
+ ast_indicate(transferee, AST_CONTROL_UNHOLD);
- newchan->_state = AST_STATE_UP;
- ast_clear_flag(newchan, AST_FLAGS_ALL);
- newchan->_softhangup = 0;
+ if ((ast_waitfordigit(transferee, 100) < 0)
+ || (ast_waitfordigit(newchan, 100) < 0)
+ || ast_check_hangup(transferee)
+ || ast_check_hangup(newchan)) {
+ ast_hangup(newchan);
+ return -1;
+ }
- tobj = ast_calloc(1, sizeof(struct ast_bridge_thread_obj));
- if (!tobj) {
- ast_hangup(xferchan);
- ast_hangup(newchan);
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ if (!xferchan) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ /* Make formats okay */
+ xferchan->readformat = transferee->readformat;
+ xferchan->writeformat = transferee->writeformat;
+ ast_channel_masquerade(xferchan, transferee);
+ ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+ xferchan->_state = AST_STATE_UP;
+ ast_clear_flag(xferchan, AST_FLAGS_ALL);
+ xferchan->_softhangup = 0;
+ if ((f = ast_read(xferchan)))
+ ast_frfree(f);
+ newchan->_state = AST_STATE_UP;
+ ast_clear_flag(newchan, AST_FLAGS_ALL);
+ newchan->_softhangup = 0;
+ if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+ ast_hangup(xferchan);
+ ast_hangup(newchan);
+ return -1;
+ }
+ tobj->chan = xferchan;
+ tobj->peer = newchan;
+ tobj->bconfig = *config;
+
+ if (ast_stream_and_wait(newchan, xfersound, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ ast_bridge_call_thread_launch(tobj);
+ return -1; /* XXX meaning the channel is bridged ? */
+ } else {
+ /* Transferee hung up */
+ finishup(transferee);
return -1;
}
- tobj->chan = xferchan;
- tobj->peer = newchan;
- tobj->bconfig = *config;
-
- if (ast_stream_and_wait(newchan, xfersound, ""))
- ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
- ast_bridge_call_thread_launch(tobj);
- return -1; /* XXX meaning the channel is bridged ? */
}
-
/* add atxfer and automon as undefined so you can only use em if you configure them */
-#define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
+#define FEATURES_COUNT ARRAY_LEN(builtin_features)
struct ast_call_feature builtin_features[] =
{
@@ -1152,7 +1253,7 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
}
/*! \todo XXX Check - this is very similar to the code in channel.c */
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name)
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate)
{
int state = 0;
int cause = 0;
@@ -1194,7 +1295,7 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
x = 0;
started = ast_tvnow();
to = timeout;
- while (!ast_check_hangup(caller) && timeout && (chan->_state != AST_STATE_UP)) {
+ while (!((transferee && transferee->_softhangup) && (!igncallerstate && ast_check_hangup(caller))) && timeout && (chan->_state != AST_STATE_UP)) {
struct ast_frame *f = NULL;
monitor_chans[0] = caller;
@@ -1249,31 +1350,34 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
} else if (caller && (active_channel == caller)) {
f = ast_read(caller);
if (f == NULL) { /*doh! where'd he go?*/
- if (caller->_softhangup && !chan->_softhangup) {
- /* make this a blind transfer */
- ready = 1;
+ if (!igncallerstate) {
+ if (caller->_softhangup && !chan->_softhangup) {
+ /* make this a blind transfer */
+ ready = 1;
+ break;
+ }
+ state = AST_CONTROL_HANGUP;
+ res = 0;
break;
}
- state = AST_CONTROL_HANGUP;
- res = 0;
- break;
- }
+ } else {
- if (f->frametype == AST_FRAME_DTMF) {
- dialed_code[x++] = f->subclass;
- dialed_code[x] = '\0';
- if (strlen(dialed_code) == len) {
- x = 0;
- } else if (x && strncmp(dialed_code, disconnect_code, x)) {
- x = 0;
+ if (f->frametype == AST_FRAME_DTMF) {
+ dialed_code[x++] = f->subclass;
dialed_code[x] = '\0';
- }
- if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
- /* Caller Canceled the call */
- state = AST_CONTROL_UNHOLD;
- ast_frfree(f);
- f = NULL;
- break;
+ 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);
+ f = NULL;
+ break;
+ }
}
}
}
@@ -2345,6 +2449,9 @@ static int load_config(void)
transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+ atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+ atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
+ atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
cfg = ast_config_load("features.conf");
if (!cfg) {
@@ -2406,6 +2513,19 @@ static int load_config(void)
atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
} else
atxfernoanswertimeout = atxfernoanswertimeout * 1000;
+ } else if (!strcasecmp(var->name, "atxferloopdelay")) {
+ if ((sscanf(var->value, "%u", &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, "%u", &atxferloopdelay) != 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")) {