/* * 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_register.c * @brief REGISTER and registrations * * @author Pekka Pessi * @author Martti Mela * @author Kai Vehmanen * * @date Created: Wed Mar 8 11:48:49 EET 2006 ppessi */ #include "config.h" /** @internal SU network changed detector argument pointer type */ #define SU_NETWORK_CHANGED_MAGIC_T struct nua_s #define TP_CLIENT_T struct register_usage #include #include #include #include #include #include #include #define NTA_UPDATE_MAGIC_T struct nua_s #include "nua_stack.h" #include #include #include #include #define OUTBOUND_OWNER_T struct nua_handle_s #include "outbound.h" #if HAVE_SIGCOMP #include #endif #include #include #include #include #include /* ======================================================================== */ /* Registrations and contacts */ int nua_registration_from_via(nua_registration_t **list, nua_handle_t *nh, sip_via_t const *via, int public); int nua_registration_add(nua_registration_t **list, nua_registration_t *nr); void nua_registration_remove(nua_registration_t *nr); int nua_registration_set_aor(su_home_t *, nua_registration_t *nr, sip_from_t const *aor); int nua_registration_set_contact(nua_handle_t *, nua_registration_t *nr, sip_contact_t const *m, int terminating); void nua_registration_set_ready(nua_registration_t *nr, int ready); /* ====================================================================== */ /* REGISTER usage */ static char const *nua_register_usage_name(nua_dialog_usage_t const *du); static int nua_register_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du); static void nua_register_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_register_usage_update_params(nua_dialog_usage_t const *du, nua_handle_preferences_t const *, nua_handle_preferences_t const *, nua_handle_preferences_t const *); static void nua_register_usage_peer_info(nua_dialog_usage_t *du, nua_dialog_state_t const *ds, sip_t const *sip); static void nua_register_usage_refresh(nua_handle_t *, nua_dialog_state_t *, nua_dialog_usage_t *, sip_time_t); static int nua_register_usage_shutdown(nua_handle_t *, nua_dialog_state_t *, nua_dialog_usage_t *); /** @internal @brief REGISTER usage, aka nua_registration_t. */ struct register_usage { nua_registration_t *nr_next, **nr_prev, **nr_list; /* Doubly linked list and its head */ sip_from_t *nr_aor; /**< AoR for this registration, NULL if none */ sip_contact_t *nr_contact; /**< Our Contact */ sip_contact_t nr_dcontact[1]; /**< Contact in dialog */ sip_via_t *nr_via; /**< Corresponding Via headers */ unsigned long nr_min_expires; /**< Value from 423 negotiation */ /** Status of registration */ unsigned nr_ready:1; /** Kind of registration. * * If nr_default is true, this is not a real registration but placeholder * for Contact header derived from a transport address. * * If nr_secure is true, this registration supports SIPS/TLS. * * If nr_public is true, transport should have public address. */ unsigned nr_default:1, nr_secure:1, nr_public:1, nr_ip4:1, nr_ip6:1; /** Stack-generated contact */ unsigned nr_by_stack:1; unsigned:0; int nr_error_report_id; /**< ID used to ask for error reports from tport */ sip_route_t *nr_route; /**< Outgoing Service-Route */ sip_path_t *nr_path; /**< Incoming Path */ tport_t *nr_tport; /**< Transport to be used when registered */ nua_dialog_state_t *nr_dialogs; /**< List of our dialogs */ #if HAVE_SIGCOMP struct sigcomp_compartment *nr_compartment; #endif outbound_t *nr_ob; /**< Outbound connection */ }; nua_usage_class const nua_register_usage[1] = { { sizeof (struct register_usage), (sizeof nua_register_usage), nua_register_usage_add, nua_register_usage_remove, nua_register_usage_name, nua_register_usage_update_params, nua_register_usage_peer_info, nua_register_usage_refresh, nua_register_usage_shutdown }}; static char const *nua_register_usage_name(nua_dialog_usage_t const *du) { return "register"; } static int nua_register_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { nua_registration_t *nr = NUA_DIALOG_USAGE_PRIVATE(du); if (ds->ds_has_register) return -1; /* There can be only one usage */ ds->ds_has_register = 1; nr->nr_public = 1; /* */ return 0; } static void nua_register_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) { nua_registration_t *nr = NUA_DIALOG_USAGE_PRIVATE(du); if (nr->nr_list) nua_registration_remove(nr); /* Remove from list of registrations */ if (nr->nr_ob) outbound_unref(nr->nr_ob); #if HAVE_SIGCOMP if (nr->nr_compartment) sigcomp_compartment_unref(nr->nr_compartment); nr->nr_compartment = NULL; #endif if (nr->nr_error_report_id) tport_release(nr->nr_tport, nr->nr_error_report_id, NULL, NULL, nr, 0); if (nr->nr_tport) tport_unref(nr->nr_tport), nr->nr_tport = NULL; ds->ds_has_register = 0; /* There can be only one */ } /** @internal Store information about registrar. */ static void nua_register_usage_peer_info(nua_dialog_usage_t *du, nua_dialog_state_t const *ds, sip_t const *sip) { nua_registration_t *nr = NUA_DIALOG_USAGE_PRIVATE(du); if (nr->nr_ob) outbound_peer_info(nr->nr_ob, sip); } /* ======================================================================== */ /* REGISTER */ static void nua_register_connection_closed(tp_stack_t *sip_stack, nua_registration_t *nr, tport_t *tport, msg_t *msg, int error); /* Interface towards outbound_t */ sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh, su_home_t *home, int in_dialog, sip_via_t const *v, char const *transport, char const *m_param, ...); static int nua_stack_outbound_refresh(nua_handle_t *, outbound_t *ob); static int nua_stack_outbound_status(nua_handle_t *, outbound_t *ob, int status, char const *phrase, tag_type_t tag, tag_value_t value, ...); static int nua_stack_outbound_failed(nua_handle_t *, outbound_t *ob, int status, char const *phrase, tag_type_t tag, tag_value_t value, ...); static int nua_stack_outbound_credentials(nua_handle_t *, auth_client_t **auc); outbound_owner_vtable nua_stack_outbound_callbacks = { sizeof nua_stack_outbound_callbacks, /* oo_contact */ nua_handle_contact_by_via, /* oo_refresh */ nua_stack_outbound_refresh, /* oo_status */ nua_stack_outbound_status, /* oo_probe_error */ nua_stack_outbound_failed, /* oo_keepalive_error */ nua_stack_outbound_failed, /* oo_credentials */ nua_stack_outbound_credentials }; /**@fn void nua_register(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...); * * Send SIP REGISTER request to the registrar. * * Request status will be delivered to the application using #nua_r_register * event. When successful the registration will be updated periodically. * * The handle used for registration cannot be used for any other purposes. * * @param nh Pointer to operation handle * @param tag, value, ... List of tagged parameters * * @return * nothing * * @par Related tags: * NUTAG_REGISTRAR(), NUTAG_INSTANCE(), NUTAG_OUTBOUND(), * NUTAG_KEEPALIVE(), NUTAG_KEEPALIVE_STREAM(), NUTAG_M_USERNAME(), * NUTAG_M_DISPLAY(), NUTAG_M_PARAMS(), NUTAG_M_FEATURES() * * @par Events: * #nua_r_register, #nua_i_outbound * * @par Generating Contact Header * * If the application did not specify the Contact header in the tags, * nua_register() will generate one. It will obtain the schema, IP address * for the host and port number for the Contact URI from the transport * socket. The diplay name is taken from NUTAG_M_DISPLAY(), URL username * part is taken from NUTAG_M_USERNAME(), URI parameters from * NUTAG_M_PARAMS(), and Contact header parameters from NUTAG_M_FEATURES(). * If NUTAG_CALLEE_CAPS(1) is specified, additional Contact header * parameters are generated based on SDP capabilities and SIP @Allow header. * * Note that @b nua may append a identifier of its own to the @Contact URI * username. Such nua-generated identifier trailer always starts with "=" * (equal sign), rest of the nua-generated identifier may contain any * url-unreserved characters except "=". * * Likewise, nua may add transport parameters (such as "transport=tcp" or * "maddr") to the @Contact URI. It can add addtional header parameters, like * "+sip.instance" or "reg-id", too. * * For instance, if application uses tags like * @code * nua_register(nh, * NUTAG_M_DISPLAY("1"), * NUTAG_M_USERNAME("line-1"), * NUTAG_M_PARAMS("user=phone"), * NUTAG_M_FEATURES("audio"), * NUTAG_CALLEE_CAPS(0), * TAG_END()) * @endcode * @b nua can generate a Contact header like * @code * Contact: 1 * ;audio;reg-id=1 * ;+sip.instance=urn:uuid:97701ad9-39df-1229-1083-dbc0a85f029c * @endcode * * The incoming request from the proxy should contain the registered contact * URI as the request URI. The application can use the username prefix set * by NUTAG_M_USERNAME() and the non-transport parameters of the request URI * set by NUTAG_M_PARAMS() when determining to which registration the * incoming request belongs. * * For example, a request line correspoding to the @Contact in above example * may look like: * @code * INVITE sip:line-1=SSQAIbjv@192.168.1.200;user=phone SIP/2.0 * @endcode * * @sa NUTAG_M_DISPLAY(), NUTAG_M_USERNAME(), NUTAG_M_PARAMS(), * NUTAG_M_FEATURES(), NUTAG_CALLEE_CAPS(). * * @par NAT, Firewall and Outbound Support * * Normally, @b nua will start start a protocol engine for outbound * connections used for NAT and firewall traversal and connectivity checks * when registering. * * @note If the application provides @b nua with a * @Contact header of its own (or includes a SIPTAG_CONTACT(NULL) tag in * nua_register() tags), the outbound protocol engine is not started. It is * assumed that the application knows better what it is doing when it sets * the @Contact, or it is using experimental CPL upload as specified in * * draft-lennox-sip-reg-payload-01.txt. * * First, outbound engine will probe for NATs in between UA and registrar. * It will send a REGISTER request as usual. Upon receiving the response it * checks for the presence of "received" and "rport" parameters in the Via * header returned by registrar. The presence of NAT is determined from the * "received" parameter in a Via header. When a REGISTER request was sent, * the stack inserted the actual source IP address in the Via header: if * that is different from the source IP address seen by the registrar, the * registrar inserts the source IP address it sees into the "received" * parameter. * * Please note that an ALG (application-level gateway) modifying the Via * headers in outbound requests and again in incoming responses will make * the above-described NAT check to fail. * * The response to the initial REGISTER should also include option tags * indicating whether registrar supports various SIP extension options: @e * outbound, @e pref, @e path, @e gruu. * * Basically, @e outbound means that instead of registering its contact URI * with a particular address-of-record URI, the user-agent registers a * transport-level connection. Such a connection is identified on the * Contact header field with an instance identifier, application-provided * @ref NUTAG_INSTANCE() "unique string" identifying the user-agent instance * and a stack-generated numeric index identifying the transport-level * connection. * * If the @e outbound extension is supported, NUTAG_OUTBOUND() contains * option string "outbound" and the application has provided an instance * identifer to the stack with NUTAG_INSTANCE(), the nua_register() will try * to use outbound. * * If @e outbound is not supported, nua_register() has to generate a URI * that can be used to reach it from outside. It will check for public * transport addresses detected by underlying stack with, e.g., STUN, UPnP * or SOCKS. If there are public addresses, nua_register() will use them. If * there is no public address, it will try to generate a Contact URI from * the "received" and "rport" parameters found in the Via header of the * response message. * * @todo Actually generate public addresses. * * You can disable this kind of NAT traversal by setting "no-natify" into * NUTAG_OUTBOUND() options string. * * @par GRUU and Service-Route * * After a successful response to the REGISTER request has been received, * nua_register() will update the information about the registration based * on it. If there is a "gruu" parameter included in the response, * nua_register() will save it and use the gruu URI in the Contact header * fields of dialog-establishing messages, such as INVITE or SUBSCRIBE. * Also, if the registrar has included a Service-Route header in the * response, and the service route feature has not been disabled using * NUTAG_SERVICE_ROUTE_ENABLE(), the route URIs from the Service-Route * header will be used for initial non-REGISTER requests. * * The #nua_r_register message will include the contact header and route * used in with the registration. * * @par Registration Keep-Alive * * After the registration has successfully completed the nua_register() will * validate the registration and initiate the keepalive mechanism, too. The * user-agent validates the registration by sending a OPTIONS requests to * itself. If there is an error, nua_register() will indicate that to the * application using #nua_i_outbound event, and start unregistration * procedure (unless that has been explicitly disabled). * * You can disable validation by inserting "no-validate" into * NUTAG_OUTBOUND() string. * * The keepalive mechanism depends on the network features detected earlier. * If @a outbound extension is used, the STUN keepalives will be used. * Otherwise, NUA stack will repeatedly send OPTIONS requests to itself. In * order to save bandwidth, it will include Max-Forwards: 0 in the * keep-alive requests, however. The keepalive interval is determined by * NUTAG_KEEPALIVE() parameter. If the interval is 0, no keepalive messages * is sent. * * You can disable keepalive OPTIONS by inserting "no-options-keepalive" * into NUTAG_OUTBOUND() string. Currently there are no other keepalive * mechanisms available. * * The value of NUTAG_KEEPALIVE_STREAM(), if specified, is used to indicate * the desired transport-layer keepalive interval for stream-based * transports like TLS and TCP. * * As alternative to OPTIONS/STUN keepalives, the client can propose * a more frequent registration refresh interval with * NUTAG_M_FEATURES() (e.g. NUTAG_M_FEATURES("expires=120") given as * parameter to nua_register()). * * @sa #nua_r_register, nua_unregister(), #nua_r_unregister, * #nua_i_register, * @RFC3261 section 10, * @Expires, @Contact, @CallID, @CSeq, * @Path, @RFC3327, @ServiceRoute, @RFC3608, @RFC3680, * NUTAG_REGISTRAR(), NUTAG_INSTANCE(), NUTAG_OUTBOUND(), * NUTAG_KEEPALIVE(), NUTAG_KEEPALIVE_STREAM(), * SIPTAG_CONTACT(), SIPTAG_CONTACT_STR(), NUTAG_M_USERNAME(), * NUTAG_M_DISPLAY(), NUTAG_M_PARAMS(), NUTAG_M_FEATURES(), */ /** @NUA_EVENT nua_r_register * * Response to an outgoing REGISTER. * * The REGISTER may be sent explicitly by nua_register() or implicitly by * NUA state machines. * * When REGISTER request has been restarted the @a status may be 100 even * while the real response status returned is different. * * @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 registration * @param hmagic application context associated with the registration * @param sip response message to REGISTER request or NULL upon an error * (status code is in @a status and * descriptive message in @a phrase parameters) * @param tags empty * * @sa nua_register(), nua_unregister(), #nua_r_unregister, * @Contact, @CallID, @CSeq, @RFC3261 section 10, * @Path, @RFC3327, @ServiceRoute, @RFC3608, @RFC3680 * * @END_NUA_EVENT */ /**@fn void nua_unregister(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...); * Unregister. * * Send a REGISTER request with expiration time 0. This removes the * registration from the registrar. If the handle was earlier used * with nua_register() the periodic updates will be terminated. * * If a SIPTAG_CONTACT_STR() with argument "*" is used, all the * registrations will be removed from the registrar otherwise only the * contact address belonging to the NUA stack is removed. * * @param nh Pointer to operation handle * @param tag, value, ... List of tagged parameters * * @return * nothing * * @par Related tags: * NUTAG_REGISTRAR() \n * Header tags defined in except SIPTAG_EXPIRES() or SIPTAG_EXPIRES_STR() * * @par Events: * #nua_r_unregister * * @sa nua_register(), #nua_r_register, nua_handle_destroy(), nua_shutdown(), * #nua_i_register, * @Expires, @Contact, @CallID, @CSeq, @RFC3261 section 10, * @Path, @RFC3327, @ServiceRoute, @RFC3608, @RFC3680, * NUTAG_REGISTRAR(), NUTAG_INSTANCE(), NUTAG_OUTBOUND(), * NUTAG_KEEPALIVE(), NUTAG_KEEPALIVE_STREAM(), * SIPTAG_CONTACT(), SIPTAG_CONTACT_STR(), NUTAG_M_USERNAME(), * NUTAG_M_DISPLAY(), NUTAG_M_PARAMS(), NUTAG_M_FEATURES(), */ /** @NUA_EVENT nua_r_unregister * * Answer to outgoing un-REGISTER. * * @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 registration * @param hmagic application context associated with the registration * @param sip response message to REGISTER request or NULL upon an error * (status code is in @a status and * descriptive message in @a phrase parameters) * @param tags empty * * @sa nua_unregister(), nua_register(), #nua_r_register, * @Contact, @CallID, @CSeq, @RFC3261 section 10, * @Path, @RFC3327, @ServiceRoute, @RFC3608, @RFC3680 * * @END_NUA_EVENT */ static int nua_register_client_template(nua_client_request_t *cr, msg_t **return_msg, tagi_t const *tags); static int nua_register_client_init(nua_client_request_t *cr, msg_t *, sip_t *, tagi_t const *tags); static int nua_register_client_request(nua_client_request_t *cr, msg_t *, sip_t *, tagi_t const *tags); static int nua_register_client_check_restart(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip); static int nua_register_client_response(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip); static nua_client_methods_t const nua_register_client_methods = { SIP_METHOD_REGISTER, /* crm_method, crm_method_name */ 0, /* crm_extra */ { /* crm_flags */ /* create_dialog */ 1, /* in_dialog */ 0, /* target refresh */ 0 }, nua_register_client_template, /* crm_template */ nua_register_client_init, /* crm_init */ nua_register_client_request, /* crm_send */ nua_register_client_check_restart, /* crm_check_restart */ nua_register_client_response, /* crm_recv */ NULL, /* crm_preliminary */ NULL, /* crm_report */ NULL, /* crm_complete */ }; /**@internal Send REGISTER. */ int nua_stack_register(nua_t *nua, nua_handle_t *nh, nua_event_t e, tagi_t const *tags) { return nua_client_create(nh, e, &nua_register_client_methods, tags); } static int nua_register_client_template(nua_client_request_t *cr, msg_t **return_msg, tagi_t const *tags) { nua_dialog_usage_t *du; if (cr->cr_event == nua_r_register) return 0; /* Use a copy of REGISTER message as the template for un-REGISTER */ du = nua_dialog_usage_get(cr->cr_owner->nh_ds, nua_register_usage, NULL); if (du && du->du_cr) { if (nua_client_set_target(cr, du->du_cr->cr_target) < 0) return -1; *return_msg = msg_copy(du->du_cr->cr_msg); return 1; } return 0; } static int nua_register_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; nua_registration_t *nr; sip_to_t const *aor = sip->sip_to; int unreg; /* Explicit empty (NULL) contact - used for CPL store/remove? */ if (!sip->sip_contact && cr->cr_has_contact) /* Do not create any usage */ return 0; unreg = cr->cr_event != nua_r_register || (sip->sip_expires && sip->sip_expires->ex_delta == 0); if (unreg) nua_client_set_terminating(cr, 1); du = nua_dialog_usage_add(nh, nh->nh_ds, nua_register_usage, NULL); if (du == NULL) return -1; nr = nua_dialog_usage_private(du); if (nua_client_bind(cr, du) < 0) return -1; if (!nr->nr_list) { nua_registration_add(&nh->nh_nua->nua_registrations, nr); if (aor == NULL) aor = sip->sip_from; if (aor == NULL) aor = nh->nh_nua->nua_from; if (nua_registration_set_aor(nh->nh_home, nr, aor) < 0) return -1; } if (nua_registration_set_contact(nh, nr, sip->sip_contact, unreg) < 0) return -1; if (!nr->nr_ob && (NH_PGET(nh, outbound) || NH_PGET(nh, instance))) { nr->nr_ob = outbound_new(nh, &nua_stack_outbound_callbacks, nh->nh_nua->nua_root, nh->nh_nua->nua_nta, NH_PGET(nh, instance)); if (!nr->nr_ob) return nua_client_return(cr, 900, "Cannot create outbound", msg); nua_register_usage_update_params(du, NULL, nh->nh_prefs, nh->nh_dprefs); } if (nr->nr_ob) { outbound_t *ob = nr->nr_ob; sip_contact_t *m; if (!unreg && sip->sip_contact) { for (m = sip->sip_contact; m; m = m->m_next) if (!m->m_expires || strtoul(m->m_expires, NULL, 10) != 0) break; if (m == NULL) unreg = 1; /* All contacts have expires=0 */ } if (outbound_set_contact(ob, sip->sip_contact, nr->nr_via, unreg) < 0) return nua_client_return(cr, 900, "Cannot set outbound contact", msg); } return 0; } static int nua_register_client_request(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 = cr->cr_usage; nua_registration_t *nr; sip_contact_t *m, *contacts = sip->sip_contact; char const *min_expires = NULL; int unreg; tport_t *tport = NULL; (void)nh; /* Explicit empty (NULL) contact - used for CPL store/remove? */ if (!contacts && cr->cr_has_contact) return nua_base_client_request(cr, msg, sip, tags); if ((du && du->du_shutdown) || (sip->sip_expires && sip->sip_expires->ex_delta == 0)) nua_client_set_terminating(cr, 1); if (contacts) { if (!cr->cr_terminating) { for (m = contacts; m; m = m->m_next) if (!m->m_expires || strtoul(m->m_expires, NULL, 10) != 0) break; /* All contacts have expires=0 */ if (m == NULL) nua_client_set_terminating(cr, 1); } } unreg = cr->cr_terminating; nr = nua_dialog_usage_private(du); if (nr) { if (nr->nr_ob) { outbound_stop_keepalive(nr->nr_ob); outbound_start_registering(nr->nr_ob); } if (nr->nr_by_stack) { sip_contact_t *m = nr->nr_contact, *previous = NULL; outbound_get_contacts(nr->nr_ob, &m, &previous); sip_add_dup(msg, sip, (sip_header_t *)m); /* previous is an outdated contact generated by stack * and it is now unregistered */ if (previous) sip_add_dup(msg, sip, (sip_header_t *)previous); } tport = nr->nr_tport; } for (m = sip->sip_contact; m; m = m->m_next) { if (m->m_url->url_type == url_any) { /* If there is a '*' in contact list, remove everything else */ while (m != sip->sip_contact) sip_header_remove(msg, sip, (sip_header_t *)sip->sip_contact); while (m->m_next) sip_header_remove(msg, sip, (sip_header_t *)m->m_next); contacts = m; break; } if (!m->m_expires) continue; if (unreg) { /* Remove the expire parameters from contacts */ msg_header_remove_param(m->m_common, "expires"); } else if (nr && nr->nr_min_expires && strtoul(m->m_expires, 0, 10) < nr->nr_min_expires) { if (min_expires == NULL) min_expires = su_sprintf(msg_home(msg), "expires=%lu", nr->nr_min_expires); msg_header_replace_param(msg_home(msg), m->m_common, min_expires); } } return nua_base_client_trequest(cr, msg, sip, TAG_IF(unreg, SIPTAG_EXPIRES_STR("0")), #if 0 TAG_IF(unreg, NTATAG_SIGCOMP_CLOSE(1)), TAG_IF(!unreg, NTATAG_COMP("sigcomp")), #endif NTATAG_TPORT(tport), TAG_NEXT(tags)); } static int nua_register_client_check_restart(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip) { nua_registration_t *nr = nua_dialog_usage_private(cr->cr_usage); unsigned short retry_count = cr->cr_retry_count; int restart = 0, retry; if (nr && nr->nr_ob) { msg_t *_reqmsg = nta_outgoing_getrequest(cr->cr_orq); sip_t *req = sip_object(_reqmsg); msg_destroy(_reqmsg); retry = outbound_register_response(nr->nr_ob, cr->cr_terminating, req, sip); restart = retry >= ob_reregister_now; if (retry == ob_reregister) /* outbound restarts REGISTER later */; if (retry < 0) /* XXX - report an error? */; } if (nr && status == 423) { if (sip->sip_min_expires) nr->nr_min_expires = sip->sip_min_expires->me_delta; } /* Check for status-specific reasons to retry */ if (nua_base_client_check_restart(cr, status, phrase, sip)) return 1; /* Restart only if nua_base_client_check_restart() did not try to restart */ if (restart && retry_count == cr->cr_retry_count) return nua_client_restart(cr, 100, "Outbound NAT Detected"); return 0; } static int nua_register_client_response(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip) { nua_handle_t *nh = cr->cr_owner; nua_dialog_usage_t *du = cr->cr_usage; nua_registration_t *nr = nua_dialog_usage_private(du); int ready; ready = du && !cr->cr_terminated && status < 300; if (ready) { sip_time_t mindelta = 0; sip_time_t now = sip_now(), delta, reqdelta, mdelta; sip_contact_t const *m, *sent; msg_t *_reqmsg = nta_outgoing_getrequest(cr->cr_orq); sip_t *req = sip_object(_reqmsg); tport_t *tport; msg_destroy(_reqmsg); assert(nr); assert(sip); assert(req); #if HAVE_SIGCOMP { struct sigcomp_compartment *cc; cc = nta_outgoing_compartment(cr->cr_orq); sigcomp_compartment_unref(nr->nr_compartment); nr->nr_compartment = cc; } #endif /* XXX - if store/remove, remove Content-Disposition Content-Type body */ /** Search for lowest delta of SIP contacts we tried to register */ mindelta = SIP_TIME_MAX; reqdelta = req->sip_expires ? req->sip_expires->ex_delta : 0; for (m = sip->sip_contact; m; m = m->m_next) { if (m->m_url->url_type != url_sip && m->m_url->url_type != url_sips) continue; for (sent = req->sip_contact; sent; sent = sent->m_next) { if (url_cmp(m->m_url, sent->m_url)) continue; if (sent->m_expires) mdelta = strtoul(sent->m_expires, NULL, 10); else mdelta = reqdelta; if (mdelta == 0) mdelta = 3600; delta = sip_contact_expires(m, sip->sip_expires, sip->sip_date, mdelta, now); if (delta > 0 && delta < mindelta) mindelta = delta; if (url_cmp_all(m->m_url, sent->m_url) == 0) break; } } if (mindelta == SIP_TIME_MAX) mindelta = 3600; nua_dialog_usage_set_refresh(du, mindelta); /* RFC 3608 Section 6.1 Procedures at the UA The UA performs a registration as usual. The REGISTER response may contain a Service-Route header field. If so, the UA MAY store the value of the Service-Route header field in an association with the address-of-record for which the REGISTER transaction had registered a contact. If the UA supports multiple addresses-of-record, it may be able to store multiple service routes, one per address-of-record. If the UA refreshes the registration, the stored value of the Service- Route is updated according to the Service-Route header field of the latest 200 class response. If there is no Service-Route header field in the response, the UA clears any service route for that address- of-record previously stored by the UA. If the re-registration request is refused or if an existing registration expires and the UA chooses not to re-register, the UA SHOULD discard any stored service route for that address-of-record. */ su_free(nh->nh_home, nr->nr_route); nr->nr_route = sip_route_dup(nh->nh_home, sip->sip_service_route); { /* RFC 3327 */ /* Store last URI in Path header */ sip_path_t *path = sip->sip_path; while (path && path->r_next) path = path->r_next; if (!nr->nr_path || !path || url_cmp_all(nr->nr_path->r_url, path->r_url)) { su_free(nh->nh_home, nr->nr_path); nr->nr_path = sip_path_dup(nh->nh_home, path); } } if (sip->sip_to->a_url->url_type == url_sips) nr->nr_secure = 1; if (nr->nr_ob) { outbound_gruuize(nr->nr_ob, sip); outbound_start_keepalive(nr->nr_ob, cr->cr_orq); } tport = nta_outgoing_transport (cr->cr_orq); /* cache persistant connection for registration */ if (tport && tport != nr->nr_tport) { if (nr->nr_error_report_id) { if (tport_release(nr->nr_tport, nr->nr_error_report_id, NULL, NULL, nr, 0) < 0) SU_DEBUG_1(("nua_register: tport_release() failed\n")); nr->nr_error_report_id = 0; } tport_unref(nr->nr_tport); nr->nr_tport = tport; if (tport_is_secondary(tport)) { tport_set_params(tport, TPTAG_SDWN_ERROR(1), TAG_END()); nr->nr_error_report_id = tport_pend(tport, NULL, nua_register_connection_closed, nr); } } else tport_unref(tport); /* note: nta_outgoing_transport() makes a ref */ nua_registration_set_ready(nr, 1); } else if (du) { nua_dialog_usage_reset_refresh(du); su_free(nh->nh_home, nr->nr_route); nr->nr_route = NULL; outbound_stop_keepalive(nr->nr_ob); /* release the persistant transport for registration */ if (nr->nr_tport) { if (nr->nr_error_report_id) { if (tport_release(nr->nr_tport, nr->nr_error_report_id, NULL, NULL, nr, 0) < 0) SU_DEBUG_1(("nua_register: tport_release() failed\n")); nr->nr_error_report_id = 0; } tport_unref(nr->nr_tport), nr->nr_tport = NULL; } nua_registration_set_ready(nr, 0); } return nua_base_client_response(cr, status, phrase, sip, NULL); } static void nua_register_connection_closed(tp_stack_t *sip_stack, nua_registration_t *nr, tport_t *tport, msg_t *msg, int error) { nua_dialog_usage_t *du; tp_name_t const *tpn; int pending; assert(nr && tport == nr->nr_tport); if (nr == NULL || tport != nr->nr_tport) return; du = NUA_DIALOG_USAGE_PUBLIC(nr); pending = nr->nr_error_report_id; if (tport_release(tport, pending, NULL, NULL, nr, 0) < 0) SU_DEBUG_1(("nua_register: tport_release() failed\n")); nr->nr_error_report_id = 0; tpn = tport_name(nr->nr_tport); SU_DEBUG_5(("nua_register(%p): tport to %s/%s:%s%s%s closed %s\n", du->du_dialog->ds_owner, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, tpn->tpn_comp ? ";comp=" : "", tpn->tpn_comp ? tpn->tpn_comp : "", error != 0 ? su_strerror(error) : "")); tport_unref(nr->nr_tport), nr->nr_tport = NULL; /* Schedule re-REGISTER immediately */ nua_dialog_usage_set_refresh_range(du, 0, 0); } static void nua_register_usage_update_params(nua_dialog_usage_t const *du, nua_handle_preferences_t const *changed, nua_handle_preferences_t const *nhp, nua_handle_preferences_t const *dnhp) { nua_registration_t *nr = nua_dialog_usage_private(du); outbound_t *ob = nr->nr_ob; if (!ob) return; if (!changed || NHP_ISSET(changed, outbound) || NHP_ISSET(changed, keepalive) || NHP_ISSET(changed, keepalive_stream)) { char const *outbound = NHP_ISSET(nhp, outbound) ? nhp->nhp_outbound : dnhp->nhp_outbound; unsigned keepalive = NHP_ISSET(nhp, keepalive) ? nhp->nhp_keepalive : dnhp->nhp_keepalive; unsigned keepalive_stream = NHP_ISSET(nhp, keepalive_stream) ? nhp->nhp_keepalive_stream : NHP_ISSET(dnhp, keepalive_stream) ? nhp->nhp_keepalive_stream : keepalive; outbound_set_options(ob, outbound, keepalive, keepalive_stream); } if (!changed || NHP_ISSET(changed, proxy)) { if (NHP_ISSET(nhp, proxy)) outbound_set_proxy(ob, nhp->nhp_proxy); } } static void nua_register_usage_refresh(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now) { nua_t *nua = nh->nh_nua; nua_client_request_t *cr = du->du_cr; if (cr) { if (nua_client_resend_request(cr, 0) >= 0) return; } /* Report that we have de-registered */ nua_stack_event(nua, nh, NULL, nua_r_register, NUA_ERROR_AT(__FILE__, __LINE__), NULL); nua_dialog_usage_remove(nh, ds, du, NULL, NULL); } /** @interal Shut down REGISTER usage. * * @retval >0 shutdown done * @retval 0 shutdown in progress * @retval <0 try again later */ static int nua_register_usage_shutdown(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { nua_client_request_t *cr = du->du_cr; nua_registration_t *nr = NUA_DIALOG_USAGE_PRIVATE(du); if (cr) { if (nua_client_is_queued(cr)) /* Already registering. */ return -1; cr->cr_event = nua_r_unregister; if (nua_client_resend_request(cr, 1) >= 0) return 0; } /* release the persistant transport for registration */ if (nr->nr_tport) tport_decref(&nr->nr_tport), nr->nr_tport = NULL; nua_dialog_usage_remove(nh, ds, du, NULL, NULL); return 200; } /* ---------------------------------------------------------------------- */ /* nua_registration_t interface */ #if HAVE_SOFIA_STUN #include #endif static void nua_stack_tport_update(nua_t *nua, nta_agent_t *nta); static int nua_registration_add_contact_and_route(nua_handle_t *nh, nua_registration_t *nr, msg_t *msg, sip_t *sip, int add_contact, int add_service_route); int nua_stack_init_transport(nua_t *nua, tagi_t const *tags) { url_string_t const *contact1 = NULL, *contact2 = NULL; char const *name1 = "sip", *name2 = "sip"; char const *certificate_dir = NULL; tl_gets(tags, NUTAG_URL_REF(contact1), NUTAG_SIPS_URL_REF(contact2), NUTAG_CERTIFICATE_DIR_REF(certificate_dir), TAG_END()); if (!contact1 && contact2) contact1 = contact2, contact2 = NULL; if (contact1 && (url_is_string(contact1) ? strncasecmp(contact1->us_str, "sips:", 5) == 0 : contact1->us_url->url_type == url_sips)) name1 = "sips"; if (contact2 && (url_is_string(contact2) ? strncasecmp(contact2->us_str, "sips:", 5) == 0 : contact2->us_url->url_type == url_sips)) name2 = "sips"; if (!contact1 /* && !contact2 */) { if (nta_agent_add_tport(nua->nua_nta, NULL, TPTAG_IDENT("sip"), TPTAG_CERTIFICATE(certificate_dir), TAG_NEXT(nua->nua_args)) < 0 && nta_agent_add_tport(nua->nua_nta, URL_STRING_MAKE("sip:*:*"), TPTAG_IDENT("sip"), TPTAG_CERTIFICATE(certificate_dir), TAG_NEXT(nua->nua_args)) < 0) return -1; #if HAVE_SOFIA_STUN if (stun_is_requested(TAG_NEXT(nua->nua_args)) && nta_agent_add_tport(nua->nua_nta, URL_STRING_MAKE("sip:0.0.0.0:*"), TPTAG_IDENT("stun"), TPTAG_PUBLIC(tport_type_stun), /* use stun */ TPTAG_CERTIFICATE(certificate_dir), TAG_NEXT(nua->nua_args)) < 0) { SU_DEBUG_0(("nua: error initializing STUN transport\n")); } #endif } else { if (nta_agent_add_tport(nua->nua_nta, contact1, TPTAG_IDENT(name1), TPTAG_CERTIFICATE(certificate_dir), TAG_NEXT(nua->nua_args)) < 0) return -1; if (contact2 && nta_agent_add_tport(nua->nua_nta, contact2, TPTAG_IDENT(name2), TPTAG_CERTIFICATE(certificate_dir), TAG_NEXT(nua->nua_args)) < 0) return -1; } if (nua_stack_init_registrations(nua) < 0) return -1; return 0; } #if 0 /* Store network detector param value */ if (agent->sa_nw_updates == 0) agent->sa_nw_updates = nw_updates; NTATAG_DETECT_NETWORK_UPDATES_REF(nw_updates), unsigned nw_updates = 0; unsigned nw_updates = 0; su_network_changed_t *sa_nw_changed; #endif static void nua_network_changed_cb(nua_t *nua, su_root_t *root) { uint32_t nw_updates = NUA_NW_DETECT_TRY_FULL; switch (nw_updates) { case NUA_NW_DETECT_ONLY_INFO: nua_stack_event(nua, NULL, NULL, nua_i_network_changed, SIP_200_OK, NULL); break; case NUA_NW_DETECT_TRY_FULL: /* 1) Shutdown all tports */ nta_agent_close_tports(nua->nua_nta); /* 2) Create new tports */ if (nua_stack_init_transport(nua, nua->nua_args) < 0) /* We are hosed */ nua_stack_event(nua, NULL, NULL, nua_i_network_changed, 900, "Internal Error", NULL); else nua_stack_event(nua, NULL, NULL, nua_i_network_changed, SIP_200_OK, NULL); break; default: break; } return; } int nua_stack_launch_network_change_detector(nua_t *nua) { su_network_changed_t *snc = NULL; snc = su_root_add_network_changed(nua->nua_home, nua->nua_api_root, nua_network_changed_cb, nua); if (!snc) return -1; nua->nua_nw_changed = snc; return 0; } int nua_stack_init_registrations(nua_t *nua) { /* Create initial identities: peer-to-peer, public, sips */ nua_registration_t **nr_list = &nua->nua_registrations, **nr_next; nua_handle_t **nh_list; nua_handle_t *dnh = nua->nua_dhandle; sip_via_t const *v; /* Remove existing, local address based registrations and count the rest */ while (nr_list && *nr_list) { nr_next = &(*nr_list)->nr_next; if ((*nr_list)->nr_default == 1) { nua_registration_remove(*nr_list); /* memset(*nr_list, 170, sizeof(**nr_list)); */ /* XXX - free, too */ } nr_list = nr_next; } nr_list = &nua->nua_registrations; v = nta_agent_public_via(nua->nua_nta); if (v) { nua_registration_from_via(nr_list, dnh, v, 1); } v = nta_agent_via(nua->nua_nta); if (v) { nua_registration_from_via(nr_list, dnh, v, 0); } else { sip_via_t v[2]; sip_via_init(v)->v_next = v + 1; v[0].v_protocol = sip_transport_udp; v[0].v_host = "addr.is.invalid."; sip_via_init(v + 1); v[1].v_protocol = sip_transport_tcp; v[1].v_host = "addr.is.invalid."; nua_registration_from_via(nr_list, dnh, v, 0); } /* Go through all the registrations and set to refresh almost immediately */ nh_list = &nua->nua_handles; for (; *nh_list; nh_list = &(*nh_list)->nh_next) { nua_dialog_state_t *ds; nua_dialog_usage_t *du; ds = (*nh_list)->nh_ds; du = ds->ds_usage; if (ds->ds_has_register == 1 && du->du_class->usage_refresh) { nua_dialog_usage_refresh(*nh_list, ds, du, 1); } } nta_agent_bind_tport_update(nua->nua_nta, (nta_update_magic_t *)nua, nua_stack_tport_update); return 0; } int nua_registration_from_via(nua_registration_t **list, nua_handle_t *nh, sip_via_t const *via, int public) { su_home_t *home = nh->nh_home; sip_via_t *v, *pair, /* v2[2], */ *vias, **vv, **prev; nua_registration_t *nr = NULL, **next; su_home_t autohome[SU_HOME_AUTO_SIZE(1024)]; int nr_items = 0; vias = sip_via_copy(su_home_auto(autohome, sizeof autohome), via); for (; *list; list = &(*list)->nr_next) ++nr_items; next = list; for (vv = &vias; (v = *vv);) { char const *protocol; sip_contact_t *contact; sip_via_t v2[2]; *vv = v->v_next, v->v_next = NULL, pair = NULL; if (v->v_protocol == sip_transport_tcp) protocol = sip_transport_udp; else if (v->v_protocol == sip_transport_udp) protocol = sip_transport_tcp; else protocol = NULL; if (protocol) { /* Try to pair vias if we have both udp and tcp */ for (prev = vv; *prev; prev = &(*prev)->v_next) { if (strcasecmp(protocol, (*prev)->v_protocol)) continue; if (strcasecmp(v->v_host, (*prev)->v_host)) continue; if (str0cmp(v->v_port, (*prev)->v_port)) continue; break; } if (*prev) { pair = *prev; *prev = pair->v_next; pair->v_next = NULL; } } /* if more than one candidate, ignore local entries */ if (v && (*vv || nr_items > 0) && host_is_local(v->v_host)) { SU_DEBUG_9(("nua_register: ignoring contact candidate %s:%s.\n", v->v_host, v->v_port ? v->v_port : "")); continue; } nr = su_zalloc(home, sizeof *nr); if (!nr) break; v2[0] = *v; if (pair) /* Don't use protocol if we have both udp and tcp */ protocol = NULL, v2[0].v_next = &v2[1], v2[1] = *pair; else protocol = via->v_protocol, v2[0].v_next = NULL; v2[1].v_next = NULL; contact = nua_handle_contact_by_via(nh, home, 0, v2, protocol, NULL); v = sip_via_dup(home, v2); if (!contact || !v) { su_free(home, nr); break; } nr->nr_ready = 1, nr->nr_default = 1, nr->nr_public = public; nr->nr_secure = contact->m_url->url_type == url_sips; nr->nr_contact = contact; *nr->nr_dcontact = *contact, nr->nr_dcontact->m_params = NULL; nr->nr_via = v; nr->nr_ip4 = host_is_ip4_address(contact->m_url->url_host); nr->nr_ip6 = !nr->nr_ip4 && host_is_ip6_reference(contact->m_url->url_host); SU_DEBUG_9(("nua_register: Adding contact URL '%s' to list.\n", contact->m_url->url_host)); ++nr_items; nr->nr_next = *next, nr->nr_prev = next; *next = nr, next = &nr->nr_next; nr->nr_list = list; } su_home_deinit(autohome); return 0; } static void nua_stack_tport_update(nua_t *nua, nta_agent_t *nta) { #if 0 nua_registration_t *default_oc; nua_registration_t const *defaults = nua->nua_registrations; sip_via_t *via = nta_agent_via(nta); default_oc = outbound_by_aor(defaults, NULL, 1); if (default_oc) { assert(default_oc->nr_via); outbound_contacts_from_via(default_oc, via, via->v_next); /* refresh_register(nua_handle_t *nh, nua_dialog_usage_t *du, sip_time_t now); */ } #endif return; } nua_registration_t *nua_registration_by_aor(nua_registration_t const *list, sip_from_t const *aor, url_t const *remote_uri, int only_default) { sip_from_t *alt_aor = NULL, _alt_aor[1]; int sips_aor = aor && aor->a_url->url_type == url_sips; int sips_uri = remote_uri && remote_uri->url_type == url_sips; nua_registration_t const *nr, *public = NULL, *any = NULL; nua_registration_t const *registered = NULL; nua_registration_t const *namewise = NULL, *sipswise = NULL; int ip4 = remote_uri && host_is_ip4_address(remote_uri->url_host); int ip6 = remote_uri && host_is_ip6_reference(remote_uri->url_host); if (only_default || aor == NULL) { /* Ignore AoR, select only by remote_uri */ for (nr = list; nr; nr = nr->nr_next) { if (!nr->nr_ready) continue; if (only_default && !nr->nr_default) continue; if (nr->nr_ip4 && ip6) continue; if (nr->nr_ip6 && ip4) continue; if (sips_uri ? nr->nr_secure : !nr->nr_secure) return (nua_registration_t *)nr; if (!registered && nr->nr_aor) registered = nr; if (!public && nr->nr_public) public = nr; if (!any) any = nr; } if (registered) return (nua_registration_t *)registered; if (public) return (nua_registration_t *)public; if (any) return (nua_registration_t *)any; return NULL; } if (!sips_aor && aor) { alt_aor = memcpy(_alt_aor, aor, sizeof _alt_aor); alt_aor->a_url->url_type = url_sips; alt_aor->a_url->url_scheme = "sips"; } for (nr = list; nr; nr = nr->nr_next) { if (!nr->nr_ready || !nr->nr_contact) continue; if (nr->nr_aor) { if (aor && url_cmp(nr->nr_aor->a_url, aor->a_url) == 0) return (nua_registration_t *)nr; if (!namewise && alt_aor && url_cmp(nr->nr_aor->a_url, aor->a_url) == 0) namewise = nr; } if (!sipswise && ((sips_aor || sips_uri) ? nr->nr_secure : !nr->nr_secure)) sipswise = nr; if (!registered) registered = nr; if (!public && nr->nr_public) public = nr; if (!any) any = nr; } if (namewise) return (nua_registration_t *)namewise; if (sipswise) return (nua_registration_t *)sipswise; if (registered) return (nua_registration_t *)registered; /* XXX - should we do some policing whether sips_aor or sips_uri can be used with sip contact? */ if (public) return (nua_registration_t *)public; if (any) return (nua_registration_t *)any; return NULL; } nua_registration_t * nua_registration_for_request(nua_registration_t const *list, sip_t const *sip) { sip_from_t const *aor; url_t *uri; aor = sip->sip_from; uri = sip->sip_request->rq_url; return nua_registration_by_aor(list, aor, uri, 0); } nua_registration_t * nua_registration_for_response(nua_registration_t const *list, sip_t const *sip, sip_record_route_t const *record_route, sip_contact_t const *remote_contact) { nua_registration_t *nr; sip_to_t const *aor = NULL; url_t const *uri = NULL; if (sip) aor = sip->sip_to; if (record_route) uri = record_route->r_url; else if (sip && sip->sip_record_route) uri = sip->sip_record_route->r_url; else if (remote_contact) uri = remote_contact->m_url; else if (sip && sip->sip_from) uri = sip->sip_from->a_url; nr = nua_registration_by_aor(list, aor, uri, 0); return nr; } /** Return Contact usable in dialogs */ sip_contact_t const *nua_registration_contact(nua_registration_t const *nr) { if (nr->nr_by_stack && nr->nr_ob) { sip_contact_t const *m = outbound_dialog_contact(nr->nr_ob); if (m) return m; } if (nr->nr_contact) return nr->nr_dcontact; else return NULL; } /** Return initial route. */ sip_route_t const *nua_registration_route(nua_registration_t const *nr) { return nr ? nr->nr_route : NULL; } sip_contact_t const *nua_stack_get_contact(nua_registration_t const *nr) { nr = nua_registration_by_aor(nr, NULL, NULL, 1); return nr && nr->nr_contact ? nr->nr_dcontact : NULL; } /** Add a Contact (and Route) header to request */ int nua_registration_add_contact_to_request(nua_handle_t *nh, msg_t *msg, sip_t *sip, int add_contact, int add_service_route) { nua_registration_t *nr = NULL; if (!add_contact && !add_service_route) return 0; if (nh == NULL || msg == NULL) return -1; if (sip == NULL) sip = sip_object(msg); if (nr == NULL) nr = nua_registration_for_request(nh->nh_nua->nua_registrations, sip); return nua_registration_add_contact_and_route(nh, nr, msg, sip, add_contact, add_service_route); } /** Add a Contact header to response. * * @param nh * @param msg response message * @param sip headers in response message * @param record_route record-route from request * @param remote_contact Contact from request */ int nua_registration_add_contact_to_response(nua_handle_t *nh, msg_t *msg, sip_t *sip, sip_record_route_t const *record_route, sip_contact_t const *remote_contact) { nua_registration_t *nr = NULL; if (sip == NULL) sip = sip_object(msg); if (nh == NULL || msg == NULL || sip == NULL) return -1; if (nr == NULL) nr = nua_registration_for_response(nh->nh_nua->nua_registrations, sip, record_route, remote_contact); return nua_registration_add_contact_and_route(nh, nr, msg, sip, 1, 0); } /** Add a Contact (and Route) header to request */ static int nua_registration_add_contact_and_route(nua_handle_t *nh, nua_registration_t *nr, msg_t *msg, sip_t *sip, int add_contact, int add_service_route) { if (nr == NULL) return -1; if (add_contact) { sip_contact_t const *m = NULL; char const *m_display; char const *m_username; char const *m_params; url_t const *u; if (nr->nr_by_stack && nr->nr_ob) { m = outbound_dialog_gruu(nr->nr_ob); if (m) return msg_header_add_dup(msg, (msg_pub_t *)sip, (void const *)m); m = outbound_dialog_contact(nr->nr_ob); } if (m == NULL) m = nr->nr_contact; if (!m) return -1; u = m->m_url; if (NH_PISSET(nh, m_display)) m_display = NH_PGET(nh, m_display); else m_display = m->m_display; if (NH_PISSET(nh, m_username)) m_username = NH_PGET(nh, m_username); else m_username = m->m_url->url_user; if (NH_PISSET(nh, m_params)) { m_params = NH_PGET(nh, m_params); if (u->url_params && m_params && strstr(u->url_params, m_params) == 0) m_params = NULL; } else m_params = NULL; m = sip_contact_format(msg_home(msg), "%s<%s:%s%s%s%s%s%s%s%s%s>", m_display ? m_display : "", u->url_scheme, m_username ? m_username : "", m_username ? "@" : "", u->url_host, u->url_port ? ":" : "", u->url_port ? u->url_port : "", u->url_params ? ";" : "", u->url_params ? u->url_params : "", m_params ? ";" : "", m_params ? m_params : ""); if (msg_header_insert(msg, (msg_pub_t *)sip, (void *)m) < 0) return -1; } if (add_service_route && !sip->sip_status) { sip_route_t const *sr = nua_registration_route(nr); if (msg_header_add_dup(msg, (msg_pub_t *)sip, (void const *)sr) < 0) return -1; } return 0; } /** Add a registration to list of contacts */ int nua_registration_add(nua_registration_t **list, nua_registration_t *nr) { assert(list && nr); if (nr->nr_list == NULL) { nua_registration_t *next = *list; if (next) next->nr_prev = &nr->nr_next; nr->nr_next = next, nr->nr_prev = list, nr->nr_list = list; *list = nr; } return 0; } /** Remove from list of registrations */ void nua_registration_remove(nua_registration_t *nr) { if ((*nr->nr_prev = nr->nr_next)) nr->nr_next->nr_prev = nr->nr_prev; nr->nr_next = NULL, nr->nr_prev = NULL, nr->nr_list = NULL; } /** Set address-of-record. */ int nua_registration_set_aor(su_home_t *home, nua_registration_t *nr, sip_from_t const *aor) { sip_from_t *new_aor, *old_aor; if (!home || !nr || !aor) return -1; new_aor = sip_from_dup(home, aor); if (!new_aor) return -1; old_aor = nr->nr_aor; nr->nr_aor = new_aor; msg_header_free(home, (void *)old_aor); return 0; } /** Set contact. */ int nua_registration_set_contact(nua_handle_t *nh, nua_registration_t *nr, sip_contact_t const *application_contact, int terminating) { sip_contact_t *m = NULL, *previous; url_t *uri; if (!nh || !nr) return -1; uri = nr->nr_aor ? nr->nr_aor->a_url : NULL; previous = nr->nr_contact; if (application_contact) { m = sip_contact_dup(nh->nh_home, application_contact); } else if (terminating && nr->nr_contact) { return 0; } else { nua_registration_t *nr0; nr0 = nua_registration_by_aor(*nr->nr_list, NULL, uri, 1); if (nr0 && nr0->nr_via) { char const *tport = nr0->nr_via->v_next ? NULL : nr0->nr_via->v_protocol; m = nua_handle_contact_by_via(nh, nh->nh_home, 0, nr0->nr_via, tport, NULL); } } if (!m) return -1; nr->nr_contact = m; *nr->nr_dcontact = *m, nr->nr_dcontact->m_params = NULL; nr->nr_ip4 = host_is_ip4_address(m->m_url->url_host); nr->nr_ip6 = !nr->nr_ip4 && host_is_ip6_reference(m->m_url->url_host); nr->nr_by_stack = !application_contact; msg_header_free(nh->nh_home, (void *)previous); return 0; } /** Mark registration as ready */ void nua_registration_set_ready(nua_registration_t *nr, int ready) { if (nr) { assert(!ready || nr->nr_contact); nr->nr_ready = ready; } } /** @internal Hook for processing incoming request by registration. * * This is used for keepalive/validate OPTIONS. */ int nua_registration_process_request(nua_registration_t *list, nta_incoming_t *irq, sip_t const *sip) { sip_call_id_t *i; nua_registration_t *nr; if (!outbound_targeted_request(sip)) return 0; /* Process by outbound... */ i = sip->sip_call_id; for (nr = list; nr; nr = nr->nr_next) { outbound_t *ob = nr->nr_ob; if (ob) if (outbound_process_request(ob, irq, sip)) return 501; /* Just in case */ } return 481; /* Call/Transaction does not exist */ } /** Outbound requests us to refresh registration */ static int nua_stack_outbound_refresh(nua_handle_t *nh, outbound_t *ob) { nua_dialog_state_t *ds = nh->nh_ds; nua_dialog_usage_t *du; du = nua_dialog_usage_get(ds, nua_register_usage, NULL); if (du) nua_dialog_usage_refresh(nh, ds, du, 1); return 0; } /** @NUA_EVENT nua_i_outbound * * Status from outbound engine. * * @param status SIP status code or NUA status code (>= 900) * describing the outbound state * @param phrase a short textual description of @a status code * @param nh operation handle associated with the outbound engine * @param hmagic application context associated with the handle * @param sip NULL or response message to an keepalive message or * registration probe * (error code and message are in status an phrase parameters) * @param tags empty * * @sa NUTAG_OUTBOUND(), NUTAG_KEEPALIVE(), NUTAG_KEEPALIVE_STREAM(), * nua_register(), #nua_r_register, nua_unregister(), #nua_r_unregister * * @END_NUA_EVENT */ /** @internal Callback from outbound_t */ static int nua_stack_outbound_status(nua_handle_t *nh, outbound_t *ob, int status, char const *phrase, tag_type_t tag, tag_value_t value, ...) { ta_list ta; ta_start(ta, tag, value); nua_stack_event(nh->nh_nua, nh, NULL, nua_i_outbound, status, phrase, ta_args(ta)); ta_end(ta); return 0; } /** @internal Callback from outbound_t */ static int nua_stack_outbound_failed(nua_handle_t *nh, outbound_t *ob, int status, char const *phrase, tag_type_t tag, tag_value_t value, ...) { ta_list ta; ta_start(ta, tag, value); nua_stack_event(nh->nh_nua, nh, NULL, nua_i_outbound, status, phrase, ta_args(ta)); ta_end(ta); return 0; } /** @internal Callback for obtaining credentials for keepalive */ static int nua_stack_outbound_credentials(nua_handle_t *nh, auth_client_t **auc) { return auc_copy_credentials(auc, nh->nh_auth); } #include #include /** @internal Generate a @Contact header. */ sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh, su_home_t *home, int in_dialog, sip_via_t const *v, char const *transport, char const *m_param, ...) { su_strlst_t *l; char const *s; char const *host, *port, *maddr, *comp; int one = 1; char _transport[16]; va_list va; sip_contact_t *m; url_t url; url_init(&url, url_sip); if (!v) return NULL; host = v->v_host; if (v->v_received) host = v->v_received; port = sip_via_port(v, &one); maddr = v->v_maddr; comp = v->v_comp; if (host == NULL) return NULL; if (sip_transport_has_tls(v->v_protocol) || sip_transport_has_tls(transport)) { url.url_type = url_sips; url.url_scheme = url_scheme(url_sips); if (port && strcmp(port, SIPS_DEFAULT_SERV) == 0) port = NULL; if (port || host_is_ip_address(host)) transport = NULL; } else if (port && host_is_ip_address(host) && strcmp(port, SIP_DEFAULT_SERV) == 0) { port = NULL; } if (transport) { if (strncasecmp(transport, "SIP/2.0/", 8) == 0) transport += 8; /* Make transport parameter lowercase */ if (strlen(transport) < (sizeof _transport)) { char *s = strcpy(_transport, transport); short c; for (s = _transport; (c = *s) && c != ';'; s++) if (isupper(c)) *s = tolower(c); transport = _transport; } } s = NH_PGET(nh, m_username); if (s) url.url_user = s; url.url_host = host; url.url_port = port; url.url_params = su_strdup(home, NH_PGET(nh, m_params)); if (transport) { url.url_params = url_strip_param_string((char*)url.url_params, "transport"); url_param_add(home, &url, su_sprintf(home, "transport=%s", transport)); } if (maddr) { url.url_params = url_strip_param_string((char*)url.url_params, "maddr"); url_param_add(home, &url, su_sprintf(home, "maddr=%s", maddr)); } if (comp) { url.url_params = url_strip_param_string((char*)url.url_params, "comp"); url_param_add(home, &url, su_sprintf(home, "comp=%s", comp)); } l = su_strlst_create(NULL); s = NH_PGET(nh, m_display); if (s) { int quote = s[span_token_lws(s)] != '\0'; su_strlst_append(l, quote ? "\"" : ""); su_strlst_append(l, s); su_strlst_append(l, quote ? "\" " : " "); } su_strlst_append(l, "<"); su_strlst_append(l, url_as_string(home, &url)); su_strlst_append(l, ">"); va_start(va, m_param); for (s = m_param; s; s = va_arg(va, char *)) { if (strlen(s) == 0) continue; su_strlst_append(l, s[0] == ';' ? "" : ";"); su_strlst_append(l, s); } va_end(va); if (!in_dialog) { s = NH_PGET(nh, m_features); if (s) s[0] == ';' ? "" : su_strlst_append(l, ";"), su_strlst_append(l, s); if (NH_PGET(nh, callee_caps)) { sip_allow_t const *allow = NH_PGET(nh, allow); if (allow) { su_strlst_append(l, ";methods=\""); if (allow->k_items) { size_t i; for (i = 0; allow->k_items[i]; i++) { su_strlst_append(l, allow->k_items[i]); if (allow->k_items[i + 1]) su_strlst_append(l, ","); } } su_strlst_append(l, "\""); } if (nh->nh_soa) { char **media = soa_media_features(nh->nh_soa, 0, home); while (*media) { if (su_strlst_len(l)) su_strlst_append(l, ";"); su_strlst_append(l, *media++); } } } } m = sip_contact_make(home, su_strlst_join(l, su_strlst_home(l), "")); su_strlst_destroy(l); return m; }