[asterisk-commits] trunk r31274 - /trunk/channels/chan_sip.c

asterisk-commits at lists.digium.com asterisk-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 asterisk-commits mailing list