Index: channels/chan_sip.c
===================================================================
--- channels/chan_sip.c	(.../trunk)	(revision 144190)
+++ channels/chan_sip.c	(.../team/seanbright/issue5014)	(revision 144190)
@@ -1153,6 +1153,13 @@
 	REFER_NOAUTH                   /*!< We had no auth for REFER */
 };
 
+/* \brief struct to store off exten and context for tech independent pickup.
+ */
+struct pickup_target {
+	char *exten;
+	char *context;
+};
+
 /*! \brief generic struct to map between strings and integers.
  * Fill it with x-s pairs, terminate with an entry with s = NULL;
  * Then you can call map_x_s(...) to map an integer to a string,
@@ -1957,6 +1964,7 @@
 static int sip_refer_allocate(struct sip_pvt *p);
 static void ast_quiet_chan(struct ast_channel *chan);
 static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target);
+static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
 /*!
  * \brief generic function for determining if a correct transport is being 
  * used to contact a peer
@@ -9367,11 +9375,25 @@
 		break;
 	case DIALOG_INFO_XML: /* SNOM subscribes in this format */
 		ast_str_append(&tmp, 0, "<?xml version=\"1.0\"?>\n");
-		ast_str_append(&tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%d\" state=\"%s\" entity=\"%s\">\n", p->dialogver++, full ? "full":"partial", mto);
-		if ((state & AST_EXTENSION_RINGING) && global_notifyringing)
-			ast_str_append(&tmp, 0, "<dialog id=\"%s\" direction=\"recipient\">\n", p->exten);
-		else
+		ast_str_append(&tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%d\" state=\"%s\" entity=\"%s\">\n", p->dialogver++, full ? "full" : "partial", mto);
+		if ((state & AST_EXTENSION_RINGING) && global_notifyringing) {
+			/* We create a fake call-id which the phone will send back in an INVITE
+			   Replaces header which we can grab and do some magic with. */
+			ast_str_append(&tmp, 0, "<dialog id=\"%s\" call-id=\"pickup-%s\" direction=\"recipient\">\n", p->exten, p->callid);
+			ast_str_append(&tmp, 0, "<remote>\n");
+			/* Note that the identity and target elements for the local participant are currently
+			   (and may forever be) incorrect since we have no reliable way to get at that information 
+			   at the moment.  Luckily the phone seems to still live happily without it being correct */
+			ast_str_append(&tmp, 0, "<identity>%s</identity>\n", mto);
+			ast_str_append(&tmp, 0, "<target uri=\"%s\"/>\n", mto);
+			ast_str_append(&tmp, 0, "</remote>\n");
+			ast_str_append(&tmp, 0, "<local>\n");
+			ast_str_append(&tmp, 0, "<identity>%s</identity>\n", mto);
+			ast_str_append(&tmp, 0, "<target uri=\"%s\"/>\n", mto);
+			ast_str_append(&tmp, 0, "</local>\n");
+		} else {
 			ast_str_append(&tmp, 0, "<dialog id=\"%s\">\n", p->exten);
+		}
 		ast_str_append(&tmp, 0, "<state>%s</state>\n", statestring);
 		if (state == AST_EXTENSION_ONHOLD) {
 			ast_str_append(&tmp, 0, "<local>\n<target uri=\"%s\">\n"
@@ -17116,7 +17138,39 @@
 	return sip_uri_params_cmp(params1, params2);
 }
 
+static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context)
+{
+	char *argument = NULL;
+	int length;
+	struct ast_app *pickup = pbx_findapp("Pickup");
 
+	if (!pickup) {
+		ast_log(LOG_ERROR, "Unable to perform pickup: Application 'Pickup' not loaded (app_directed_pickup.so).\n");
+		return -1;
+	}
+
+	/* Enough room for 'extension', 'context', and '@' (and \0) */
+	length = strlen(extension) + strlen(context) + 2;
+
+	if (!(argument = ast_malloc(length))) {
+		ast_log(LOG_ERROR, "Failed to allocate memory for pickup extension.\n");
+		return -1;
+	}
+
+	snprintf(argument, length, "%s@%s", extension, context);
+
+	ast_debug(2, "About to call Pickup(%s)\n", argument);
+
+	/* There is no point in capturing the return value since pickup_exec
+	   doesn't return anything meaningful unless the passed data is an empty
+	   string (which in our case it will not be) */
+	pbx_exec(channel, pickup, argument);
+
+	ast_free(argument);
+
+	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
@@ -17143,6 +17197,7 @@
 	int st_interval = 0;            /* Session-Timer negotiated refresh interval                */
 	enum st_refresher st_ref;       /* Session-Timer session refresher                          */
 	int dlg_min_se = -1;
+	struct pickup_target *pickup = NULL;
 	st_ref = SESSION_TIMER_REFRESHER_AUTO;
 
 	/* Find out what they support */
@@ -17279,14 +17334,41 @@
 			}
 		}
 
