[svn-commits] trunk r31274 - /trunk/channels/chan_sip.c
svn-commits at lists.digium.com
svn-commits at lists.digium.com
Thu Jun 1 00:30:46 MST 2006
Author: oej
Date: Thu Jun 1 02:30:45 2006
New Revision: 31274
URL: http://svn.digium.com/view/asterisk?rev=31274&view=rev
Log:
Commit of the new SIP transfer support (oej/siptransfer branch)
- improved support of attended transfers (REFER with replaces)
- support of INVITE/replaces in the context of a transfer
- improved support of blind transfers (REFER)
Thanks to Voop, Nuvio and Foniris for sponsoring this work.
Modified:
trunk/channels/chan_sip.c
Modified: trunk/channels/chan_sip.c
URL: http://svn.digium.com/view/asterisk/trunk/channels/chan_sip.c?rev=31274&r1=31273&r2=31274&view=diff
==============================================================================
--- trunk/channels/chan_sip.c (original)
+++ trunk/channels/chan_sip.c Thu Jun 1 02:30:45 2006
@@ -638,7 +638,7 @@
#define SIP_USECLIENTCODE (1 << 12) /*!< Trust X-ClientCode info message */
#define SIP_OUTGOING (1 << 13) /*!< Is this an outgoing call? */
#define SIP_CAN_BYE (1 << 14) /*!< Can we send BYE on this dialog? */
-#define SIP_FREEBIT3 (1 << 15) /*!< Free for session-related use */
+#define SIP_DEFER_BYE_ON_TRANSFER (1 << 15) /*!< Do not hangup at first ast_hangup */
#define SIP_DTMF (3 << 16) /*!< DTMF Support: four settings, uses two bits */
#define SIP_DTMF_RFC2833 (0 << 16) /*!< DTMF Support: RTP DTMF - "rfc2833" */
#define SIP_DTMF_INBAND (1 << 16) /*!< DTMF Support: Inband audio, only for ULAW/ALAW - "inband" */
@@ -763,9 +763,6 @@
AST_STRING_FIELD(opaque); /*!< Opaque nonsense */
AST_STRING_FIELD(qop); /*!< Quality of Protection, since SIP wasn't complicated enough yet. */
AST_STRING_FIELD(domain); /*!< Authorization domain */
- AST_STRING_FIELD(refer_to); /*!< Place to store REFER-TO extension */
- AST_STRING_FIELD(referred_by); /*!< Place to store REFERRED-BY extension */
- AST_STRING_FIELD(refer_contact);/*!< Place to store Contact info from a REFER extension */
AST_STRING_FIELD(from); /*!< The From: header */
AST_STRING_FIELD(useragent); /*!< User agent in SIP request */
AST_STRING_FIELD(exten); /*!< Extension where to start */
@@ -824,7 +821,6 @@
struct sockaddr_in recv; /*!< Received as */
struct in_addr ourip; /*!< Our IP */
struct ast_channel *owner; /*!< Who owns us */
- struct sip_pvt *refer_call; /*!< Call we are referring */
struct sip_route *route; /*!< Head of linked list of routing steps (fm Record-Route) */
int route_persistant; /*!< Is this the "real" route? */
struct sip_auth *peerauth; /*!< Realm authentication */
@@ -1115,6 +1111,7 @@
static const char *hangup_cause2sip(int cause);
static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *sin, const int intended_method);
+int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno);
/*--- Codec handling / SDP */
static void try_suggested_sip_codec(struct sip_pvt *p);
@@ -1250,6 +1247,7 @@
static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, char *e);
static void handle_request_info(struct sip_pvt *p, struct sip_request *req);
static int handle_request_options(struct sip_pvt *p, struct sip_request *req);
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int debug, int ignore, int seqno, struct sockaddr_in *sin);
/*------Response handling functions */
static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno);
@@ -2880,10 +2878,29 @@
if (option_debug && sipdebug)
ast_log(LOG_DEBUG, "Hangup call %s, SIP callid %s)\n", ast->name, p->callid);
+ if (ast_test_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) {
+ if (option_debug >3)
+ ast_log(LOG_DEBUG, "SIP Transfer: Not hanging up right now... Rescheduling hangup.\n");
+ if (p->autokillid > -1)
+ sip_cancel_destroy(p);
+ sip_scheddestroy(p, 32000);
+ ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */
+ ast_clear_flag(&p->flags[0], SIP_NEEDDESTROY);
+ p->owner = NULL; /* Owner will be gone after we return, so take it away */
+ return 0;
+ }
+ if (option_debug) {
+ if (ast_test_flag(ast, AST_FLAG_ZOMBIE) && p->refer && option_debug)
+ ast_log(LOG_DEBUG, "SIP Transfer: Hanging up Zombie channel %s after transfer ... Call-ID: %s\n", ast->name, p->callid);
+ else
+ ast_log(LOG_DEBUG, "Hangup call %s, SIP callid %s)\n", ast->name, p->callid);
+ }
+
ast_mutex_lock(&p->lock);
if (option_debug && sipdebug)
ast_log(LOG_DEBUG, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username);
update_call_counter(p, DEC_CALL_LIMIT);
+
/* Determine how to disconnect */
if (p->owner != ast) {
ast_log(LOG_WARNING, "Huh? We aren't the owner? Can't hangup call.\n");
@@ -3079,6 +3096,19 @@
p->owner = newchan;
ret = 0;
}
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "SIP Fixup: New owner for dialogue %s: %s (Old parent: %s)\n", p->callid, p->owner->name, oldchan->name);
+ if (p->refer) {
+ if (option_debug > 2) {
+ if (oldchan->tech_pvt) {
+ struct sip_pvt *old = oldchan->tech_pvt;
+ ast_log(LOG_DEBUG, "Releasing connection between %s and pvt %s\n", oldchan->name, old->callid);
+ } else
+ ast_log(LOG_DEBUG, "Hmmm. No sip_pvt to release for %s\n", oldchan->name);
+ }
+ oldchan->tech_pvt = NULL; /* Release connection between old channel and it's pvt so we can hang up in peace */
+ }
+
ast_mutex_unlock(&p->lock);
return ret;
}
@@ -4307,6 +4337,10 @@
snprintf(req->header[req->headers], maxlen, "%s: %s\r\n", var, value);
req->len += strlen(req->header[req->headers]);
req->headers++;
+ if (req->headers < SIP_MAX_HEADERS)
+ req->headers++;
+ else
+ ast_log(LOG_WARNING, "Out of SIP header space... Will generate broken SIP message\n");
return 0;
}
@@ -6506,6 +6540,9 @@
p->addr = pvt->recv;
}
+ /* Save SIP options profile */
+ p->sipoptions = pvt->sipoptions;
+
if (c) /* Overwrite the default username from config at registration */
ast_copy_string(p->username, c, sizeof(p->username));
else
@@ -7223,167 +7260,227 @@
return sip_pvt_ptr;
}
-/*! \brief Call transfer support (the REFER method) */
-static int get_refer_info(struct sip_pvt *sip_pvt, struct sip_request *outgoing_req)
-{
-
- const char *p_refer_to = NULL;
- const char *h_contact = NULL;
+/*! \brief Call transfer support (the REFER method)
+ * Extracts Refer headers into pvt dialog structure */
+static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoing_req)
+{
+
const char *p_referred_by = NULL;
- char *h_refer_to = NULL, *h_referred_by = NULL;
- char *replace_callid = "", *refer_to = NULL, *referred_by = NULL, *ptr = NULL;
+ char *h_refer_to = NULL;
+ char *h_referred_by = NULL;
+ char *refer_to;
+ const char *p_refer_to;
+ char *referred_by_uri = NULL;
+ char *ptr;
struct sip_request *req = NULL;
- struct sip_pvt *sip_pvt_ptr = NULL;
- struct ast_channel *chan = NULL, *peer = NULL;
+ const char *transfer_context = NULL;
+ struct sip_refer *referdata;
+
req = outgoing_req;
+ referdata = transferer->refer;
if (!req)
- req = &sip_pvt->initreq;
+ req = &transferer->initreq;
if (!(p_refer_to = get_header(req, "Refer-To"))) {
- ast_log(LOG_WARNING, "No Refer-To Header That's illegal\n");
- return -1;
+ ast_log(LOG_WARNING, "Refer-To Header missing. Skipping transfer.\n");
+ return -2; /* Syntax error */
}
h_refer_to = ast_strdupa(p_refer_to);
refer_to = get_in_brackets(h_refer_to);
-
- if (!(p_referred_by = get_header(req, "Referred-By"))) {
- ast_log(LOG_DEBUG, "No Referrred-By Header That's not illegal\n");
- return -1;
- }
- h_referred_by = ast_strdupa(p_referred_by);
if (pedanticsipchecking)
- ast_uri_decode(h_referred_by);
- referred_by = get_in_brackets(h_referred_by);
- h_contact = get_header(req, "Contact");
-
- if (strncmp(refer_to, "sip:", 4)) {
- ast_log(LOG_WARNING, "Refer-to: Huh? Not a SIP header (%s)?\n", refer_to);
- return -1;
- }
-
- if (strncmp(referred_by, "sip:", 4)) {
- ast_log(LOG_WARNING, "Referred-by: Huh? Not a SIP header (%s) Ignoring?\n", referred_by);
- referred_by = NULL;
- }
-
- if (refer_to)
- refer_to += 4;
-
- if (referred_by)
- referred_by += 4;
-
- if ((ptr = strchr(refer_to, '?'))) {
- /* Search for arguments */
+ ast_uri_decode(refer_to);
+
+ if (strncasecmp(refer_to, "sip:", 4)) {
+ ast_log(LOG_WARNING, "Can't transfer to non-sip: URI. (Refer-to: %s)?\n", refer_to);
+ return -3;
+ }
+ refer_to += 4; /* Skip sip: */
+
+ /* Get referred by header if it exists */
+ if ((p_referred_by = get_header(req, "Referred-By"))) {
+ char *lessthan;
+ h_referred_by = ast_strdupa(p_referred_by);
+ if (pedanticsipchecking)
+ ast_uri_decode(h_referred_by);
+
+ /* Store referrer's caller ID name */
+ ast_copy_string(referdata->referred_by_name, h_referred_by, sizeof(referdata->referred_by_name));
+ if ((lessthan = strchr(referdata->referred_by_name, '<'))) {
+ *(lessthan - 1) = '\0'; /* Space */
+ }
+
+ referred_by_uri = get_in_brackets(h_referred_by);
+ if(strncasecmp(referred_by_uri, "sip:", 4)) {
+ ast_log(LOG_WARNING, "Huh? Not a sip: header (Referred-by: %s). Skipping.\n", referred_by_uri);
+ referred_by_uri = (char *) NULL;
+ } else {
+ referred_by_uri += 4; /* Skip sip: */
+ }
+ }
+
+ /* Check for arguments in the refer_to header */
+ if ((ptr = strchr(refer_to, '?'))) { /* Search for arguments */
*ptr = '\0';
ptr++;
if (!strncasecmp(ptr, "REPLACES=", 9)) {
- char *p;
- replace_callid = ast_strdupa(ptr + 9);
- /* someday soon to support invite/replaces properly!
- replaces_header = ast_strdupa(replace_callid);
- -anthm
- */
- ast_uri_decode(replace_callid);
- if ((ptr = strchr(replace_callid, '%')))
+ char *to, *from;
+
+ /* This is an attended transfer */
+ referdata->attendedtransfer = 1;
+ strncpy(referdata->replaces_callid, ptr+9, sizeof(referdata->replaces_callid));
+ ast_uri_decode(referdata->replaces_callid);
+ if ((ptr = strchr(referdata->replaces_callid, ';'))) /* Remove options */ {
*ptr = '\0';
- if ((ptr = strchr(replace_callid, ';')))
- *ptr = '\0';
- /* Skip leading whitespace XXX memmove behaviour with overlaps ? */
- p = ast_skip_blanks(replace_callid);
- if (p != replace_callid)
- memmove(replace_callid, p, strlen(p));
+ }
+ ptr++;
+
+ /* Find the different tags before we destroy the string */
+ to = strcasestr(ptr, "to-tag=");
+ from = strcasestr(ptr, "from-tag=");
+
+ /* Grab the to header */
+ if (to) {
+ ptr = to + 7;
+ if ((to = strchr(ptr, '&')))
+ *to = '\0';
+ if ((to = strchr(ptr, ';')))
+ *to = '\0';
+ ast_copy_string(referdata->replaces_callid_totag, ptr, sizeof(referdata->replaces_callid_totag));
+ }
+
+ if (from) {
+ ptr = from + 9;
+ if ((to = strchr(ptr, '&')))
+ *to = '\0';
+ if ((to = strchr(ptr, ';')))
+ *to = '\0';
+ ast_copy_string(referdata->replaces_callid_fromtag, ptr, sizeof(referdata->replaces_callid_fromtag));
+ }
+
+ if (option_debug > 1) {
+ if (!pedanticsipchecking)
+ ast_log(LOG_DEBUG,"Attended transfer: Will use Replace-Call-ID : %s (No check of from/to tags)\n", referdata->replaces_callid );
+ else
+ ast_log(LOG_DEBUG,"Attended transfer: Will use Replace-Call-ID : %s F-tag: %s T-tag: %s\n", referdata->replaces_callid, referdata->replaces_callid_fromtag ? referdata->replaces_callid_fromtag : "<none>", referdata->replaces_callid_totag ? referdata->replaces_callid_totag : "<none>" );
+ }
}
}
- /* strip domain and everything after ';' (domain should be saved in SIPDOMAIN) */
- ptr = refer_to;
- strsep(&ptr, "@;"); /* trim anything after @ or ; */
- ptr = referred_by;
- strsep(&ptr, "@;"); /* trim anything after @ or ;, NULL is ok */
-
- if (sip_debug_test_pvt(sip_pvt)) {
- ast_verbose("Transfer to %s in %s\n", refer_to, sip_pvt->context);
- if (referred_by)
- ast_verbose("Transfer from %s in %s\n", referred_by, sip_pvt->context);
- }
- if (!ast_strlen_zero(replace_callid)) {
- /* This is a supervised transfer */
- ast_log(LOG_DEBUG,"Assigning Replace-Call-ID Info %s to REPLACE_CALL_ID\n", replace_callid);
-
- ast_string_field_free(sip_pvt, refer_to);
- ast_string_field_free(sip_pvt, referred_by);
- ast_string_field_free(sip_pvt, refer_contact);
- sip_pvt->refer_call = NULL;
- if ((sip_pvt_ptr = get_sip_pvt_byid_locked(replace_callid, NULL, NULL))) {
- sip_pvt->refer_call = sip_pvt_ptr;
- if (sip_pvt->refer_call == sip_pvt) {
- ast_log(LOG_NOTICE, "Supervised transfer attempted to transfer into same call id (%s == %s)!\n", replace_callid, sip_pvt->callid);
- sip_pvt->refer_call = NULL;
- } else
- return 0;
- } else {
- ast_log(LOG_NOTICE, "Supervised transfer requested, but unable to find callid '%s'. Both legs must reside on Asterisk box to transfer at this time.\n", replace_callid);
- /* XXX The refer_to could contain a call on an entirely different machine, requiring an
- INVITE with a replaces header -anthm XXX */
- /* The only way to find out is to use the dialplan - oej */
- }
- } else if (ast_exists_extension(NULL, sip_pvt->context, refer_to, 1, NULL) || !strcmp(refer_to, ast_parking_ext())) {
- /* This is an unsupervised transfer (blind transfer) */
-
- ast_log(LOG_DEBUG,"Unsupervised transfer to (Refer-To): %s\n", refer_to);
- if (referred_by)
- ast_log(LOG_DEBUG,"Transferred by (Referred-by: ) %s \n", referred_by);
- ast_log(LOG_DEBUG,"Transfer Contact Info %s (REFER_CONTACT)\n", h_contact);
- ast_string_field_set(sip_pvt, refer_to, refer_to);
- if (referred_by)
- ast_string_field_set(sip_pvt, referred_by, referred_by);
- if (h_contact)
- ast_string_field_set(sip_pvt, refer_contact, h_contact);
- sip_pvt->refer_call = NULL;
- chan = sip_pvt->owner;
- if (chan && (peer = ast_bridged_channel(chan)) != NULL) {
- pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
- pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
- }
+ if ((ptr = strchr(refer_to, '@'))) { /* Separate domain */
+ char *urioption;
+ *ptr = '\0';
+ ptr++;
+ if ((urioption = strchr(ptr, ';'))) {
+ *urioption = '\0';
+ urioption++;
+ }
+ /* Save the domain for the dial plan */
+ strncpy(referdata->refer_to_domain, ptr, sizeof(referdata->refer_to_domain));
+ if (urioption)
+ strncpy(referdata->refer_to_urioption, urioption, sizeof(referdata->refer_to_urioption));
+ }
+
+ if ((ptr = strchr(refer_to, ';'))) /* Remove options */
+ *ptr = '\0';
+ ast_copy_string(referdata->refer_to, refer_to, sizeof(referdata->refer_to));
+
+ if ((ptr = strchr(referred_by_uri, ';'))) /* Remove options */
+ *ptr = '\0';
+ ast_copy_string(referdata->referred_by, referred_by_uri, sizeof(referdata->referred_by));
+
+ /* Determine transfer context */
+ if (transferer->owner) /* Mimic behaviour in res_features.c */
+ transfer_context = pbx_builtin_getvar_helper(transferer->owner, "TRANSFER_CONTEXT");
+
+ /* By default, use the context in the channel sending the REFER */
+ if (ast_strlen_zero(transfer_context)) {
+ if (!ast_strlen_zero(transferer->owner->macrocontext))
+ transfer_context=transferer->owner->macrocontext;
+ else if (ast_strlen_zero(transferer->context))
+ transfer_context = default_context;
+ else
+ transfer_context = transferer->context;
+ }
+
+ strncpy(referdata->refer_to_context, transfer_context, sizeof(referdata->refer_to_context));
+
+ /* Either an existing extension or the parking extension */
+ if (ast_exists_extension(NULL, transfer_context, refer_to, 1, NULL) ) {
+ if (sip_debug_test_pvt(transferer)) {
+ ast_verbose("SIP transfer to extension %s@%s by %s\n", refer_to, transfer_context, referred_by_uri);
+ }
+ /* We are ready to transfer to the extension */
return 0;
- } else if (ast_canmatch_extension(NULL, sip_pvt->context, refer_to, 1, NULL)) {
- return 1;
- }
-
+ }
+ if (sip_debug_test_pvt(transferer))
+ ast_verbose("Failed SIP Transfer to non-existing extension %s in context %s\n n", refer_to, transfer_context);
+
+ /* Failure, we can't find this extension */
return -1;
}
-/*! \brief Call transfer support (old way, deprecated in the IETF) */
+
+/*! \brief Call transfer support (old way, depreciated by the IETF)--*/
static int get_also_info(struct sip_pvt *p, struct sip_request *oreq)
{
- char tmp[256], *c, *a;
+ char tmp[256] = "", *c, *a;
struct sip_request *req = oreq;
+ struct sip_refer *referdata;
+ const char *transfer_context = NULL;
- if (!req)
+ referdata = p->refer;
+
+ if (!oreq)
req = &p->initreq;
+ else
+ req = oreq;
ast_copy_string(tmp, get_header(req, "Also"), sizeof(tmp));
c = get_in_brackets(tmp);
+
+ if (pedanticsipchecking)
+ ast_uri_decode(c);
+
if (strncmp(c, "sip:", 4)) {
- ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
+ ast_log(LOG_WARNING, "Huh? Not a SIP header in Also: transfer (%s)?\n", c);
return -1;
}
c += 4;
- a = c;
- strsep(&a, "@;"); /* trim anything after @ or ; */
+ if ((a = strchr(c, '@'))) { /* Separate Domain */
+ *a = '\0';
+ a++;
+ ast_copy_string(referdata->refer_to_domain, a, sizeof(referdata->refer_to_domain));
+ }
+
+ if ((a = strchr(c, ';'))) /* Remove arguments */
+ *a = '\0';
if (sip_debug_test_pvt(p))
ast_verbose("Looking for %s in %s\n", c, p->context);
- if (ast_exists_extension(NULL, p->context, c, 1, NULL)) {
- /* This is an unsupervised transfer */
+ if (p->owner) /* Mimic behaviour in res_features.c */
+ transfer_context = pbx_builtin_getvar_helper(p->owner, "TRANSFER_CONTEXT");
+
+ /* By default, use the context in the channel sending the REFER */
+ if (!transfer_context || ast_strlen_zero(transfer_context)) {
+ if (!ast_strlen_zero(p->owner->macrocontext))
+ transfer_context = p->owner->macrocontext;
+ else if (ast_strlen_zero(p->context))
+ transfer_context = default_context;
+ else
+ transfer_context = p->context;
+ }
+ if (ast_exists_extension(NULL, transfer_context, c, 1, NULL)) {
+ /* This is a blind transfer */
if (option_debug)
- ast_log(LOG_DEBUG,"Assigning Extension %s to REFER-TO\n", c);
- ast_string_field_set(p, refer_to, c);
- ast_string_field_free(p, referred_by);
- ast_string_field_free(p, refer_contact);
- p->refer_call = NULL;
+ ast_log(LOG_DEBUG,"SIP Bye-also transfer to Extension %s@%s \n", c, transfer_context);
+ ast_copy_string(referdata->refer_to, c, sizeof(referdata->refer_to));
+ ast_copy_string(referdata->referred_by, "", sizeof(referdata->referred_by));
+ ast_copy_string(referdata->refer_contact, "", sizeof(referdata->refer_contact));
+ referdata->refer_call = NULL;
+ /* Set new context */
+ ast_string_field_set(p, context, transfer_context);
return 0;
} else if (ast_canmatch_extension(NULL, p->context, c, 1, NULL)) {
return 1;
@@ -7391,7 +7488,6 @@
return -1;
}
-
/*! \brief check Via: header for hostname, port and rport request/answer */
static int check_via(struct sip_pvt *p, struct sip_request *req)
{
@@ -9173,6 +9269,7 @@
ast_cli(fd, " * SIP Call\n");
ast_cli(fd, " Direction: %s\n", ast_test_flag(&cur->flags[0], SIP_OUTGOING)?"Outgoing":"Incoming");
ast_cli(fd, " Call-ID: %s\n", cur->callid);
+ ast_cli(fd, " Owner channel ID: %s\n", cur->owner ? cur->owner->name : "<none>");
ast_cli(fd, " Our Codec Capability: %d\n", cur->capability);
ast_cli(fd, " Non-Codec Capability (DTMF): %d\n", cur->noncodeccapability);
ast_cli(fd, " Their Codec Capability: %d\n", cur->peercapability);
@@ -10297,11 +10394,11 @@
case 202: /* Transfer accepted */
/* We need to do something here */
/* The transferee is now sending INVITE to target */
+ p->refer->status = REFER_ACCEPTED;
/* Now wait for next message */
if (option_debug > 2)
ast_log(LOG_DEBUG, "Got 202 accepted on transfer\n");
/* We should hang along, waiting for NOTIFY's here */
- /* (done in a separate function) */
break;
case 401: /* Not www-authorized on SIP method */
@@ -10317,6 +10414,7 @@
}
if ((p->authtries > 1) || do_proxy_auth(p, req, auth, auth2, SIP_REFER, 0)) {
ast_log(LOG_NOTICE, "Failed to authenticate on REFER to '%s'\n", get_header(&p->initreq, "From"));
+ p->refer->status = REFER_NOAUTH;
ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
}
break;
@@ -10326,11 +10424,13 @@
case 501: /* Method not implemented */
/* Return to the current call onhold */
/* Status flag needed to be reset */
- ast_log(LOG_NOTICE, "SIP transfer failed, call miserably fails. \n");
+ ast_log(LOG_NOTICE, "SIP transfer to %s failed, call miserably fails. \n", p->refer->refer_to);
ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ p->refer->status = REFER_FAILED;
break;
case 603: /* Transfer declined */
- ast_log(LOG_NOTICE, "SIP transfer declined, call fails. \n" );
+ ast_log(LOG_NOTICE, "SIP transfer to %s declined, call miserably fails. \n", p->refer->refer_to);
+ p->refer->status = REFER_FAILED;
ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
break;
}
@@ -10571,12 +10671,13 @@
} else if (sipmethod == SIP_NOTIFY) {
/* They got the notify, this is the end */
if (p->owner) {
- ast_log(LOG_WARNING, "Notify answer on an owned channel?\n");
- ast_queue_hangup(p->owner);
+ if (!p->refer) {
+ ast_log(LOG_WARNING, "Notify answer on an owned channel?\n");
+ ast_queue_hangup(p->owner);
+ }
} else {
- if (p->subscribed == NONE) {
+ if (p->subscribed == NONE)
ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
- }
}
} else if (sipmethod == SIP_REGISTER)
res = handle_response_register(p, resp, rest, req, ignore, seqno);
@@ -10790,6 +10891,16 @@
} else if (sipmethod == SIP_CANCEL) {
ast_log(LOG_DEBUG, "Got 200 OK on CANCEL\n");
/* Wait for 487, then destroy */
+ } else if (sipmethod == SIP_NOTIFY) {
+ /* They got the notify, this is the end */
+ if (p->owner) {
+ ast_log(LOG_WARNING, "Notify answer on an owned channel?\n");
+ ast_queue_hangup(p->owner);
+ } else {
+ if (!p->subscribed)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ /* Wait for 487, then destroy */
} else if (sipmethod == SIP_MESSAGE)
/* We successfully transmitted a message */
ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
@@ -10819,6 +10930,8 @@
if (sipmethod == SIP_INVITE) {
/* Re-invite failed */
handle_response_invite(p, resp, rest, req, seqno);
+ } else if (sipdebug) {
+ ast_log (LOG_DEBUG, "Remote host can't match request %s to call '%s'. Giving up\n", sip_methods[sipmethod].text, p->callid);
}
break;
case 501: /* Not Implemented */
@@ -10900,13 +11013,13 @@
transmit_response(transferer->tech_pvt, "202 Accepted", &req);
if (!res) {
/* Transfer succeeded */
- transmit_notify_with_sipfrag(transferer->tech_pvt, d->seqno, "200 OK", 1);
+ transmit_notify_with_sipfrag(transferer->tech_pvt, d->seqno, "200 OK", TRUE);
transferer->hangupcause = AST_CAUSE_NORMAL_CLEARING;
ast_hangup(transferer); /* This will cause a BYE */
if (option_debug)
ast_log(LOG_DEBUG, "SIP Call parked on extension '%d'\n", ext);
} else {
- transmit_notify_with_sipfrag(transferer->tech_pvt, d->seqno, "503 Service Unavailable", 1);
+ transmit_notify_with_sipfrag(transferer->tech_pvt, d->seqno, "503 Service Unavailable", TRUE);
if (option_debug)
ast_log(LOG_DEBUG, "SIP Call parked failed \n");
/* Do not hangup call */
@@ -10994,72 +11107,89 @@
}
}
-/*! \brief Attempt transfer of SIP call */
-static int attempt_transfer(struct sip_pvt *p1, struct sip_pvt *p2)
+/*! \brief Attempt transfer of SIP call
+ This fix for attended transfers on a local PBX */
+static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target)
{
int res = 0;
- struct ast_channel
- *chana = NULL,
- *chanb = NULL,
- *bridgea = NULL,
- *bridgeb = NULL,
- *peera = NULL,
+ struct ast_channel *peera = NULL,
*peerb = NULL,
*peerc = NULL,
*peerd = NULL;
- if (!p1->owner || !p2->owner) {
- ast_log(LOG_WARNING, "Transfer attempted without dual ownership?\n");
- return -1;
- }
- chana = p1->owner;
- chanb = p2->owner;
- bridgea = ast_bridged_channel(chana);
- bridgeb = ast_bridged_channel(chanb);
+
+ /* We will try to connect the transferee with the target and hangup
+ all channels to the transferer */
+ if (option_debug > 3) {
+ ast_log(LOG_DEBUG, "Sip transfer:--------------------\n");
+ if (transferer->chan1)
+ ast_log(LOG_DEBUG, "-- Transferer to PBX channel: %s State %s\n", transferer->chan1->name, ast_state2str(transferer->chan1->_state));
+ else
+ ast_log(LOG_DEBUG, "-- No transferer first channel - odd??? \n");
+ if (target->chan1)
+ ast_log(LOG_DEBUG, "-- Transferer to PBX second channel (target): %s State %s\n", target->chan1->name, ast_state2str(target->chan1->_state));
+ else
+ ast_log(LOG_DEBUG, "-- No target first channel ---\n");
+ if (transferer->chan2)
+ ast_log(LOG_DEBUG, "-- Bridged call to transferee: %s State %s\n", transferer->chan2->name, ast_state2str(transferer->chan2->_state));
+ else
+ ast_log(LOG_DEBUG, "-- No bridged call to transferee\n");
+ if (target->chan2)
+ ast_log(LOG_DEBUG, "-- Bridged call to transfer target: %s State %s\n", target->chan2 ? target->chan2->name : "<none>", target->chan2 ? ast_state2str(target->chan2->_state) : "(none)");
+ else
+ ast_log(LOG_DEBUG, "-- No target second channel ---\n");
+ ast_log(LOG_DEBUG, "-- END Sip transfer:--------------------\n");
+ }
+ if (transferer->chan2) { /* We have a bridge on the transferer's channel */
+ peera = transferer->chan1; /* Transferer - PBX -> transferee channel * the one we hangup */
+ peerb = target->chan1; /* Transferer - PBX -> target channel - This will get lost in masq */
+ peerc = transferer->chan2; /* Asterisk to Transferee */
+ peerd = target->chan2; /* Asterisk to Target */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "SIP transfer: Four channels to handle\n");
+ } else if (target->chan2) { /* Transferer has no bridge (IVR), but transferee */
+ peera = target->chan1; /* Transferer to PBX -> target channel */
+ peerb = transferer->chan1; /* Transferer to IVR*/
+ peerc = target->chan2; /* Asterisk to Target */
+ peerd = transferer->chan2; /* Nothing */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "SIP transfer: Three channels to handle\n");
+ }
+
+ if (peera && peerb && peerc && (peerb != peerc)) {
+ ast_quiet_chan(peera); /* Stop generators */
+ ast_quiet_chan(peerb);
+ ast_quiet_chan(peerc);
+ if (peerd)
+ ast_quiet_chan(peerd);
+
+ /* Fix CDRs so they're attached to the remaining channel */
+ if (peera->cdr && peerb->cdr)
+ peerb->cdr = ast_cdr_append(peerb->cdr, peera->cdr);
+ else if (peera->cdr)
+ peerb->cdr = peera->cdr;
+ peera->cdr = NULL;
+
+ if (peerb->cdr && peerc->cdr)
+ peerb->cdr = ast_cdr_append(peerb->cdr, peerc->cdr);
+ else if (peerc->cdr)
+ peerb->cdr = peerc->cdr;
+ peerc->cdr = NULL;
- if (bridgea) {
- peera = chana;
- peerb = chanb;
- peerc = bridgea;
- peerd = bridgeb;
- } else if (bridgeb) {
- peera = chanb;
- peerb = chana;
- peerc = bridgeb;
- peerd = bridgea;
- }
-
- if (peera && peerb && peerc && (peerb != peerc)) {
- ast_quiet_chan(peera);
- ast_quiet_chan(peerb);
- ast_quiet_chan(peerc);
- ast_quiet_chan(peerd);
-
- if (peera->cdr && peerb->cdr) {
- peerb->cdr = ast_cdr_append(peerb->cdr, peera->cdr);
- } else if (peera->cdr) {
- peerb->cdr = peera->cdr;
- }
- peera->cdr = NULL;
-
- if (peerb->cdr && peerc->cdr) {
- peerb->cdr = ast_cdr_append(peerb->cdr, peerc->cdr);
- } else if (peerc->cdr) {
- peerb->cdr = peerc->cdr;
- }
- peerc->cdr = NULL;
-
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "SIP transfer: trying to masquerade %s into %s\n", peerc->name, peerb->name);
if (ast_channel_masquerade(peerb, peerc)) {
ast_log(LOG_WARNING, "Failed to masquerade %s into %s\n", peerb->name, peerc->name);
res = -1;
- }
+ } else
+ ast_log(LOG_DEBUG, "SIP transfer: Succeeded to masquerade channels.\n");
return res;
} else {
- ast_log(LOG_NOTICE, "Transfer attempted with no appropriate bridged calls to transfer\n");
- if (chana)
- ast_softhangup_nolock(chana, AST_SOFTHANGUP_DEV);
- if (chanb)
- ast_softhangup_nolock(chanb, AST_SOFTHANGUP_DEV);
+ ast_log(LOG_NOTICE, "SIP Transfer attempted with no appropriate bridged calls to transfer\n");
+ if (transferer->chan1)
+ ast_softhangup_nolock(transferer->chan1, AST_SOFTHANGUP_DEV);
+ if (target->chan1)
+ ast_softhangup_nolock(target->chan1, AST_SOFTHANGUP_DEV);
return -1;
}
return 0;
@@ -11248,14 +11378,177 @@
return res;
}
-/*! \brief Handle incoming INVITE request */
+/*! \brief Handle the transfer part of INVITE with a replaces: header,
+ meaning a target pickup or an attended transfer */
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int debug, int ignore, int seqno, struct sockaddr_in *sin)
+{
+ struct ast_frame *f;
+ int earlyreplace = 0;
+ int oneleggedreplace = 0; /* Call with no bridge, propably IVR or voice message */
+ struct ast_channel *c = p->owner; /* Our incoming call */
+ struct ast_channel *replacecall = p->refer->refer_call->owner; /* The channel we're about to take over */
+ struct ast_channel *targetcall; /* The bridge to the take-over target */
+
+ /* Check if we're in ring state */
+ if (replacecall->_state == AST_STATE_RING)
+ earlyreplace = 1;
+
+ /* Check if we have a bridge */
+ if (!(targetcall = ast_bridged_channel(replacecall))) {
+ /* We have no bridge */
+ if (!earlyreplace) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, " Attended transfer attempted to replace call with no bridge (maybe ringing). Channel %s!\n", replacecall->name);
+ oneleggedreplace = 1;
+ }
+ }
+ if (option_debug > 3 && targetcall && targetcall->_state == AST_STATE_RINGING)
+ ast_log(LOG_DEBUG, "SIP transfer: Target channel is in ringing state\n");
+
+ if (option_debug > 3) {
+ if (targetcall)
+ ast_log(LOG_DEBUG, "SIP transfer: Invite Replace incoming channel should bridge to channel %s while hanging up channel %s\n", targetcall->name, replacecall->name);
+ else
+ ast_log(LOG_DEBUG, "SIP transfer: Invite Replace incoming channel should replace and hang up channel %s (one call leg)\n", replacecall->name);
+ }
+
+ if (ignore) {
+ ast_log(LOG_NOTICE, "Ignoring this INVITE with replaces in a stupid way.\n");
+ /* We should answer something here. If we are here, the
+ call we are replacing exists, so an accepted
+ can't harm */
+ transmit_response_with_sdp(p, "200 OK", req, 1);
+ /* Do something more clever here */
+ ast_channel_unlock(c);
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+ return 1;
+ }
+ if (!c) {
+ /* What to do if no channel ??? */
+ ast_log(LOG_ERROR, "Unable to create new channel. Invite/replace failed.\n");
+ transmit_response_with_sdp(p, "503 Service Unavailable", req, 1);
+ append_history(p, "Xfer", "INVITE/Replace Failed. No new channel.");
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+ return 1;
+ }
+ append_history(p, "Xfer", "INVITE/Replace received");
+ /* We have three channels to play with
+ channel c: New incoming call
+ targetcall: Call from PBX to target
+ p->refer->refer_call: SIP pvt dialog from transferer to pbx.
+ replacecall: The owner of the previous
+ We need to masq C into refer_call to connect to
+ targetcall;
+ If we are talking to internal audio stream, target call is null.
+ */
+
+ /* Fake call progress */
+ transmit_response(p, "100 Trying", req);
+ ast_setstate(c, AST_STATE_RING);
+
+ /* Masquerade the new call into the referred call to connect to target call
+ Targetcall is not touched by the masq */
+
+ /* Answer the incoming call and set channel to UP state */
+ transmit_response_with_sdp(p, "200 OK", req, 1);
+ ast_setstate(c, AST_STATE_UP);
+
+ /* Stop music on hold and other generators */
+ ast_quiet_chan(replacecall);
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Invite/Replaces: preparing to masquerade %s into %s\n", c->name, replacecall->name);
+ /* Unlock clone, but not original (replacecall) */
+ ast_channel_unlock(c);
+
+ /* Unlock PVT */
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+
+ /* Make sure that the masq does not free our PVT for the old call */
+ ast_set_flag(&p->refer->refer_call->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+
+ /* Prepare the masquerade - if this does not happen, we will be gone */
+ if(ast_channel_masquerade(replacecall, c))
+ ast_log(LOG_ERROR, "Failed to masquerade C into Replacecall\n");
+ else if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Invite/Replaces: Going to masquerade %s into %s\n", c->name, replacecall->name);
+
+ /* The masquerade will happen as soon as someone reads a frame from the channel */
+
+ /* C should now be in place of replacecall */
+ /* ast_read needs to lock channel */
+ ast_channel_unlock(c);
+
+ if (earlyreplace || oneleggedreplace ) {
+ /* Force the masq to happen */
+ if ((f = ast_read(replacecall))) { /* Force the masq to happen */
+ ast_frfree(f);
+ f = NULL;
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Invite/Replace: Could successfully read frame from RING channel!\n");
+ } else {
+ ast_log(LOG_WARNING, "Invite/Replace: Could not read frame from RING channel \n");
+ }
+ c->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_channel_unlock(replacecall);
+ } else { /* Bridged call, UP channel */
+ if ((f = ast_read(replacecall))) { /* Force the masq to happen */
+ /* Masq ok */
+ ast_frfree(f);
+ f = NULL;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Invite/Replace: Could successfully read frame from channel! Masq done.\n");
+ } else {
+ ast_log(LOG_WARNING, "Invite/Replace: Could not read frame from channel. Transfer failed\n");
+ }
+ ast_channel_unlock(replacecall);
+ }
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+
+ ast_setstate(c, AST_STATE_DOWN);
+ if (option_debug > 3) {
+ struct ast_channel *test;
+ ast_log(LOG_DEBUG, "After transfer:----------------------------\n");
+ ast_log(LOG_DEBUG, " -- C: %s State %s\n", c->name, ast_state2str(c->_state));
+ if (replacecall)
+ ast_log(LOG_DEBUG, " -- replacecall: %s State %s\n", replacecall->name, ast_state2str(replacecall->_state));
+ if (p->owner) {
+ ast_log(LOG_DEBUG, " -- P->owner: %s State %s\n", p->owner->name, ast_state2str(p->owner->_state));
+ test = ast_bridged_channel(p->owner);
+ if (test)
+ ast_log(LOG_DEBUG, " -- Call bridged to P->owner: %s State %s\n", test->name, ast_state2str(test->_state));
+ else
+ ast_log(LOG_DEBUG, " -- No call bridged to C->owner \n");
+ } else
+ ast_log(LOG_DEBUG, " -- No channel yet \n");
+ ast_log(LOG_DEBUG, "End After transfer:----------------------------\n");
+ }
+
+ ast_channel_unlock(p->owner); /* Unlock new owner */
+ ast_mutex_unlock(&p->lock); /* Unlock SIP structure */
+
+ /* The call should be down with no ast_channel, so hang it up */
+ c->tech_pvt = NULL;
+ ast_hangup(c);
+ return 0;
+}
+
+
+/*! \brief Handle incoming INVITE request
+\note If the INVITE has a Replaces header, it is part of an
+ * attended transfer. If so, we do not go through the dial
+ * plan but tries to find the active call and masquerade
+ * into it
+ */
static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin, int *recount, char *e)
{
int res = 1;
- struct ast_channel *c=NULL; /* New channel */
int gotdest;
+ const char *p_replaces;
+ char *replace_id = NULL;
const char *required;
unsigned int required_profile = 0;
+ struct ast_channel *c = NULL; /* New channel */
/* Find out what they support */
if (!p->sipoptions) {
@@ -11266,43 +11559,163 @@
/* Find out what they require */
required = get_header(req, "Require");
- if (!ast_strlen_zero(required)) {
+ if (required && !ast_strlen_zero(required)) {
required_profile = parse_sip_options(NULL, required);
- if (required_profile) { /* They require something */
- /* At this point we support no extensions, so fail */
- transmit_response_with_unsupported(p, "420 Bad extension", req, required);
+ if (required_profile && required_profile != SIP_OPT_REPLACES) {
+ /* At this point we only support REPLACES */
+ transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, required);
ast_log(LOG_WARNING,"Received SIP INVITE with unsupported required extension: %s\n", required);
if (!p->lastinvite)
ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
return -1;
-
}
}
/* Check if this is a loop */
- /* This happens since we do not properly support SIP domain
- handling yet... -oej */
if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner && (p->owner->_state != AST_STATE_UP)) {
/* This is a call to ourself. Send ourselves an error code and stop
- processing immediately, as SIP really has no good mechanism for
- being able to call yourself */
+ processing immediately, as SIP really has no good mechanism for
+ being able to call yourself */
+ /* If pedantic is on, we need to check the tags. If they're different, this is
+ in fact a forked call through a SIP proxy somewhere. */
transmit_response(p, "482 Loop Detected", req);
/* We do NOT destroy p here, so that our response will be accepted */
return 0;
}
-
+
if (!ast_test_flag(req, SIP_PKT_IGNORE) && p->pendinginvite) {
/* We already have a pending invite. Sorry. You are on hold. */
transmit_response(p, "491 Request Pending", req);
- if (option_debug > 1)
+ if (option_debug)
ast_log(LOG_DEBUG, "Got INVITE on call where we already have pending INVITE, deferring that - %s\n", p->callid);
- /* Do NOT destroy dialog */
return 0;
}
+
+ if ((p_replaces = get_header(req, "Replaces")) && !ast_strlen_zero(p_replaces)) {
+ /* We have a replaces header */
+ char *ptr;
+ char *fromtag = NULL;
+ char *totag = NULL;
+ char *start, *to;
+ int error = 0;
+
+ if (p->owner) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "INVITE w Replaces on existing call? Refusing action. [%s]\n", p->callid);
+ transmit_response(p, "400 Bad request", req); /* The best way to not not accept the transfer */
+ /* Do not destroy existing call */
+ return -1;
+ }
+
+ if (sipdebug && option_debug > 2)
+ ast_log(LOG_DEBUG, "INVITE part of call transfer. Replaces [%s]\n", p_replaces);
+ /* Create a buffer we can manipulate */
+ replace_id = ast_strdupa(p_replaces);
+ ast_uri_decode(replace_id);
+
+ if (!p->refer && !sip_refer_allocate(p)) {
+ transmit_response(p, "500 Server Internal Error", req);
+ append_history(p, "Xfer", "INVITE/Replace Failed. Out of memory.");
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return -1;
+ }
+
+ /* Todo: (When we find phones that support this)
+ if the replaces header contains ";early-only"
+ we can only replace the call in early
+ stage, not after it's up.
+
+ If it's not in early mode, 486 Busy.
+ */
+
+ /* Skip leading whitespace */
+ while(replace_id[0] && (replace_id[0] < 33))
+ memmove(replace_id, replace_id+1, strlen(replace_id));
+
+ if ((ptr = strchr(replace_id, ';'))) {
+ *ptr = '\0';
+ ptr++;
+ }
+ start = ptr;
+
+ to = strcasestr(ptr, "to-tag=");
+ if (to) {
+ ptr = to + 7;
+ totag = ptr;
+ if ((to = strchr(ptr, ';')))
+ *to = '\0';
+ to++;
+ ptr = to;
+ }
+
+ to = strcasestr(ptr, "from-tag=");
+ if (to) {
+ ptr = to + 9;
+ fromtag = ptr;
+ if ((to = strchr(ptr, '&')))
+ *to = '\0';
+ if ((to = strchr(ptr, ';')))
+ *to = '\0';
+ }
+
+ if (sipdebug && option_debug > 3)
[... 737 lines stripped ...]
More information about the svn-commits
mailing list