freeswitch/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c

2128 lines
60 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2006 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* 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 <Pekka.Pessi@nokia.com>
* @author Martti Mela <Martti.Mela@nokia.com>
* @author Kai Vehmanen <Kai.Vehmanen@nokia.com>
*
* @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 <sofia-sip/su_string.h>
#include <sofia-sip/su_strlst.h>
#include <sofia-sip/su_uniqueid.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/sip_protos.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/sip_status.h>
#define NTA_UPDATE_MAGIC_T struct nua_s
#include "nua_stack.h"
#include <sofia-sip/hostdomain.h>
#include <sofia-sip/nta_tport.h>
#include <sofia-sip/tport.h>
#include <sofia-sip/tport_tag.h>
#define OUTBOUND_OWNER_T struct nua_handle_s
#include "outbound.h"
#if HAVE_SIGCOMP
#include <sigcomp.h>
#endif
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
/* ======================================================================== */
/* 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 <sip:line-1=SSQAIbjv@192.168.1.200;transport=tcp;user=phone>
* ;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
* <a href="http://www.ietf.org/internet-drafts/draft-lennox-sip-reg-payload-01.txt">
* draft-lennox-sip-reg-payload-01.txt</a>.
*
* 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 <sofia-sip/sip_tag.h> 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" VA_NONE));
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" VA_NONE));
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" VA_NONE));
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",
(void *)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 <sofia-sip/stun.h>
#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)
? su_casenmatch(contact1->us_str, "sips:", 5)
: contact1->us_url->url_type == url_sips))
name1 = "sips";
if (contact2 &&
(url_is_string(contact2)
? su_casenmatch(contact2->us_str, "sips:", 5)
: 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" VA_NONE));
}
#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;
nw_updates = nua->nua_prefs->ngp_detect_network_updates;
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_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 (!su_casematch(protocol, (*prev)->v_protocol))
continue;
if (!su_casematch(v->v_host, (*prev)->v_host))
continue;
if (!su_strmatch(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 <ctype.h>
#include <sofia-sip/bnf.h>
/** @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 (su_casenmatch(transport, "SIP/2.0/", 8))
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;
}