/**Create a request belonging to the leg * (stdarg version of nta_outgoing_create()). * * @deprecated * Use nta_outgoing_tcreate() or nta_outgoing_mcreate() instead. */ nta_outgoing_t *nta_outgoing_vcreate(nta_leg_t *leg, nta_response_f *callback, nta_outgoing_magic_t *magic, url_string_t const *route_url, sip_method_t method, char const *name, url_string_t const *request_uri, void const *extra, va_list headers) { nta_agent_t *agent = leg->leg_agent; msg_t *msg = nta_msg_create(agent, 0); sip_t *sip = sip_object(msg); nta_outgoing_t *orq; if (extra && sip_add_headers(msg, sip, extra, headers) < 0) orq = NULL; else if (route_url && leg->leg_route && !sip->sip_route && sip_add_dup(msg, sip, (sip_header_t *)leg->leg_route) < 0) orq = NULL; else if (nta_msg_request_complete(msg, leg, method, name, request_uri) < 0) orq = NULL; else orq = nta_outgoing_mcreate(agent, callback, magic, route_url, msg); if (!orq) msg_destroy(msg); return orq; }
static int nua_refer_client_init(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { nua_handle_t *nh = cr->cr_owner; if (sip->sip_referred_by == NULL) { sip_from_t *a = sip->sip_from; sip_referred_by_t by[1]; sip_referred_by_init(by); if (a == NULL) a = nh->nh_nua->nua_from; by->b_display = a->a_display; *by->b_url = *a->a_url; sip_add_dup(msg, sip, (sip_header_t *)by); } if (sip->sip_event) sip_header_remove(msg, sip, (sip_header_t *)sip->sip_event); return 0; }
int nua_notify_server_init(nua_server_request_t *sr) { if (!sr->sr_initial) { nua_dialog_state_t *ds = sr->sr_owner->nh_ds; /* Check for forked subscription. */ if (ds->ds_remote_tag && ds->ds_remote_tag[0] && su_strcasecmp(ds->ds_remote_tag, sr->sr_request.sip->sip_from->a_tag)) { sip_contact_t const *m = NULL; m = nua_stack_get_contact(sr->sr_owner->nh_nua->nua_registrations); if (m) { sip_warning_t w[1]; sip_warning_init(w)->w_code = 399; w->w_host = m->m_url->url_host; w->w_port = m->m_url->url_port; w->w_text = "Forking SUBSCRIBEs are not supported"; sip_add_dup(sr->sr_response.msg, NULL, (sip_header_t*)w); } return SR_STATUS(sr, 481, "Subscription Does Not Exist"); } } return 0; }
static int nua_subscribe_client_request(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { nua_dialog_usage_t *du = cr->cr_usage; sip_time_t expires = 0; if (cr->cr_event == nua_r_destroy || !du || du->du_shutdown) nua_client_set_terminating(cr, 1); if (du) { struct event_usage *eu = nua_dialog_usage_private(du); sip_event_t *o = sip->sip_event; if (nua_client_bind(cr, du) < 0) return -1; if (eu->eu_no_id && o && o->o_id) { /* Notifier does not handle id properly, remove it */ msg_header_remove_param(o->o_common, "id"); } #if 0 if (cr->cr_terminating) { /* Already terminated subscription? */ if (eu->eu_substate == nua_substate_terminated || eu->eu_substate == nua_substate_embryonic) { return nua_client_return(cr, SIP_200_OK, msg); } } #endif nua_dialog_usage_reset_refresh(du); /* during SUBSCRIBE transaction */ if (cr->cr_terminating || cr->cr_event != nua_r_subscribe) expires = eu->eu_delta = 0; else if (sip->sip_expires) /* Use value specified by application or negotiated with Min-Expires */ expires = eu->eu_delta = sip->sip_expires->ex_delta; else /* We just use common default value, but the default is actually package-specific according to the RFC 3265 section 4.4.4: [Event] packages MUST also define a default "Expires" value to be used if none is specified. */ expires = eu->eu_delta = 3600; eu->eu_final_wait = 0; if (eu->eu_substate == nua_substate_terminated) eu->eu_substate = nua_substate_embryonic; } if (!sip->sip_expires || sip->sip_expires->ex_delta != expires) { sip_expires_t ex[1]; sip_expires_init(ex)->ex_delta = expires; sip_add_dup(msg, sip, (sip_header_t *)ex); } return nua_base_client_request(cr, msg, sip, tags); }
static int nua_subscribe_client_init(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { nua_handle_t *nh = cr->cr_owner; nua_dialog_usage_t *du; sip_event_t *o = sip->sip_event; du = nua_dialog_usage_get(nh->nh_ds, nua_subscribe_usage, o); if (du == NULL && o == NULL) du = nua_dialog_usage_get(nh->nh_ds, nua_subscribe_usage, NONE); if (du) { if (du->du_event && o == NULL) /* Add Event header */ sip_add_dup(msg, sip, (sip_header_t *)du->du_event); } else if (cr->cr_event == nua_r_subscribe) { /* Create dialog usage */ du = nua_dialog_usage_add(nh, nh->nh_ds, nua_subscribe_usage, o); /* Note that we allow SUBSCRIBE without event */ } cr->cr_usage = du; return 0; }
/** Fork an outgoing request (stdarg version of nta_outgoing_fork()). * * @deprecated * Use nta_outgoing_mcreate() instead. */ nta_outgoing_t *nta_outgoing_vfork(nta_outgoing_t *old_orq, nta_response_f *callback, nta_outgoing_magic_t *magic, url_string_t const *route_url, url_string_t const *request_uri, void const *extra, va_list headers) { nta_outgoing_t * orq; msg_t *msg, *imsg; sip_t *sip, *isip; nta_agent_t *agent; su_home_t *home; if (!old_orq || !old_orq->orq_request || !request_uri) return NULL; agent = old_orq->orq_agent; imsg = old_orq->orq_request; if (!(msg = nta_msg_create(agent, 0))) return NULL; msg_clone(msg, imsg); sip = sip_object(msg); isip = sip_object(imsg); home = msg_home(msg); /* Copy the SIP headers from the imsg message */ if (sip_copy_all(msg, sip, isip) < 0) orq = NULL; else if (sip_via_remove(msg, sip) == NULL) orq = NULL; else if (sip_add_dup(msg, sip_object(msg), (sip_header_t const *) sip_request_create(home, sip->sip_request->rq_method, sip->sip_request->rq_method_name, request_uri, NULL)) < 0) orq = NULL; else if (sip_add_headers(msg, sip, extra, headers) < 0) orq = NULL; else orq = nta_outgoing_mcreate(agent, callback, magic, route_url, msg); if (!orq) msg_destroy(msg); return orq; }
/** @internal Respond to a SUBSCRIBE request. * */ static int nua_subscribe_server_respond(nua_server_request_t *sr, tagi_t const *tags) { struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage); msg_t *msg = sr->sr_response.msg; sip_t *sip = sr->sr_response.sip; if (200 <= sr->sr_status && sr->sr_status < 300) { sip_expires_t ex[1]; sip_expires_init(ex); if (nu) { sip_time_t now = sip_now(); if (nu->nu_requested) { if (sip->sip_expires) { /* Expires in response can only shorten the expiration time */ if (nu->nu_requested > now + sip->sip_expires->ex_delta) nu->nu_requested = now + sip->sip_expires->ex_delta; } else { unsigned sub_expires = NH_PGET(sr->sr_owner, sub_expires); if (nu->nu_requested > now + sub_expires) nu->nu_requested = now + sub_expires; } if (nu->nu_requested >= now) nu->nu_expires = nu->nu_requested; else nu->nu_expires = now; if (nu->nu_expires <= now) nu->nu_substate = nua_substate_terminated; } if (nu->nu_expires > now) ex->ex_delta = nu->nu_expires - now; } else { /* Always add header Expires: 0 */ } if (!sip->sip_expires || sip->sip_expires->ex_delta > ex->ex_delta) sip_add_dup(msg, sip, (sip_header_t *)ex); } return nua_base_server_respond(sr, tags); }
/**Send a request message. * * @retval 0 if request is pending * @retval >=1 if error event has been sent * @retval < 0 if no error event has been sent */ static int nua_client_request_sendmsg(nua_client_request_t *cr) { nua_handle_t *nh = cr->cr_owner; nua_dialog_state_t *ds = nh->nh_ds; sip_method_t method = cr->cr_method; char const *name = cr->cr_method_name; url_string_t const *url = (url_string_t *)cr->cr_target; nta_leg_t *leg; msg_t *msg; sip_t *sip; int error; assert(cr->cr_orq == NULL); cr->cr_offer_sent = cr->cr_answer_recv = 0; cr->cr_offer_recv = cr->cr_answer_sent = 0; if (!ds->ds_leg && cr->cr_dialog) { ds->ds_leg = nta_leg_tcreate(nh->nh_nua->nua_nta, nua_stack_process_request, nh, SIPTAG_CALL_ID(cr->cr_sip->sip_call_id), SIPTAG_FROM(cr->cr_sip->sip_from), SIPTAG_TO(cr->cr_sip->sip_to), SIPTAG_CSEQ(cr->cr_sip->sip_cseq), TAG_END()); if (!ds->ds_leg) return -1; } if (cr->cr_sip->sip_from && ds->ds_leg) { if (cr->cr_sip->sip_from->a_tag == NULL) { if (sip_from_tag(msg_home(cr->cr_msg), cr->cr_sip->sip_from, nta_leg_tag(ds->ds_leg, NULL)) < 0) { return -1; } } } cr->cr_retry_count++; if (ds->ds_leg) leg = ds->ds_leg; else leg = nh->nh_nua->nua_dhandle->nh_ds->ds_leg; /* Default leg */ msg = msg_copy(cr->cr_msg), sip = sip_object(msg); if (msg == NULL) return -1; if (nua_dialog_is_established(ds)) { while (sip->sip_route) sip_route_remove(msg, sip); } else if (!ds->ds_route) { sip_route_t *initial_route = NH_PGET(nh, initial_route); if (initial_route) { initial_route = sip_route_dup(msg_home(msg), initial_route); if (!initial_route) return -1; msg_header_prepend(msg, (msg_pub_t*)sip, /* This should be (msg_header_t **)&sip->sip_route * but directly casting pointer &sip->sip_route gives * spurious type-punning warning */ (msg_header_t **)((char *)sip + offsetof(sip_t, sip_route)), (msg_header_t *)initial_route); } } /** * For in-dialog requests, the request URI is taken from the @Contact * header received from the remote party during dialog establishment, * and the NUTAG_URL() is ignored. * * Also, the @CallID and @CSeq headers and @From and @To tags are * generated based on the dialog information and added to the request. * If the dialog has a route, it is added to the request, too. */ if (nta_msg_request_complete(msg, leg, method, name, url) < 0) { msg_destroy(msg); return -1; } /**@MaxForwards header (with default value set by NTATAG_MAX_FORWARDS()) is * also added now, if it does not exist. */ if (!ds->ds_remote) ds->ds_remote = sip_to_dup(nh->nh_home, sip->sip_to); if (!ds->ds_local) ds->ds_local = sip_from_dup(nh->nh_home, sip->sip_from); /** * Next, values previously set with nua_set_params() or nua_set_hparams() * are used: @Allow, @Supported, @Organization, @UserAgent and * @AllowEvents headers are added to the request if they are not already * set. */ if (!sip->sip_allow) sip_add_dup(msg, sip, (sip_header_t*)NH_PGET(nh, allow)); if (!sip->sip_supported && NH_PGET(nh, supported)) sip_add_dup(msg, sip, (sip_header_t *)NH_PGET(nh, supported)); if (method == sip_method_register && NH_PGET(nh, path_enable) && !sip_has_feature(sip->sip_supported, "path") && !sip_has_feature(sip->sip_require, "path")) sip_add_make(msg, sip, sip_supported_class, "path"); if (!sip->sip_organization && NH_PGET(nh, organization)) sip_add_make(msg, sip, sip_organization_class, NH_PGET(nh, organization)); if (!sip->sip_user_agent && NH_PGET(nh, user_agent)) sip_add_make(msg, sip, sip_user_agent_class, NH_PGET(nh, user_agent)); /** Any node implementing one or more event packages SHOULD include an * appropriate @AllowEvents header indicating all supported events in * all methods which initiate dialogs and their responses (such as * INVITE) and OPTIONS responses. */ if (!sip->sip_allow_events && NH_PGET(nh, allow_events) && (method == sip_method_notify || /* Always in NOTIFY */ (!ds->ds_remote_tag && /* And in initial requests */ (method == sip_method_subscribe || method == sip_method_refer || method == sip_method_options || method == sip_method_invite)))) sip_add_dup(msg, sip, (void *)NH_PGET(nh, allow_events)); /** * Next, the stack generates a @Contact header for the request (unless * the application already gave a @Contact header or it does not want to * use @Contact and indicates that by including SIPTAG_CONTACT(NULL) or * SIPTAG_CONTACT(SIP_NONE) in the tagged parameters.) If the * application has registered the URI in @From header, the @Contact * header used with registration is used. Otherwise, the @Contact header * is generated from the local IP address and port number. */ /**For the initial requests, @ServiceRoute set that was received from the * registrar is also added to the request message. */ if (cr->cr_method != sip_method_register) { if (cr->cr_contactize && cr->cr_has_contact) { sip_contact_t *ltarget = sip_contact_dup(nh->nh_home, sip->sip_contact); if (ds->ds_ltarget) msg_header_free(nh->nh_home, (msg_header_t *)ds->ds_ltarget); ds->ds_ltarget = ltarget; } if (ds->ds_ltarget && !cr->cr_has_contact) sip_add_dup(msg, sip, (sip_header_t *)ds->ds_ltarget); if (nua_registration_add_contact_to_request(nh, msg, sip, cr->cr_contactize && !cr->cr_has_contact && !ds->ds_ltarget, !ds->ds_route) < 0) { msg_destroy(msg); return -1; } } cr->cr_wait_for_cred = 0; if (cr->cr_methods->crm_send) error = cr->cr_methods->crm_send(cr, msg, sip, NULL); else error = nua_base_client_request(cr, msg, sip, NULL); if (error == -1) msg_destroy(msg); return error; }
/**Initialize client request for sending. * * This function is called when the request is taken from queue and sent. * * @retval 0 if request is pending * @retval >=1 if error event has been sent */ static int nua_client_init_request0(nua_client_request_t *cr) { nua_handle_t *nh = cr->cr_owner; nua_t *nua = nh->nh_nua; nua_dialog_state_t *ds = nh->nh_ds; msg_t *msg = NULL; sip_t *sip; url_string_t const *url = NULL; tagi_t const *t; int has_contact = 0; int error = 0; if (!cr->cr_method_name) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), NULL); if (cr->cr_msg) return nua_client_request_try(cr); cr->cr_answer_recv = 0, cr->cr_offer_sent = 0; cr->cr_offer_recv = 0, cr->cr_answer_sent = 0; cr->cr_terminated = 0, cr->cr_graceful = 0; nua_stack_init_handle(nua, nh, cr->cr_tags); if (cr->cr_method == sip_method_cancel) { if (cr->cr_methods->crm_init) { error = cr->cr_methods->crm_init(cr, NULL, NULL, cr->cr_tags); if (error) return error; } if (cr->cr_methods->crm_send) return cr->cr_methods->crm_send(cr, NULL, NULL, cr->cr_tags); else return nua_base_client_request(cr, NULL, NULL, cr->cr_tags); } if (!cr->cr_methods->crm_template || cr->cr_methods->crm_template(cr, &msg, cr->cr_tags) == 0) msg = nua_client_request_template(cr); sip = sip_object(msg); if (!sip) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); if (nh->nh_tags) { for (t = nh->nh_tags; t; t = t_next(t)) { if (t->t_tag == siptag_contact || t->t_tag == siptag_contact_str) has_contact = 1; else if (t->t_tag == nutag_url) url = (url_string_t const *)t->t_value; } } /**@par Populating SIP Request Message with Tagged Arguments * * The tagged arguments can be used to pass values for any SIP headers * to the stack. When the INVITE message (or any other SIP message) is * created, the tagged values saved with nua_handle() are used first, * next the tagged values given with the operation (nua_invite()) are * added. * * When multiple tags for the same header are specified, the behaviour * depends on the header type. If only a single header field can be * included in a SIP message, the latest non-NULL value is used, e.g., * @Subject. However, if the SIP header can consist of multiple lines or * header fields separated by comma, e.g., @Accept, all the tagged * values are concatenated. * * However, if a tag value is #SIP_NONE (-1 casted as a void pointer), * the values from previous tags are ignored. */ for (t = cr->cr_tags; t; t = t_next(t)) { if (t->t_tag == siptag_contact || t->t_tag == siptag_contact_str) has_contact = 1; else if (t->t_tag == nutag_url) url = (url_string_t const *)t->t_value; else if (t->t_tag == nutag_dialog) { cr->cr_dialog = t->t_value > 1; cr->cr_contactize = t->t_value >= 1; } else if (t->t_tag == nutag_auth && t->t_value) { /* XXX ignoring errors */ if (nh->nh_auth) auc_credentials(&nh->nh_auth, nh->nh_home, (char *)t->t_value); } } if (cr->cr_method == sip_method_register && url == NULL) url = (url_string_t const *)NH_PGET(nh, registrar); if ((t = cr->cr_tags)) { if (sip_add_tagis(msg, sip, &t) < 0) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); } /** * Now, the target URI for the request needs to be determined. * * For initial requests, values from tags are used. If NUTAG_URL() is * given, it is used as target URI. Otherwise, if SIPTAG_TO() is given, * it is used as target URI. If neither is given, the complete request * line already specified using SIPTAG_REQUEST() or SIPTAG_REQUEST_STR() * is used. At this point, the target URI is stored in the request line, * together with method name and protocol version ("SIP/2.0"). The * initial dialog information is also created: @CallID, @CSeq headers * are generated, if they do not exist, and a tag is added to the @From * header. */ if (!ds->ds_leg) { if (ds->ds_remote_tag && ds->ds_remote_tag[0] && sip_to_tag(nh->nh_home, sip->sip_to, ds->ds_remote_tag) < 0) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); if (sip->sip_from == NULL && sip_add_dup(msg, sip, (sip_header_t *)nua->nua_from) < 0) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); if (sip->sip_to == NULL && cr->cr_method == sip_method_register && sip_add_dup_as(msg, sip, sip_to_class, (sip_header_t *)sip->sip_from) < 0) { return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); } } else { if (ds->ds_route) url = NULL; } if (url && nua_client_set_target(cr, (url_t *)url) < 0) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); cr->cr_has_contact = has_contact; if (cr->cr_methods->crm_init) { error = cr->cr_methods->crm_init(cr, msg, sip, cr->cr_tags); if (error < -1) msg = NULL; if (error < 0) return nua_client_return(cr, NUA_ERROR_AT(__FILE__, __LINE__), msg); if (error != 0) return error; } cr->cr_msg = msg; cr->cr_sip = sip; return nua_client_request_try(cr); }
int nua_base_client_check_restart(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip) { nua_handle_t *nh = cr->cr_owner; nta_outgoing_t *orq; #if 0 if (status == 302 || status == 305) { sip_route_t r[1]; if (!can_redirect(sip->sip_contact, cr->cr_method)) return 0; switch (status) { case 302: if (nua_dialog_zap(nh, nh->nh_ds) == 0 && nua_client_set_target(cr, sip->sip_contact->m_url) >= 0) return nua_client_restart(cr, 100, "Redirected"); break; case 305: sip_route_init(r); *r->r_url = *sip->sip_contact->m_url; if (nua_dialog_zap(nh, nh->nh_ds) == 0 && sip_add_dup(cr->cr_msg, cr->cr_sip, (sip_header_t *)r) >= 0) return nua_client_restart(cr, 100, "Redirected via a proxy"); break; default: break; } } #endif if (status == 423) { unsigned my_expires = 0; if (cr->cr_sip->sip_expires) my_expires = cr->cr_sip->sip_expires->ex_delta; if (sip->sip_min_expires && sip->sip_min_expires->me_delta > my_expires) { sip_expires_t ex[1]; sip_expires_init(ex); ex->ex_delta = sip->sip_min_expires->me_delta; if (sip_add_dup(cr->cr_msg, NULL, (sip_header_t *)ex) < 0) return 0; return nua_client_restart(cr, 100, "Re-Negotiating Expiration"); } } if ((status == 401 && sip->sip_www_authenticate) || (status == 407 && sip->sip_proxy_authenticate)) { int server = 0, proxy = 0; if (sip->sip_www_authenticate) server = auc_challenge(&nh->nh_auth, nh->nh_home, sip->sip_www_authenticate, sip_authorization_class); if (sip->sip_proxy_authenticate) proxy = auc_challenge(&nh->nh_auth, nh->nh_home, sip->sip_proxy_authenticate, sip_proxy_authorization_class); if (server >= 0 && proxy >= 0) { int invalid = cr->cr_challenged && server + proxy == 0; cr->cr_challenged = 1; if (invalid) { /* Bad username/password */ SU_DEBUG_7(("nua(%p): bad credentials, clearing them\n", (void *)nh)); auc_clear_credentials(&nh->nh_auth, NULL, NULL); } else if (auc_has_authorization(&nh->nh_auth)) return nua_client_restart(cr, 100, "Request Authorized by Cache"); orq = cr->cr_orq, cr->cr_orq = NULL; cr->cr_waiting = cr->cr_wait_for_cred = 1; nua_client_report(cr, status, phrase, NULL, orq, NULL); nta_outgoing_destroy(orq); cr->cr_status = 0, cr->cr_phrase = NULL; nua_client_request_unref(cr); return 1; } } if (0 && 500 <= status && status < 600 && sip->sip_retry_after && sip->sip_retry_after->af_delta < 32) { su_timer_t *timer; char phrase[18]; /* Retry After XXXX\0 */ timer = su_timer_create(su_root_task(nh->nh_nua->nua_root), 0); if (su_timer_set_interval(timer, nua_client_restart_after, cr, sip->sip_retry_after->af_delta * 1000) < 0) { su_timer_destroy(timer); return 0; /* Too bad */ } cr->cr_timer = timer; /* This takes over cr reference from orq */ snprintf(phrase, sizeof phrase, "Retry After %u", (unsigned)sip->sip_retry_after->af_delta); orq = cr->cr_orq, cr->cr_orq = NULL; cr->cr_waiting = 1; nua_client_report(cr, 100, phrase, NULL, orq, NULL); nta_outgoing_destroy(orq); cr->cr_status = 0, cr->cr_phrase = NULL; return 1; } return 0; /* This was a final response that cannot be restarted. */ }
static int nua_notify_client_request(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { nua_dialog_usage_t *du = cr->cr_usage; struct notifier_usage *nu = nua_dialog_usage_private(du); su_home_t *home = msg_home(msg); sip_time_t now = sip_now(); sip_subscription_state_t *ss = sip->sip_subscription_state; char const *expires; if (du == NULL) /* Subscription has been terminated */ return nua_client_return(cr, SIP_481_NO_TRANSACTION, msg); assert(du && nu); if (du && nua_client_bind(cr, du) < 0) return -1; if (nu->nu_requested) nu->nu_expires = nu->nu_requested; nu->nu_requested = 0; if (nu->nu_expires <= now || du->du_shutdown) { nu->nu_substate = nua_substate_terminated; expires = "expires=0"; } else { expires = su_sprintf(home, "expires=%lu", nu->nu_expires - now); } if (ss == NULL || nua_substate_make(ss->ss_substate) != nu->nu_substate) { if (nu->nu_substate == nua_substate_terminated) expires = nu->nu_expires > now ? "reason=noresource" : "reason=timeout"; ss = sip_subscription_state_format(home, "%s;%s", nua_substate_name(nu->nu_substate), expires); msg_header_insert(msg, (void *)sip, (void *)ss); } else if (nu->nu_substate != nua_substate_terminated) { msg_header_replace_param(home, ss->ss_common, expires); } #if SU_HAVE_EXPERIMENTAL if (nu->nu_tag && !sip->sip_etag) msg_header_add_make(msg, (void *)sip, sip_etag_class, nu->nu_tag); if (nu->nu_no_body) { nu->nu_no_body = 0; msg_header_remove(msg, (void *)sip, (void *)sip->sip_payload); msg_header_remove(msg, (void *)sip, (void *)sip->sip_content_length); } #endif if (nu->nu_substate == nua_substate_terminated) nua_client_set_terminating(cr, 1); if (cr->cr_terminating) { nua_server_request_t *sr; for (sr = du->du_dialog->ds_sr; sr; sr = sr->sr_next) { if (sr->sr_usage == du) { /* If subscribe has not been responded, don't terminate usage by NOTIFY */ sr->sr_terminating = 1; nua_client_set_terminating(cr, 0); break; } } } if (du->du_event && !sip->sip_event) sip_add_dup(cr->cr_msg, sip, (sip_header_t *)du->du_event); return nua_base_client_request(cr, msg, sip, tags); }