return 0;
}
+/*! \brief Checks state of the p->refer->refer_call state: can it be picked up?
+ *
+ * It will terminate the call if it cannot be picked up; e.g. because it
+ * was gone, or because it wasn't in a ringing state.
+ *
+ * \return 1 if terminated, 0 if ok
+ */
+static int terminate_on_invalid_replaces_state(struct sip_pvt *p, struct sip_request *req, const char *replace_id)
+{
+ if (p->refer->refer_call == p) {
+ ast_log(LOG_NOTICE, "INVITE with replaces into its own call id (%s == %s)!\n", replace_id, p->callid);
+ transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */
+
+ } else if (!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 */
+ transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replace)", req);
+
+ } else if (ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RINGING &&
+ ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RING &&
+ ast_channel_state(p->refer->refer_call->owner) != 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);
+
+ } else {
+ /* Ok */
+ return 0;
+ }
+ /* Terminated */
+ return 1;
+}
+
/*!
* \brief bare-bones support for SIP UPDATE
*
int gotdest;
const char *p_replaces;
char *replace_id = NULL;
+ int magic_call_id = 0;
int refer_locked = 0;
const char *required;
unsigned int required_profile = 0;
ast_channel_unlock(subscription->owner);
}
subscription = dialog_unref(subscription, "unref dialog subscription");
+ magic_call_id = !ast_strlen_zero(pickup.exten);
}
}
- /* This locks both refer_call pvt and refer_call pvt's owner!!!*/
- if (!error && ast_strlen_zero(pickup.exten) && (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;
- } else {
- refer_locked = 1;
- }
-
- /* The matched call is the call from the transferer to Asterisk .
- We want to bridge the bridged part of the call to the
- incoming invite, thus taking over the refered call */
-
- if (p->refer->refer_call == p) {
- ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", replace_id, p->callid);
- transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */
- error = 1;
- }
-
- if (!error && ast_strlen_zero(pickup.exten) && !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 */
- transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replace)", req);
- error = 1;
- }
-
- if (!error && ast_strlen_zero(pickup.exten) && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RINGING && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RING && ast_channel_state(p->refer->refer_call->owner) != 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;
+ if (!error) {
+ if (magic_call_id) {
+ ;
+ /* This locks both refer_call pvt and refer_call pvt's owner!!!*/
+ } else if ((p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag))) {
+ /* The matched call is the call from the transferer to Asterisk .
+ We want to bridge the bridged part of the call to the
+ incoming invite, thus taking over the refered call */
+ refer_locked = 1;
+ if (terminate_on_invalid_replaces_state(p, req, replace_id)) {
+ error = 1;
+ }
+ } else {
+ 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;
+ }
}
if (error) { /* Give up this dialog */
goto request_invite_cleanup;
}
+ /* We cannot call sip_new with any channel locks
+ * held. Unlock the referred channel if locked. */
+ if (refer_locked) {
+ sip_pvt_unlock(p->refer->refer_call);
+ if (p->refer->refer_call->owner) {
+ ast_channel_unlock(p->refer->refer_call->owner);
+ }
+ }
+
/* First invitation - create the channel. Allocation
* failures are handled below. */
-
c = sip_new(p, AST_STATE_DOWN, S_OR(p->peername, NULL), NULL, p->logger_callid);
+ /* Reacquire the lock on the referred call. */
+ if (refer_locked) {
+ /* Reuse deadlock avoid pattern found in
+ * get_sip_pvt_byid_locked. */
+ sip_pvt_lock(p->refer->refer_call);
+ while (p->refer->refer_call->owner && ast_channel_trylock(p->refer->refer_call->owner)) {
+ sip_pvt_unlock(p->refer->refer_call);
+ usleep(1);
+ sip_pvt_lock(p->refer->refer_call);
+ }
+
+ /* And now, check once more that the call is still
+ * still available. Yuck. Bail out if it isn't. */
+ if (terminate_on_invalid_replaces_state(p, req, replace_id)) {
+ /* Free the referred call: it's not ours anymore. */
+ sip_pvt_unlock(p->refer->refer_call);
+ if (p->refer->refer_call->owner) {
+ ast_channel_unlock(p->refer->refer_call->owner);
+ }
+ p->refer->refer_call = dialog_unref(p->refer->refer_call, "unref dialog p->refer->refer_call");
+ refer_locked = 0;
+ /* Kill the channel we just created. */
+ sip_pvt_unlock(p);
+ ast_hangup(c);
+ sip_pvt_lock(p); /* pvt is expected to remain locked on return, so re-lock it */
+ /* Mark the call as failed. */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ p->invitestate = INV_COMPLETED;
+ res = INV_REQ_ERROR;
+ goto request_invite_cleanup;
+ }
+ }
+
if (cc_recall_core_id != -1) {
ast_setup_cc_recall_datastore(c, cc_recall_core_id);
ast_cc_agent_set_interfaces_chanvar(c);
p->lastinvite = seqno;
if (c && replace_id) { /* Attended transfer or call pickup - we're the target */
- if (!ast_strlen_zero(pickup.exten)) {
+ if (magic_call_id) {
append_history(p, "Xfer", "INVITE/Replace received");
/* Let the caller know we're giving it a shot */
request_invite_cleanup:
- if (refer_locked && p->refer && p->refer->refer_call) {
+ if (refer_locked) {
sip_pvt_unlock(p->refer->refer_call);
if (p->refer->refer_call->owner) {
ast_channel_unlock(p->refer->refer_call->owner);