/* * 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 #define NTA_LEG_MAGIC_T struct nua_handle_s #define NTA_OUTGOING_MAGIC_T struct nua_handle_s #include "nua_stack.h" /* ---------------------------------------------------------------------- */ /* Notifier event usage */ struct notifier_usage { enum nua_substate nu_substate; /**< Subscription state */ sip_time_t nu_expires; }; 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); 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, 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) { ds->ds_has_events--; ds->ds_has_notifys--; } /* ====================================================================== */ /* SUBSCRIBE server */ static int respond_to_subscribe(nua_server_request_t *sr, tagi_t const *tags); /** @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. * * 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()). 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_CURRENT()/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 because the SUBSCRIBE * request was forked by an intermediate proxy. * * 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. * * 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. * * @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() * * @sa @RFC3265, nua_notify(), NUTAG_SUBSTATE(), @SubscriptionState, * @Event, nua_subscribe(), #nua_r_subscribe, #nua_i_refer, nua_refer() * * @END_NUA_EVENT */ /** @internal Process incoming SUBSCRIBE. */ int nua_stack_process_subscribe(nua_t *nua, nua_handle_t *nh, nta_incoming_t *irq, sip_t const *sip) { nua_server_request_t *sr, sr0[1]; nua_dialog_state_t *ds; nua_dialog_usage_t *du = NULL; sip_event_t *o = sip->sip_event; char const *event = o ? o->o_type : NULL; enum nua_substate substate = nua_substate_terminated; enter; if (nh) du = nua_dialog_usage_get(ds = nh->nh_ds, nua_notify_usage, o); sr = SR_INIT(sr0); if (nh == NULL || du == NULL) { sip_allow_events_t *allow_events = NUA_PGET(nua, nh, allow_events); if (event && str0cmp(event, "refer") == 0) /* refer event subscription should be initiated with REFER */ SR_STATUS1(sr, SIP_403_FORBIDDEN); else if (!event || !msg_header_find_param(allow_events->k_common, event)) SR_STATUS1(sr, SIP_489_BAD_EVENT); else substate = nua_substate_embryonic; } else { /* Refresh existing subscription */ struct notifier_usage *nu = nua_dialog_usage_private(du); unsigned long expires; assert(nh && du && nu); expires = str0cmp(event, "refer") ? 3600 : NH_PGET(nh, refer_expires); if (sip->sip_expires && sip->sip_expires->ex_delta < expires) expires = sip->sip_expires->ex_delta; if (expires == 0) nu->nu_substate = nua_substate_terminated; nu->nu_expires = sip_now() + expires; substate = nu->nu_substate; /* XXX - send notify */ SR_STATUS1(sr, SIP_200_OK); } sr = nua_server_request(nua, nh, irq, sip, sr, sizeof *sr, respond_to_subscribe, 1); if (!du && substate == nua_substate_embryonic && sr->sr_status < 300) { nh = sr->sr_owner; assert(nh && nh != nua->nua_dhandle); du = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, sip->sip_event); if (du) { struct notifier_usage *nu = nua_dialog_usage_private(du); unsigned long expires = 3600; /* XXX */ if (sip->sip_expires && sip->sip_expires->ex_delta < expires) expires = sip->sip_expires->ex_delta; nu->nu_expires = sip_now() + expires; nu->nu_substate = substate; } else SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR); } if (substate == nua_substate_embryonic && sr->sr_status >= 300) substate = nua_substate_terminated; sr->sr_usage = du; return nua_stack_server_event(nua, sr, nua_i_subscribe, NUTAG_SUBSTATE(substate), TAG_END()); } /** @internal Respond to an SUBSCRIBE request. * */ static int respond_to_subscribe(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_t *nua = nh->nh_nua; struct notifier_usage *nu; sip_allow_events_t *allow_events = NUA_PGET(nua, nh, allow_events); sip_expires_t ex[1]; sip_time_t now = sip_now(); msg_t *msg; sip_expires_init(ex); nu = nua_dialog_usage_private(sr->sr_usage); if (nu && nu->nu_expires > now) ex->ex_delta = nu->nu_expires - now; msg = nua_server_response(sr, sr->sr_status, sr->sr_phrase, NUTAG_ADD_CONTACT(sr->sr_status < 300), TAG_IF(nu, SIPTAG_EXPIRES(ex)), SIPTAG_SUPPORTED(NH_PGET(nh, supported)), SIPTAG_ALLOW_EVENTS(allow_events), TAG_NEXT(tags)); if (msg) { sip_t *sip = sip_object(msg); if (nu && sip->sip_expires && sr->sr_status < 300) nu->nu_expires = now + sip->sip_expires->ex_delta; nta_incoming_mreply(sr->sr_irq, msg); if (nu && nu->nu_substate != nua_substate_embryonic) /* Send NOTIFY (and terminate subscription, when needed) */ nua_dialog_usage_refresh(nh, ds, sr->sr_usage, sip_now()); } else { /* XXX - send nua_i_error */ SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR); nta_incoming_treply(sr->sr_irq, sr->sr_status, sr->sr_phrase, TAG_END()); } return sr->sr_status >= 200 ? sr->sr_status : 0; } /* ======================================================================== */ /* NOTIFY */ static int process_response_to_notify(nua_handle_t *nh, nta_outgoing_t *orq, sip_t const *sip); static int nua_stack_notify2(nua_t *, nua_handle_t *, nua_event_t, nua_dialog_usage_t *du, tagi_t const *tags); /**@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. If there is no @SubscriptionState header, * the subscription state can be modified with NUTAG_SUBSTATE(). * * @bug If the @Event is not given by application, stack uses the @Event * header from the first subscription usage on handle. * * @param nh Pointer to operation handle * @param tag, value, ... List of tagged parameters * * @return * nothing * * @par Related Tags: * NUTAG_SUBSTATE() \n * Tags of nua_set_hparams() \n * Tags in * * @par Events: * #nua_r_notify * * @sa @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_ALLOW_EVENTS() */ /**@internal Send NOTIFY. */ int nua_stack_notify(nua_t *nua, nua_handle_t *nh, nua_event_t e, tagi_t const *tags) { return nua_stack_notify2(nua, nh, e, NULL, tags); } int nua_stack_notify2(nua_t *nua, nua_handle_t *nh, nua_event_t e, nua_dialog_usage_t *du, tagi_t const *tags) { nua_client_request_t *cr = nh->nh_ds->ds_cr; struct notifier_usage *nu; msg_t *msg; sip_t *sip; sip_event_t const *o; sip_time_t now; int refresh = du != NULL; if (cr->cr_orq) { return UA_EVENT2(e, 900, "Request already in progress"); } nua_stack_init_handle(nua, nh, TAG_NEXT(tags)); if (refresh) { assert(!cr->cr_msg); if (cr->cr_msg) msg_destroy(cr->cr_msg); cr->cr_msg = msg_copy(du->du_msg); } msg = nua_creq_msg(nua, nh, cr, cr->cr_retry_count || refresh, SIP_METHOD_NOTIFY, NUTAG_ADD_CONTACT(1), TAG_NEXT(tags)); sip = sip_object(msg); if (!sip) return UA_EVENT1(e, NUA_INTERNAL_ERROR); if (nh->nh_ds->ds_has_notifys == 1 && !sip->sip_event) o = NONE; else o = sip->sip_event; du = nua_dialog_usage_get(nh->nh_ds, nua_notify_usage, o); nu = nua_dialog_usage_private(du); if (du && du->du_event && !sip->sip_event) sip_add_dup(msg, sip, (sip_header_t *)du->du_event); now = sip_now(); if (!du) ; else if (sip->sip_subscription_state) { /* SIPTAG_SUBSCRIPTION_STATE() overrides NUTAG_SUBSTATE() */ char const *ss_substate = sip->sip_subscription_state->ss_substate; if (strcasecmp(ss_substate, "terminated") == 0) nu->nu_substate = nua_substate_terminated; else if (strcasecmp(ss_substate, "pending") == 0) nu->nu_substate = nua_substate_pending; else /* if (strcasecmp(subs->ss_substate, "active") == 0) */ nu->nu_substate = nua_substate_active; if (sip->sip_subscription_state->ss_expires) { unsigned long expires; expires = strtoul(sip->sip_subscription_state->ss_expires, NULL, 10); if (expires > 3600) expires = 3600; nu->nu_expires = now + expires; } else if (nu->nu_substate != nua_substate_terminated) { sip_subscription_state_t *ss = sip->sip_subscription_state; char *param; if (now < nu->nu_expires) param = su_sprintf(msg_home(msg), "expires=%lu", nu->nu_expires - now); else param = "expires=0"; msg_header_add_param(msg_home(msg), ss->ss_common, param); } } else { sip_subscription_state_t *ss; enum nua_substate substate; char const *name; substate = nu->nu_substate; if (nu->nu_expires <= now) substate = nua_substate_terminated; if (substate != nua_substate_terminated) { tagi_t const *t = tl_find_last(tags, nutag_substate); if (t) substate = (enum nua_substate)t->t_value; } switch (substate) { case nua_substate_embryonic: /*FALLTHROUGH*/ case nua_substate_pending: name = "pending"; nu->nu_substate = nua_substate_pending; break; case nua_substate_active: default: name = "active"; nu->nu_substate = nua_substate_active; break; case nua_substate_terminated: name = "terminated"; nu->nu_substate = nua_substate_terminated; break; } if (nu->nu_substate != nua_substate_terminated) { unsigned long expires = nu->nu_expires - now; ss = sip_subscription_state_format(msg_home(msg), "%s;expires=%lu", name, expires); } else { ss = sip_subscription_state_make(msg_home(msg), "terminated; " "reason=noresource"); } msg_header_insert(msg, (void *)sip, (void *)ss); } if (du) { if (nu->nu_substate == nua_substate_terminated) du->du_terminating = 1; if (!du->du_terminating && !refresh) { /* Save template */ if (du->du_msg) msg_destroy(du->du_msg); du->du_msg = msg_ref_create(cr->cr_msg); } } /* NOTIFY outside a dialog */ cr->cr_orq = nta_outgoing_mcreate(nua->nua_nta, process_response_to_notify, nh, NULL, msg, SIPTAG_END(), TAG_NEXT(tags)); if (!cr->cr_orq) { msg_destroy(msg); return UA_EVENT1(e, NUA_INTERNAL_ERROR); } cr->cr_usage = du; return cr->cr_event = e; } static void restart_notify(nua_handle_t *nh, tagi_t *tags) { nua_creq_restart(nh, nh->nh_ds->ds_cr, process_response_to_notify, 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) * * @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 * * @sa nua_notify(), @RFC3265, #nua_i_subscribe, #nua_i_refer * * @END_NUA_EVENT */ static int process_response_to_notify(nua_handle_t *nh, nta_outgoing_t *orq, sip_t const *sip) { enum nua_substate substate = nua_substate_terminated; if (nua_creq_check_restart(nh, nh->nh_ds->ds_cr, orq, sip, restart_notify)) return 0; if (nh->nh_ds->ds_cr->cr_usage) { struct notifier_usage *nu = nua_dialog_usage_private(nh->nh_ds->ds_cr->cr_usage); substate = nu->nu_substate; assert(substate != nua_substate_embryonic); } return nua_stack_process_response(nh, nh->nh_ds->ds_cr, orq, sip, NUTAG_SUBSTATE(substate), TAG_END()); } 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); if (nh->nh_ds->ds_cr->cr_usage == du) /* Already notifying. */ return; if (now >= nu->nu_expires) { sip_subscription_state_t ss[1]; char const *params[] = { NULL, NULL }; tagi_t tags[2] = { { SIPTAG_SUBSCRIPTION_STATE(ss) }, { TAG_END() } }; sip_subscription_state_init(ss); ss->ss_substate = "terminated"; ss->ss_params = params; params[0] = "reason=timeout"; ss->ss_reason = "timeout"; nua_stack_notify2(nh->nh_nua, nh, nua_r_notify, du, tags); } else { nua_stack_notify2(nh->nh_nua, nh, nua_r_notify, du, 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) { nua_client_request_t *cr = nh->nh_ds->ds_cr; if (!cr->cr_usage) { /* Unnotify */ /* Commenting this line out to supress an attended transfer bug (awaiting fix from pessi) */ //nua_stack_notify2(nh->nh_nua, nh, nua_r_destroy, du, NULL); return cr->cr_usage != du; } if (!du->du_ready && !cr->cr_orq) return 1; /* Unauthenticated NOTIFY? */ return -1; /* Request in progress */ } /* ======================================================================== */ /* REFER */ /* RFC 3515 */ /** @NUA_EVENT nua_i_refer * * Incoming @b REFER request used to transfer calls. * * @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 */ /** @internal Process incoming REFER. */ int nua_stack_process_refer(nua_t *nua, nua_handle_t *nh, nta_incoming_t *irq, sip_t const *sip) { nua_dialog_usage_t *du = NULL; struct notifier_usage *nu; sip_event_t *event; sip_referred_by_t *by = NULL, default_by[1]; msg_t *response; sip_time_t expires; int created = 0; if (nh == NULL) { if (!(nh = nua_stack_incoming_handle(nua, irq, sip, 1))) return 500; created = 1; } if (nh->nh_ds->ds_has_referrals || NH_PGET(nh, refer_with_id)) event = sip_event_format(nh->nh_home, "refer;id=%u", sip->sip_cseq->cs_seq); else event = sip_event_make(nh->nh_home, "refer"); if (event) du = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, event); if (!du || du->du_ready) { if (du->du_ready) { SU_DEBUG_1(("nua(%p): REFER with existing refer;id=%u\n", nh, sip->sip_cseq->cs_seq)); } if (created) nh_destroy(nua, nh); return 500; } nu = nua_dialog_usage_private(du); du->du_ready = 1; nh->nh_ds->ds_has_referrals = 1; nua_dialog_uas_route(nh, nh->nh_ds, sip, 1); /* Set route and tags */ if (!sip->sip_referred_by) { sip_from_t *a = sip->sip_from; sip_referred_by_init(by = default_by); *by->b_url = *a->a_url; by->b_display = a->a_display; } response = nh_make_response(nua, nh, irq, SIP_202_ACCEPTED, NUTAG_ADD_CONTACT(1), TAG_END()); nta_incoming_mreply(irq, response); expires = NH_PGET(nh, refer_expires); if (sip->sip_expires && sip->sip_expires->ex_delta < expires) expires = sip->sip_expires->ex_delta; nu->nu_substate = nua_substate_pending; nu->nu_expires = sip_now() + expires; /* Immediate notify in order to establish the dialog */ if (!sip->sip_to->a_tag) nua_stack_post_signal(nh, nua_r_notify, SIPTAG_EVENT(event), SIPTAG_CONTENT_TYPE_STR("message/sipfrag"), SIPTAG_PAYLOAD_STR("SIP/2.0 100 Trying\r\n"), TAG_END()); nua_stack_event(nh->nh_nua, nh, nta_incoming_getrequest(irq), nua_i_refer, SIP_202_ACCEPTED, NUTAG_REFER_EVENT(event), TAG_IF(by, SIPTAG_REFERRED_BY(by)), TAG_END()); su_free(nh->nh_home, event); return 500; }