Index: /pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 2361)
+++ /pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 2362)
@@ -143,6 +143,8 @@
 struct tsx_inv_data
 {
-    pjsip_inv_session	*inv;
-    pj_bool_t		 sdp_done;
+    pjsip_inv_session	*inv;	    /* The invite session		    */
+    pj_bool_t		 sdp_done;  /* SDP negotiation done for this tsx?   */
+    pj_str_t		 done_tag;  /* To tag in RX response with answer    */
+    pj_bool_t		 done_early;/* Negotiation was done for early med?  */
 };
 
@@ -1408,5 +1410,20 @@
     pj_status_t status;
     pjsip_msg *msg;
-    pjmedia_sdp_session *sdp;
+    pjmedia_sdp_session *rem_sdp;
+
+    /* Check if SDP is present in the message. */
+
+    msg = rdata->msg_info.msg;
+    if (msg->body == NULL) {
+	/* Message doesn't have body. */
+	return PJ_SUCCESS;
+    }
+
+    if (pj_stricmp(&msg->body->content_type.type, &str_application) ||
+	pj_stricmp(&msg->body->content_type.subtype, &str_sdp))
+    {
+	/* Message body is not "application/sdp" */
+	return PJMEDIA_SDP_EINSDP;
+    }
 
     /* Get/attach invite session's transaction data */
@@ -1418,28 +1435,57 @@
     }
 
-    /* MUST NOT do multiple SDP offer/answer in a single transaction. 
+    /* MUST NOT do multiple SDP offer/answer in a single transaction,
+     * EXCEPT if:
+     *	- this is an initial UAC INVITE transaction (i.e. not re-INVITE), and
+     *	- the previous negotiation was done on an early media (18x) and
+     *    this response is a final/2xx response, and
+     *  - the 2xx response has different To tag than the 18x response
+     *    (i.e. the request has forked).
+     *
+     * The exception above is to add a rudimentary support for early media
+     * forking (sample case: custom ringback). See this ticket for more
+     * info: http://trac.pjsip.org/repos/ticket/657
      */
-
     if (tsx_inv_data->sdp_done) {
-	if (rdata->msg_info.msg->body) {
-	    PJ_LOG(4,(inv->obj_name, "SDP negotiation done, message "
-		      "body is ignored"));
-	}
-	return PJ_SUCCESS;
-    }
-
-    /* Check if SDP is present in the message. */
-
-    msg = rdata->msg_info.msg;
-    if (msg->body == NULL) {
-	/* Message doesn't have body. */
-	return PJ_SUCCESS;
-    }
-
-    if (pj_stricmp(&msg->body->content_type.type, &str_application) ||
-	pj_stricmp(&msg->body->content_type.subtype, &str_sdp))
-    {
-	/* Message body is not "application/sdp" */
-	return PJMEDIA_SDP_EINSDP;
+	pj_str_t res_tag;
+
+	res_tag = rdata->msg_info.to->tag;
+
+	/* Allow final response after SDP has been negotiated in early
+	 * media, IF this response is a final response with different
+	 * tag.
+	 */
+	if (tsx->role == PJSIP_ROLE_UAC &&
+	    rdata->msg_info.msg->line.status.code/100 == 2 &&
+	    tsx_inv_data->done_early &&
+	    pj_strcmp(&tsx_inv_data->done_tag, &res_tag))
+	{
+	    const pjmedia_sdp_session *reoffer_sdp = NULL;
+
+	    PJ_LOG(4,(inv->obj_name, "Received forked final response "
+		      "after SDP negotiation has been done in early "
+		      "media. Renegotiating SDP.."));
+
+	    /* Retrieve original SDP offer from INVITE request */
+	    reoffer_sdp = (const pjmedia_sdp_session*) 
+			  tsx->last_tx->msg->body->data;
+
+	    /* Feed the original offer to negotiator */
+	    status = pjmedia_sdp_neg_modify_local_offer(inv->pool, inv->neg,
+						        reoffer_sdp);
+	    if (status != PJ_SUCCESS) {
+		PJ_LOG(1,(inv->obj_name, "Error updating local offer for "
+			  "forked 2xx response (err=%d)", status));
+		return status;
+	    }
+
+	} else {
+
+	    if (rdata->msg_info.msg->body) {
+		PJ_LOG(4,(inv->obj_name, "SDP negotiation done, message "
+			  "body is ignored"));
+	    }
+	    return PJ_SUCCESS;
+	}
     }
 
@@ -1448,7 +1494,7 @@
     status = pjmedia_sdp_parse(rdata->tp_info.pool, 
 			       (char*)msg->body->data,
-			       msg->body->len, &sdp);
+			       msg->body->len, &rem_sdp);
     if (status == PJ_SUCCESS)
-	status = pjmedia_sdp_validate(sdp);
+	status = pjmedia_sdp_validate(rem_sdp);
 
     if (status != PJ_SUCCESS) {
@@ -1473,7 +1519,8 @@
 	if (inv->neg == NULL) {
 	    status=pjmedia_sdp_neg_create_w_remote_offer(inv->pool, NULL, 
-							 sdp, &inv->neg);
+							 rem_sdp, &inv->neg);
 	} else {
-	    status=pjmedia_sdp_neg_set_remote_offer(inv->pool, inv->neg, sdp);
+	    status=pjmedia_sdp_neg_set_remote_offer(inv->pool, inv->neg, 
+						    rem_sdp);
 	}
 
@@ -1490,5 +1537,5 @@
 	if (mod_inv.cb.on_rx_offer && inv->notify) {
 
-	    (*mod_inv.cb.on_rx_offer)(inv, sdp);
+	    (*mod_inv.cb.on_rx_offer)(inv, rem_sdp);
 
 	}
@@ -1497,4 +1544,5 @@
 		PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) 
     {
+	int status_code;
 
 	/* This is an answer. 
@@ -1505,5 +1553,6 @@
 		  pjsip_rx_data_get_info(rdata)));
 
-	status = pjmedia_sdp_neg_set_remote_answer(inv->pool, inv->neg, sdp);
+	status = pjmedia_sdp_neg_set_remote_answer(inv->pool, inv->neg, 
+						   rem_sdp);
 
 	if (status != PJ_SUCCESS) {
@@ -1519,7 +1568,13 @@
 	inv_negotiate_sdp(inv);
 
-	/* Mark this transaction has having SDP offer/answer done. */
+	/* Mark this transaction has having SDP offer/answer done, and
+	 * save the reference to the To tag
+	 */
 
 	tsx_inv_data->sdp_done = 1;
+	status_code = rdata->msg_info.msg->line.status.code;
+	tsx_inv_data->done_early = (status_code/100==1);
+	pj_strdup(tsx->pool, &tsx_inv_data->done_tag, 
+		  &rdata->msg_info.to->tag);
 
     } else {
