/* * This file is part of the Sofia-SIP package * * Copyright (C) 2006 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@CFILE nua_notifier.c * @brief SUBSCRIBE server, NOTIFY client and REFER server * * Simpler event server. See nua_event_server.c for more complex event * server. * * @author Pekka Pessi * * @date Created: Wed Mar 8 15:10:08 EET 2006 ppessi */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nua_stack.h" /* ---------------------------------------------------------------------- */ /* Notifier event usage */ struct notifier_usage { enum nua_substate nu_substate; /**< Subscription state */ sip_time_t nu_expires; /**< Expiration time */ sip_time_t nu_requested; /**< Requested expiration time */ #if SU_HAVE_EXPERIMENTAL char *nu_tag; /**< @ETag in last NOTIFY */ unsigned nu_etags:1; /**< Subscriber supports etags */ unsigned nu_appl_etags:1; /**< Application generates etags */ unsigned nu_no_body:1; /**< Suppress body */ #endif }; static char const *nua_notify_usage_name(nua_dialog_usage_t const *du); static int nua_notify_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du); static void nua_notify_usage_remove(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, nua_client_request_t *cr, nua_server_request_t *sr); static void nua_notify_usage_refresh(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now); static int nua_notify_usage_shutdown(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du); static nua_usage_class const nua_notify_usage[1] = { { sizeof (struct notifier_usage), (sizeof nua_notify_usage), nua_notify_usage_add, nua_notify_usage_remove, nua_notify_usage_name, nua_base_usage_update_params, NULL, nua_notify_usage_refresh, nua_notify_usage_shutdown, }}; static char const *nua_notify_usage_name(nua_dialog_usage_t const *du) { return "notify"; } static int nua_notify_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { ds->ds_has_events++; ds->ds_has_notifys++; return 0; } static void nua_notify_usage_remove(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, nua_client_request_t *cr, nua_server_request_t *sr) { ds->ds_has_events--; ds->ds_has_notifys--; } /* ====================================================================== */ /* SUBSCRIBE server */ /** @NUA_EVENT nua_i_subscribe * * Incoming @b SUBSCRIBE request. * * @b SUBSCRIBE request is used to query SIP event state or establish a SIP * event subscription. * * @param status status code of response sent automatically by stack * @param phrase response phrase sent automatically by stack * @param nh operation handle associated with the incoming request * @param hmagic application context associated with the handle * (NULL when handle is created by the stack) * @param sip SUBSCRIBE request headers * @param tags NUTAG_SUBSTATE() * * Initial SUBSCRIBE requests are dropped with 489 Bad Event * response, unless the application has explicitly included the @Event in * the list of allowed events with nua_set_params() tag NUTAG_ALLOW_EVENTS() * (or SIPTAG_ALLOW_EVENTS() or SIPTAG_ALLOW_EVENTS_STR()). * * If the event has been allowed the application * can decide whether to accept the SUBSCRIBE request or reject it. The * nua_response() call responding to a SUBSCRIBE request must have * NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag. * * If the application accepts the SUBSCRIBE request, it must immediately * send an initial NOTIFY establishing the dialog. This is because the * response to the SUBSCRIBE request may be lost by an intermediate proxy * because it had forked the SUBSCRIBE request. * * SUBSCRIBE requests modifying (usually refreshing or terminating) an * existing event subscription are accepted by default and a 200 OK * response along with a copy of previously sent NOTIFY is sent * automatically to the subscriber. * * By default, only event subscriptions accepted are those created * implicitly by REFER request. See #nua_i_refer how the application must * handle the REFER requests. * * @par Subscription Lifetime and Terminating Subscriptions * * Accepting the SUBSCRIBE request creates a dialog with a notifier * dialog usage on the handle. The dialog usage is active, until the * subscriber terminates the subscription, it times out or the application * terminates the usage with nua_notify() call containing the tag * NUTAG_SUBSTATE(nua_substate_terminated) or @SubscriptionState header with * state "terminated" and/or expiration time 0. * * When the subscriber terminates the subscription, the application is * notified of an termination by a #nua_i_subscribe event with * NUTAG_SUBSTATE(nua_substate_terminated) tag. When the subscription times * out, nua automatically initiates a NOTIFY transaction. When it is * terminated, the application is sent a #nua_r_notify event with * NUTAG_SUBSTATE(nua_substate_terminated) tag. * * @sa @RFC3265, nua_notify(), NUTAG_SUBSTATE(), @SubscriptionState, * @Event, nua_subscribe(), #nua_r_subscribe, #nua_i_refer, nua_refer() * * @END_NUA_EVENT */ static int nua_subscribe_server_init(nua_server_request_t *sr); static int nua_subscribe_server_preprocess(nua_server_request_t *sr); static int nua_subscribe_server_respond(nua_server_request_t*, tagi_t const *); static int nua_subscribe_server_report(nua_server_request_t*, tagi_t const *); nua_server_methods_t const nua_subscribe_server_methods = { SIP_METHOD_SUBSCRIBE, nua_i_subscribe, /* Event */ { 1, /* Create dialog */ 0, /* Initial request */ 1, /* Target refresh request */ 1, /* Add Contact */ }, nua_subscribe_server_init, nua_subscribe_server_preprocess, nua_base_server_params, nua_subscribe_server_respond, nua_subscribe_server_report, }; int nua_subscribe_server_init(nua_server_request_t *sr) { nua_handle_t *nh = sr->sr_owner; nua_dialog_state_t *ds = nh->nh_ds; sip_allow_events_t const *allow_events = NH_PGET(nh, allow_events); sip_t const *sip = sr->sr_request.sip; sip_event_t *o = sip->sip_event; char const *event = o ? o->o_type : NULL; if (sr->sr_initial || !nua_dialog_usage_get(ds, nua_notify_usage, o)) { if (event && str0cmp(event, "refer") == 0) /* refer event subscription should be initiated with REFER */ return SR_STATUS1(sr, SIP_403_FORBIDDEN); /* XXX - event is case-sensitive, should use msg_header_find_item() */ if (!event || !msg_header_find_param(allow_events->k_common, event)) return SR_STATUS1(sr, SIP_489_BAD_EVENT); } return 0; } int nua_subscribe_server_preprocess(nua_server_request_t *sr) { nua_handle_t *nh = sr->sr_owner; nua_dialog_state_t *ds = nh->nh_ds; nua_dialog_usage_t *du; struct notifier_usage *nu; sip_t const *sip = sr->sr_request.sip; sip_event_t *o = sip->sip_event; char const *event = o ? o->o_type : NULL; /* Maximum expiration time */ unsigned long expires = sip->sip_expires ? sip->sip_expires->ex_delta : 3600; sip_time_t now = sip_now(); assert(nh && nh->nh_nua->nua_dhandle != nh); du = nua_dialog_usage_get(ds, nua_notify_usage, o); if (du == NULL) { /* Create a new subscription */ du = nua_dialog_usage_add(nh, ds, nua_notify_usage, o); if (du == NULL) return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR); } else { /* Refresh existing subscription */ if (str0cmp(event, "refer") == 0) expires = NH_PGET(nh, refer_expires); SR_STATUS1(sr, SIP_200_OK); } nu = nua_dialog_usage_private(du); if (now + expires >= now) nu->nu_requested = now + expires; else nu->nu_requested = SIP_TIME_MAX - 1; #if SU_HAVE_EXPERIMENTAL nu->nu_etags = sip_suppress_body_if_match(sip) || sip_suppress_notify_if_match(sip) || sip_has_feature(sr->sr_request.sip->sip_supported, "etags"); #endif sr->sr_usage = du; return sr->sr_status <= 100 ? 0 : sr->sr_status; } /** @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); } static int nua_subscribe_server_report(nua_server_request_t *sr, tagi_t const *tags) { nua_handle_t *nh = sr->sr_owner; nua_dialog_state_t *ds = nh->nh_ds; nua_dialog_usage_t *du = sr->sr_usage; struct notifier_usage *nu = nua_dialog_usage_private(du); enum nua_substate substate = nua_substate_terminated; int notify = 0; int retval; if (nu && !sr->sr_terminating) { substate = nu->nu_substate; } /* nu_requested is set by SUBSCRIBE and cleared when NOTIFY is sent */ if (nu && nu->nu_requested && substate != nua_substate_embryonic) { #if SU_HAVE_EXPERIMENTAL sip_t const *sip = sr->sr_request.sip; sip_suppress_notify_if_match_t *snim = sip_suppress_notify_if_match(sip); sip_suppress_body_if_match_t *sbim = sip_suppress_body_if_match(sip); if (!nu->nu_tag) notify = 1; else if (snim && !strcasecmp(snim->snim_tag, nu->nu_tag)) notify = 0; else if (sbim && !strcasecmp(snim->snim_tag, nu->nu_tag)) notify = 1, nu->nu_no_body = 1; else #endif notify = 1; notify = notify && du->du_cr != NULL; } retval = nua_base_server_treport(sr, NUTAG_SUBSTATE(substate), TAG_END()); if (retval >= 2 || du == NULL) return retval; if (notify) { /* Send NOTIFY (and terminate subscription, when needed) */ nua_dialog_usage_refresh(nh, ds, du, sip_now()); } return retval; } /* ======================================================================== */ /* NOTIFY client */ /**@fn void nua_notify(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...); * * Send a SIP NOTIFY request message. * * This function is used when the application implements itself the * notifier. The application must provide valid @SubscriptionState and * @Event headers using SIP tags. The subscription state can be modified * with NUTAG_SUBSTATE(), however, its effect is overriden by * @SubscriptionState header included in the nua_notify() tags. * * @bug If the @Event is not given by application, stack uses the @Event * header from the first subscription usage on handle. * * If there is no active notifier dialog usage or no notifier dialog * usage matches the @Event header given by the application the nua_notify() * request is rejected locally by the stack with status code 481. The local * rejection can be bypassed if NUTAG_NEWSUB(1) is included in tags. * * Please note that including NUTAG_NEWSUB(1) in nua_notify() tags if there * is a valid subscription may lead to an extra NOTIFY sent to subscriber if * the subscription had been terminated by the subscriber or by a timeout * before the nua_notify() is processed. * * @param nh Pointer to operation handle * @param tag, value, ... List of tagged parameters * * @return * nothing * * @par Related Tags: * NUTAG_SUBSTATE() \n * NUTAG_NEWSUB() \n * Tags of nua_set_hparams() \n * Header tags defined in * * @par Events: * #nua_r_notify * * @sa @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_ALLOW_EVENTS() */ static int nua_notify_client_init(nua_client_request_t *cr, msg_t *, sip_t *, tagi_t const *tags); static int nua_notify_client_init_etag(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags); static int nua_notify_client_request(nua_client_request_t *cr, msg_t *, sip_t *, tagi_t const *tags); static int nua_notify_client_report(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip, nta_outgoing_t *orq, tagi_t const *tags); static nua_client_methods_t const nua_notify_client_methods = { SIP_METHOD_NOTIFY, /* crm_method, crm_method_name */ 0, /* crm_extra */ { /* crm_flags */ /* create_dialog */ 1, /* in_dialog */ 1, /* target refresh */ 1 }, NULL, /* crm_template */ nua_notify_client_init, /* crm_init */ nua_notify_client_request, /* crm_send */ NULL, /* crm_check_restart */ NULL, /* crm_recv */ NULL, /* crm_preliminary */ nua_notify_client_report, /* crm_report */ NULL, /* crm_complete */ }; /**@internal Send NOTIFY. */ int nua_stack_notify(nua_t *nua, nua_handle_t *nh, nua_event_t e, tagi_t const *tags) { return nua_client_create(nh, e, &nua_notify_client_methods, tags); } static int nua_notify_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; struct notifier_usage *nu; sip_event_t const *o = sip->sip_event; sip_subscription_state_t *ss = sip->sip_subscription_state; sip_time_t now = sip_now(); if (o == NULL && nh->nh_ds->ds_has_notifys == 1) o = NONE; du = nua_dialog_usage_get(nh->nh_ds, nua_notify_usage, o); if (!du) { tagi_t const *newsub = tl_find_last(tags, nutag_newsub); if (!newsub || !newsub->t_value) return 0; /* Rejected eventually by nua_notify_client_request() */ /* Create new notifier */ du = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, o); if (du == NULL) return -1; nu = nua_dialog_usage_private(du); nu->nu_expires = now; } else nu = nua_dialog_usage_private(du); if (nu->nu_substate == nua_substate_terminated) { /*Xyzzy*/; } else if (ss != NULL) { /* SIPTAG_SUBSCRIPTION_STATE() overrides NUTAG_SUBSTATE() */ nu->nu_substate = nua_substate_make(ss->ss_substate); if (ss->ss_expires) { unsigned long expires = strtoul(ss->ss_expires, NULL, 10); if (now + expires < now) expires = SIP_TIME_MAX - now - 1; /* We can change the lifetime of unsolicited subscription at will */ if (nu->nu_requested == 0) nu->nu_expires = nu->nu_requested = now + expires; /* Notifier can only shorten the subscribed time */ else if (nu->nu_requested >= now + expires) nu->nu_expires = nu->nu_requested = now + expires; } else { if (nu->nu_requested >= nu->nu_expires) nu->nu_expires = nu->nu_requested; } } else { enum nua_substate substate = nu->nu_substate; if (nu->nu_requested >= nu->nu_expires) nu->nu_expires = nu->nu_requested; if (nu->nu_expires > now) { tagi_t const *t = tl_find_last(tags, nutag_substate); if (t) substate = (enum nua_substate)t->t_value; } else substate = nua_substate_terminated; switch (substate) { case nua_substate_embryonic: /*FALLTHROUGH*/ case nua_substate_pending: nu->nu_substate = nua_substate_pending; break; case nua_substate_active: default: nu->nu_substate = nua_substate_active; break; case nua_substate_terminated: nu->nu_substate = nua_substate_terminated; break; } } cr->cr_usage = du; return nua_notify_client_init_etag(cr, msg, sip, tags); } static int nua_notify_client_init_etag(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { #if SU_HAVE_EXPERIMENTAL nua_handle_t *nh = cr->cr_owner; struct notifier_usage *nu = nua_dialog_usage_private(cr->cr_usage); nua_server_request_t *sr; if (nu->nu_tag) su_free(nh->nh_home, nu->nu_tag), nu->nu_tag = NULL; nu->nu_no_body = 0; if (sip->sip_etag) { nu->nu_appl_etags = 1; nu->nu_tag = su_strdup(nh->nh_home, sip->sip_etag->g_string); } else if (!nu->nu_appl_etags && nu->nu_etags) { su_md5_t md5[1]; unsigned char digest[SU_MD5_DIGEST_SIZE]; sip_payload_t pl[1] = { SIP_PAYLOAD_INIT() }; char token[2 * 16]; su_md5_init(md5); if (sip->sip_payload) *pl = *sip->sip_payload; if (pl->pl_len) su_md5_update(md5, pl->pl_data, pl->pl_len); su_md5_update(md5, &pl->pl_len, sizeof(pl->pl_len)); if (sip->sip_content_type) su_md5_striupdate(md5, sip->sip_content_type->c_type); su_md5_digest(md5, digest); token64_e(token, sizeof token, digest, sizeof digest); token[(sizeof token) - 1] = '\0'; nu->nu_tag = su_strdup(nh->nh_home, token); } if (!nu->nu_requested || !nu->nu_tag) return 0; /* Check if SUBSCRIBE had matching suppression */ for (sr = nh->nh_ds->ds_sr; sr; sr = sr->sr_next) if (sr->sr_usage == cr->cr_usage && sr->sr_method == sip_method_subscribe) break; if (sr) { sip_t const *sip = sr->sr_request.sip; sip_suppress_body_if_match_t *sbim; sip_suppress_notify_if_match_t *snim; if (cr->cr_usage->du_ready) { snim = sip_suppress_notify_if_match(sip); if (snim && !strcasecmp(snim->snim_tag, nu->nu_tag)) { if (nu->nu_requested > nu->nu_expires) nu->nu_expires = nu->nu_requested; nu->nu_requested = 0; return nua_client_return(cr, 202, "NOTIFY Suppressed", msg); } } sbim = sip_suppress_body_if_match(sip); if (sbim && !strcasecmp(sbim->sbim_tag, nu->nu_tag)) nu->nu_no_body = 1; } #endif return 0; } 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 ? "noresource" : "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); } /** @NUA_EVENT nua_r_notify * * Response to an outgoing @b NOTIFY request. * * The @b NOTIFY may be sent explicitly by nua_notify() or implicitly by NUA * state machine. Implicit @b NOTIFY is sent when an established dialog is * refreshed by client or it is terminated (either by client or because of a * timeout). * * The current subscription state is included in NUTAG_SUBSTATE() tag. The * nua_substate_terminated indicates that the subscription is terminated, * the notifier usage has been removed and when there was no other usages of * the dialog the dialog state is also removed. * * @param status response status code * (if the request is retried, @a status is 100, the @a * sip->sip_status->st_status contain the real status code * from the response message, e.g., 302, 401, or 407) * @param phrase a short textual description of @a status code * @param nh operation handle associated with the subscription * @param hmagic application context associated with the handle * @param sip response to @b NOTIFY request or NULL upon an error * (status code is in @a status and * descriptive message in @a phrase parameters) * @param tags NUTAG_SUBSTATE() indicating subscription state * SIPTAG_EVENT() indicating subscription event * * @sa nua_notify(), @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_SUBSTATE() * * @END_NUA_EVENT */ static int nua_notify_client_report(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip, nta_outgoing_t *orq, tagi_t const *tags) { nua_handle_t *nh = cr->cr_owner; nua_dialog_usage_t *du = cr->cr_usage; struct notifier_usage *nu = nua_dialog_usage_private(du); enum nua_substate substate = nua_substate_terminated; if (nu && !cr->cr_terminated) substate = nu->nu_substate; nua_stack_tevent(nh->nh_nua, nh, nta_outgoing_getresponse(orq), cr->cr_event, status, phrase, NUTAG_SUBSTATE(substate), SIPTAG_EVENT(du ? du->du_event : NULL), TAG_NEXT(tags)); if (du && du->du_cr == cr && !cr->cr_terminated) { if (nu->nu_requested) { /* Re-SUBSCRIBEd while NOTIFY was in progress, resend NOTIFY */ nua_client_resend_request(cr, 0); } else if (nu->nu_expires) { nua_dialog_usage_set_refresh_at(du, nu->nu_expires); } } return 0; } static void nua_notify_usage_refresh(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now) { struct notifier_usage *nu = nua_dialog_usage_private(du); nua_client_request_t *cr = du->du_cr; nua_event_t e = nua_r_notify; if (cr) { int terminating = 0; if (nu->nu_expires && nu->nu_expires <= now) terminating = 1; else if (nu->nu_requested && nu->nu_requested <= now) terminating = 1; if (nua_client_resend_request(cr, terminating) >= 0) return; } else { if (nua_client_create(nh, e, &nua_notify_client_methods, NULL) >= 0) return; } nua_stack_tevent(nh->nh_nua, nh, NULL, e, NUA_ERROR_AT(__FILE__, __LINE__), NUTAG_SUBSTATE(nua_substate_terminated), TAG_END()); nua_dialog_usage_remove(nh, ds, du, NULL, NULL); } /** @interal Shut down NOTIFY usage. * * @retval >0 shutdown done * @retval 0 shutdown in progress * @retval <0 try again later */ static int nua_notify_usage_shutdown(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { struct notifier_usage *nu = nua_dialog_usage_private(du); nua_client_request_t *cr = du->du_cr; nu->nu_substate = nua_substate_terminated; if (cr) { if (nua_client_resend_request(cr, 1) >= 0) return 0; } else { if (nua_client_tcreate(nh, nua_r_notify, &nua_notify_client_methods, SIPTAG_EVENT(du->du_event), NUTAG_SUBSTATE(nua_substate_terminated), TAG_END()) >= 0) return 0; } nua_dialog_usage_remove(nh, ds, du, NULL, NULL); return 200; } /* ======================================================================== */ /* REFER */ /* RFC 3515 */ static int nua_refer_server_init(nua_server_request_t *sr); static int nua_refer_server_preprocess(nua_server_request_t *sr); static int nua_refer_server_respond(nua_server_request_t*, tagi_t const *); static int nua_refer_server_report(nua_server_request_t*, tagi_t const *); nua_server_methods_t const nua_refer_server_methods = { SIP_METHOD_REFER, nua_i_refer, /* Event */ { 1, /* Create dialog */ 0, /* Initial request */ 1, /* Target refresh request */ 1, /* Add Contact */ }, nua_refer_server_init, nua_refer_server_preprocess, nua_base_server_params, nua_refer_server_respond, nua_refer_server_report, }; static int nua_refer_server_init(nua_server_request_t *sr) { return 0; } static int nua_refer_server_preprocess(nua_server_request_t *sr) { nua_handle_t *nh = sr->sr_owner; sip_t const *sip = sr->sr_request.sip; struct notifier_usage *nu; sip_event_t *o; if (nh->nh_ds->ds_got_referrals || NH_PGET(nh, refer_with_id)) o = sip_event_format(nh->nh_home, "refer;id=%u", sip->sip_cseq->cs_seq); else o = sip_event_make(nh->nh_home, "refer"); if (o) { sr->sr_usage = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, o); msg_header_free(nh->nh_home, (msg_header_t *)o); } if (!sr->sr_usage) return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR); nu = nua_dialog_usage_private(sr->sr_usage); nu->nu_requested = sip_now() + NH_PGET(nh, refer_expires); return 0; } static int nua_refer_server_respond(nua_server_request_t *sr, tagi_t const *tags) { nua_handle_t *nh = sr->sr_owner; struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage); sip_refer_sub_t const *rs = sip_refer_sub(sr->sr_response.sip); if (sr->sr_status < 200 || nu == NULL) { } else if (sr->sr_status < 300 && /* Application included Refer-Sub: false in response */ (rs == NULL || str0casecmp("false", rs->rs_value))) { sr->sr_usage->du_ready = 1; nu->nu_expires = sip_now() + NH_PGET(nh, refer_expires); if (sr->sr_application) /* Application responded to REFER */ nu->nu_substate = nua_substate_active; } else { /* Destroy the implicit subscription usage */ sr->sr_terminating = 1; } return nua_base_server_respond(sr, tags); } /** @NUA_EVENT nua_i_refer * * Incoming @b REFER request used to transfer calls. The tag list will * contain tag NUTAG_REFER_EVENT() with the @Event header constructed from * the REFER request. It will also contain the SIPTAG_REFERRED_BY() tag with * the @ReferredBy header containing the identity of the party sending the * REFER. The @ReferredBy structure contained in the tag is constructed from * the @From header if the @ReferredBy header was not present in the REFER * request. * * The application can let the nua to send NOTIFYs from the call it * initiates with nua_invite() if it includes in the nua_invite() arguments * both the NUTAG_NOTIFY_REFER() with the handle with which nua_i_refer was * received and the NUTAG_REFER_EVENT() from #nua_i_refer event tags. * * @param status status code of response sent automatically by stack * @param phrase a short textual description of @a status code * @param nh operation handle associated with the incoming request * @param hmagic application context associated with the handle * (NULL if outside of an already established session) * @param sip incoming REFER request * @param tags NUTAG_REFER_EVENT() \n * SIPTAG_REFERRED_BY() * * @sa nua_refer(), #nua_r_refer, @ReferTo, NUTAG_REFER_EVENT(), * SIPTAG_REFERRED_BY(), @ReferredBy, NUTAG_NOTIFY_REFER(), * NUTAG_REFER_WITH_ID(), @RFC3515. * * @END_NUA_EVENT */ static int nua_refer_server_report(nua_server_request_t *sr, tagi_t const *tags) { nua_handle_t *nh = sr->sr_owner; struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage); sip_t const *sip = sr->sr_request.sip; sip_referred_by_t *by = sip->sip_referred_by, default_by[1]; sip_event_t const *o = sr->sr_usage->du_event; enum nua_substate substate = nua_substate_terminated; int initial = sr->sr_initial, retval; if (nu) { if (!sr->sr_terminating) substate = nu->nu_substate; } if (by == NULL) { by = sip_referred_by_init(default_by); by->b_display = sip->sip_from->a_display; *by->b_url = *sip->sip_from->a_url; } retval = nua_base_server_treport(sr, NUTAG_SUBSTATE(substate), NUTAG_REFER_EVENT(o), TAG_IF(by, SIPTAG_REFERRED_BY(by)), TAG_END()); if (retval >= 2 || nu == NULL) return retval; if (initial) nua_stack_post_signal(nh, nua_r_notify, SIPTAG_EVENT(o), SIPTAG_CONTENT_TYPE_STR("message/sipfrag"), SIPTAG_PAYLOAD_STR("SIP/2.0 100 Trying\r\n"), TAG_END()); return retval; }