-		if (sipdebug) 
-			ast_debug(4, "Invite/replaces: Will use Replace-Call-ID : %s Fromtag: %s Totag: %s\n", replace_id, fromtag ? fromtag : "<no from tag>", totag ? totag : "<no to tag>");
+		if (sipdebug)
+			ast_debug(4, "Invite/replaces: Will use Replace-Call-ID : %s Fromtag: %s Totag: %s\n",
+					  replace_id,
+					  fromtag ? fromtag : "<no from tag>",
+					  totag ? totag : "<no to tag>");
 
+		/* Try to find call that we are replacing.
+		   If we have a Replaces header, we need to cancel that call if we succeed with this call.
+		   First we cheat a little and look for a magic call-id from phones that support
+		   dialog-info+xml so we can do technology independent pickup... */
+		if (strncmp(replace_id, "pickup-", 7) == 0) {
+			struct sip_pvt *subscription = NULL;
+			replace_id += 7; /* Worst case we are looking at \0 */
 
-		/* Try to find call that we are replacing 
-			If we have a Replaces  header, we need to cancel that call if we succeed with this call 
-		*/
-		if ((p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
+			if ((subscription = get_sip_pvt_byid_locked(replace_id, NULL, NULL)) == NULL) {
+				ast_log(LOG_NOTICE, "Unable to find subscription with call-id: %s\n", replace_id);
+				transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
+				error = 1;
+			} else {
+				if (!(pickup = ast_calloc(1, sizeof(*pickup)))) {
+					ast_log(LOG_NOTICE, "Memory allocation failed.\n");
+					error = 1;
+				} else {
+					ast_log(LOG_NOTICE, "Trying to pick up %s@%s\n", subscription->exten, subscription->context);
+					pickup->exten = ast_strdup(subscription->exten);
+					pickup->context = ast_strdup(subscription->context);
+					sip_pvt_unlock(subscription);
+					if (subscription->owner) {
+						ast_channel_unlock(subscription->owner);
+					}
+				}
+			}
+		}
+
+		if (!error && !pickup && (p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
 			ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id);
 			transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
 			error = 1;
@@ -17305,7 +17387,7 @@
 			error = 1;
 		}
 
-		if (!error && !p->refer->refer_call->owner) {
+		if (!error && !pickup && !p->refer->refer_call->owner) {
 			/* Oops, someting wrong anyway, no owner, no call */
 			ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", replace_id);
 			/* Check for better return code */
@@ -17313,7 +17395,7 @@
 			error = 1;
 		}
 
-		if (!error && p->refer->refer_call->owner->_state != AST_STATE_RINGING && p->refer->refer_call->owner->_state != AST_STATE_RING && p->refer->refer_call->owner->_state != AST_STATE_UP ) {
+		if (!error && !pickup && p->refer->refer_call->owner->_state != AST_STATE_RINGING && p->refer->refer_call->owner->_state != AST_STATE_RING && p->refer->refer_call->owner->_state != AST_STATE_UP) {
 			ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id);
 			transmit_response_reliable(p, "603 Declined (Replaces)", req);
 			error = 1;
@@ -17626,10 +17708,32 @@
 		p->lastinvite = seqno;
 
 	if (replace_id) { 	/* Attended transfer or call pickup - we're the target */
-		/* Go and take over the target call */
-		if (sipdebug)
-			ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
-		return handle_invite_replaces(p, req, debug, seqno, sin);
+		if (pickup) {
+			append_history(p, "Xfer", "INVITE/Replace received");
+
+			/* Let the caller know we're giving it a shot */
+			transmit_response(p, "100 Trying", req);
+			ast_setstate(c, AST_STATE_RING);
+
+			/* Do the pickup itself */
+			ast_channel_unlock(c);
+			*nounlock = 1;
+			do_magic_pickup(c, pickup->exten, pickup->context);
+
+			/* Now we're either masqueraded or we failed to pickup, in either case we... */
+			ast_hangup(c);
+
+			/* Do a bit of cleanup */
+			ast_free(pickup->exten);
+			ast_free(pickup->context);
+			ast_free(pickup);
+			return 0;
+		} else {
+			/* Go and take over the target call */
+			if (sipdebug)
+				ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
+			return handle_invite_replaces(p, req, debug, seqno, sin);
+		}
 	}
