Logo Search packages:      
Sourcecode: sofia-sip version File versions  Download package

nua_register.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>
 *
 * @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

#include <sofia-sip/string0.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_LEG_MAGIC_T      struct nua_handle_s
#define NTA_OUTGOING_MAGIC_T struct nua_handle_s
#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);
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 *);

/** REGISTER usage, aka nua_registration_t */
00119 struct register_usage {
  nua_registration_t *nr_next, **nr_prev, **nr_list; /* Doubly linked list and its head */
00121   sip_from_t *nr_aor;         /**< AoR for this registration, NULL if none */
00122   sip_contact_t *nr_contact;  /**< Our Contact */
00123   sip_via_t *nr_via;          /**< Corresponding Via headers */

  /** Status of registration */
00126   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.
   */
00136   unsigned nr_default:1, nr_secure:1, nr_public:1, nr_ip4:1, nr_ip6:1;

  /** Stack-generated contact */
00139   unsigned nr_by_stack:1, :0;

  sip_route_t *nr_route;      /**< Outgoing Service-Route */
00142   sip_path_t *nr_path;        /**< Incoming Path */

00144   tport_t *nr_tport;          /**< Transport to be used when registered */
00145   nua_dialog_state_t *nr_dialogs; /**< List of our dialogs */

#if HAVE_SIGCOMP
  struct sigcomp_compartment *nr_compartment;
#endif

00151   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_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_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

  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 restart_register(nua_handle_t *nh, tagi_t *tags);

static int process_response_to_register(nua_handle_t *nh,
                              nta_outgoing_t *orq,
                              sip_t const *sip);

static void unregister_expires_contacts(msg_t *msg, sip_t *sip);

/* Interface towards outbound_t */
sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
                               su_home_t *home,
                               char const *extra_username,
                               sip_via_t const *v,
                               char const *transport,
                               char const *m_param,
                               ...);

static int nua_stack_outbound_features(nua_handle_t *nh, outbound_t *ob);

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,
    nua_handle_contact_by_via,
    nua_stack_outbound_refresh,
    nua_stack_outbound_status,
    nua_stack_outbound_failed,
    nua_stack_outbound_failed,
    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.
 *
 * @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
 *     Tags in <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
 */

int
nua_stack_register(nua_t *nua, nua_handle_t *nh, nua_event_t e,
               tagi_t const *tags)
{
  nua_dialog_usage_t *du;
  nua_registration_t *nr = NULL;
  outbound_t *ob = NULL;
  nua_client_request_t *cr = nh->nh_ds->ds_cr;
  msg_t *msg = NULL;
  sip_t *sip;
  int terminating = e != nua_r_register;

  if (nua_stack_set_handle_special(nh, nh_has_register, nua_r_register) < 0)
    return UA_EVENT2(e, 900, "Invalid handle for REGISTER");
  if (cr->cr_orq)
    return UA_EVENT2(e, 900, "Request already in progress");

  nua_stack_init_handle(nua, nh, TAG_NEXT(tags));

  du = nua_dialog_usage_add(nh, nh->nh_ds, nua_register_usage, NULL);
  if (!du)
    return UA_EVENT1(e, NUA_INTERNAL_ERROR);
  nr = nua_dialog_usage_private(du); assert(nr);
  nua_registration_add(&nh->nh_nua->nua_registrations, nr);
  if (!terminating && du->du_terminating)
    return UA_EVENT2(e, 900, "Unregister in progress");

  if (cr->cr_msg)
    msg_destroy(cr->cr_msg), cr->cr_msg = NULL;
  /* Use original message as template when unregistering */
  if (terminating)            
    cr->cr_msg = msg_ref_create(du->du_msg);

  msg = nua_creq_msg(nua, nh, cr, cr->cr_msg != NULL,
                 SIP_METHOD_REGISTER,
                 TAG_IF(!terminating, NUTAG_USE_DIALOG(1)),
                 TAG_NEXT(tags));
  sip = sip_object(msg);
  if (!msg || !sip)
    goto error;

  if (!nr->nr_aor) {
    if (nua_registration_set_aor(nh->nh_home, nr, sip->sip_to) < 0)
      goto error;
  }

  if (terminating)
    /* Add Expires: 0 and remove the expire parameters from contacts */
    unregister_expires_contacts(msg, sip);

  if (!sip->sip_contact && cr->cr_has_contact) {
    terminating = 1;
  }
  else if (nua_registration_set_contact(nh, nr, sip->sip_contact, terminating)
         < 0)
    goto error;

  du->du_terminating = terminating;

  if (du->du_msg == NULL && !terminating)
    du->du_msg = msg_ref_create(cr->cr_msg); /* Save original message */

  ob = nr->nr_ob;
  
  if (!ob && (NH_PGET(nh, outbound) || NH_PGET(nh, instance))) {
    nr->nr_ob = ob = outbound_new(nh, &nua_stack_outbound_callbacks,
                          nh->nh_nua->nua_root,
                          nh->nh_nua->nua_nta,
                          NH_PGET(nh, instance));
    if (!ob)
      goto error;
  }

  if (ob) {
    outbound_set_options(ob,
                   NH_PGET(nh, outbound),
                   NH_PGET(nh, keepalive),
                   NH_PISSET(nh, keepalive_stream)
                   ? NH_PGET(nh, keepalive_stream)
                   : NH_PGET(nh, keepalive));
    nua_stack_outbound_features(nh, ob);
    outbound_stop_keepalive(ob);

    if (outbound_set_contact(ob, sip->sip_contact, nr->nr_via, terminating) < 0)
      goto error;
  }

  /* This calls nta_outgoing_mcreate() but adds a few tags */
  cr->cr_orq =
    outbound_register_request(ob, terminating,
                        nr->nr_by_stack ? nr->nr_contact : NULL,
                        nua->nua_nta,
                        process_response_to_register, nh, NULL,
                        msg,
                        SIPTAG_END(), 
                        TAG_IF(terminating, NTATAG_SIGCOMP_CLOSE(1)),
                        TAG_IF(!terminating, NTATAG_COMP("sigcomp")),
                        TAG_NEXT(tags));

  if (!cr->cr_orq)
    goto error;

  cr->cr_usage = du;
  return cr->cr_event = e;

 error:
  msg_destroy(msg);
  msg_destroy(cr->cr_msg), cr->cr_msg = NULL;
  nua_dialog_usage_remove(nh, nh->nh_ds, du);    
  return UA_EVENT1(e, NUA_INTERNAL_ERROR);
}

static void
restart_register(nua_handle_t *nh, tagi_t *tags)
{
  nua_client_request_t *cr = nh->nh_ds->ds_cr;
  msg_t *msg;
  nua_dialog_usage_t *du = cr->cr_usage;
  nua_registration_t *nr = nua_dialog_usage_private(du);
  int terminating = du && du->du_terminating;

  cr->cr_restart = NULL;

  if (!cr->cr_msg)
    return;

  msg = nua_creq_msg(nh->nh_nua, nh, cr, 1,
                 SIP_METHOD_UNKNOWN,
                 TAG_NEXT(tags));

  if (!msg)
    return;             /* XXX - Uh-oh */

  if (terminating)
    unregister_expires_contacts(msg, sip_object(msg));

  /* This calls nta_outgoing_mcreate() but adds a few tags */
  cr->cr_orq =
    outbound_register_request(nr->nr_ob, terminating,
                        nr->nr_by_stack ? nr->nr_contact : NULL,
                        nh->nh_nua->nua_nta,
                        process_response_to_register, nh, NULL,
                        msg,
                        SIPTAG_END(), 
                        TAG_IF(terminating, NTATAG_SIGCOMP_CLOSE(1)),
                        TAG_IF(!terminating, NTATAG_COMP("sigcomp")),
                        TAG_NEXT(tags));

  if (!cr->cr_orq)
    msg_destroy(msg);
}

/** Refresh registration */
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 = nh->nh_ds->ds_cr;
  nua_registration_t *nr = nua_dialog_usage_private(du);
  msg_t *msg;
  sip_t *sip;

  if (du->du_terminating || du->du_shutdown)
    return;

  if (cr->cr_msg) {
    /* Dialog is busy, delay of 5 .. 15 seconds */
    nua_dialog_usage_refresh_range(du, 5, 15);
    return;
  }

  outbound_stop_keepalive(nr->nr_ob);

  cr->cr_msg = msg_copy(du->du_msg);
  msg = nua_creq_msg(nua, nh, cr, 1,
                 SIP_METHOD_REGISTER,
                 NUTAG_USE_DIALOG(1),
                 TAG_END());
  sip = sip_object(msg);
  if (!msg || !sip)
    goto error;

  cr->cr_orq =
    outbound_register_request(nr->nr_ob, 0,
                        nr->nr_by_stack ? nr->nr_contact : NULL,
                        nh->nh_nua->nua_nta,
                        process_response_to_register, nh, NULL,
                        msg,
                        SIPTAG_END(), 
                        NTATAG_COMP("sigcomp"),
                        TAG_END());
  if (!cr->cr_orq)
    goto error;

  cr->cr_usage = du;
  cr->cr_event = nua_r_register;
  return;

 error:
  msg_destroy(msg);
  msg_destroy(cr->cr_msg);
  UA_EVENT2(nua_r_register, NUA_INTERNAL_ERROR, TAG_END());
  return;
}

/** Shutdown register usage. 
 *
 * Called when stack is shut down or handle is destroyed. Unregister.
 */
static
int nua_register_usage_shutdown(nua_handle_t *nh, 
                        nua_dialog_state_t *ds,
                        nua_dialog_usage_t *du)
{
  nua_t *nua = nh->nh_nua;
  nua_client_request_t *cr = nh->nh_ds->ds_cr;
  nua_registration_t *nr = nua_dialog_usage_private(du);
  msg_t *msg;
  sip_t *sip;

  if (du->du_terminating)     /* Already terminating? */
    return 100;

  du->du_terminating = 1;

  if (cr->cr_msg)    /* Busy */
    return 100;

  outbound_stop_keepalive(nr->nr_ob);

  cr->cr_msg = msg_copy(du->du_msg);
  msg = nua_creq_msg(nua, nh, cr, 1,
                 SIP_METHOD_REGISTER,
                 NUTAG_USE_DIALOG(1),
                 TAG_END());
  sip = sip_object(msg);
  if (!msg || !sip)
    goto error;

  unregister_expires_contacts(msg, sip);

  cr->cr_orq =
    outbound_register_request(nr->nr_ob, 1,
                        nr->nr_by_stack ? nr->nr_contact : NULL,
                        nh->nh_nua->nua_nta,
                        process_response_to_register, nh, NULL,
                        msg,
                        SIPTAG_END(), 
                        NTATAG_SIGCOMP_CLOSE(1),
                        TAG_END());
  if (!cr->cr_orq)
    goto error;

  cr->cr_usage = du;
  cr->cr_event = nua_r_destroy;
  return 200;

 error:
  nua_dialog_usage_remove(nh, nh->nh_ds, du);
  msg_destroy(msg);
  msg_destroy(cr->cr_msg);
  return 500;
}


static
int process_response_to_register(nua_handle_t *nh,
                         nta_outgoing_t *orq,
                         sip_t const *sip)
{
  nua_client_request_t *cr = nh->nh_ds->ds_cr;
  nua_dialog_usage_t *du = cr->cr_usage;
  nua_registration_t *nr = nua_dialog_usage_private(du);
  int status, ready, reregister, terminating;
  char const *phrase;
  msg_t *_reqmsg = nta_outgoing_getrequest(orq);
  sip_t *req = sip_object(_reqmsg); msg_destroy(_reqmsg);

  assert(sip);
  assert(du && du->du_class == nua_register_usage);
  status = sip->sip_status->st_status;
  phrase = sip->sip_status->st_phrase;

  if (status < 200 || !du)
    return nua_stack_process_response(nh, cr, orq, sip, TAG_END());

  terminating = du->du_terminating;
  if (!terminating)
    nua_dialog_store_peer_info(nh, nh->nh_ds, sip);

  reregister = outbound_register_response(nr->nr_ob, terminating, req, sip);
  if (reregister < 0)
    SET_STATUS1(NUA_INTERNAL_ERROR);
  else if (reregister >= ob_reregister) {
    /* Save msg otherwise nua_creq_check_restart() will zap it */
    msg_t *msg = msg_ref_create(cr->cr_msg);

    if (nua_creq_check_restart(nh, cr, orq, sip, restart_register)) {
      msg_destroy(msg);
      return 0;
    }

    assert(cr->cr_msg == NULL);
    cr->cr_msg = msg;

    if (reregister >= ob_reregister_now) {
      /* We can try to reregister immediately */
      nua_creq_restart_with(nh, cr, orq, 100, "Updated Contact",
                      restart_register,
                      TAG_END());
    }
    else {
      /* Outbound will invoke refresh_register() later */
      nua_creq_save_restart(nh, cr, orq, 100, "Updated Contact",
                      restart_register);
    }
    return 0;
  }

  if (status >= 300)
    if (nua_creq_check_restart(nh, cr, orq, sip, restart_register))
      return 0;

  ready = !terminating && status < 300;
  du->du_ready = ready;

  if (status < 300) {
    if (!du->du_terminating) {
      sip_time_t mindelta = 0;
      sip_time_t now = sip_now(), delta, reqdelta;
      sip_contact_t const *m, *sent;

      /** 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) == 0) {
            sip_time_t mdelta = reqdelta;

            if (sent->m_expires)
              mdelta = strtoul(sent->m_expires, NULL, 10);
            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_expires(du, mindelta);
    }
    else
      nua_dialog_usage_set_expires(du, 0);
  }

#if HAVE_SIGCOMP
  if (ready) {
    struct sigcomp_compartment *cc;
    cc = nta_outgoing_compartment(orq);
    sigcomp_compartment_unref(nr->nr_compartment);
    nr->nr_compartment = cc;
  }
#endif

  /*  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.

  */
  if (ready) {
    su_free(nh->nh_home, nr->nr_route);
    nr->nr_route = sip_route_dup(nh->nh_home, sip->sip_service_route);
  }
  else {
    su_free(nh->nh_home, nr->nr_route);
    nr->nr_route = NULL;
  }

  if (ready) {
    /* 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 (ready)
    if (sip->sip_to->a_url->url_type == url_sips)
      nr->nr_secure = 1;

  if (nr->nr_ob) {
    if (ready) {
      outbound_gruuize(nr->nr_ob, sip);
      outbound_start_keepalive(nr->nr_ob, orq);
    }
    else
      outbound_stop_keepalive(nr->nr_ob);
  }

  nua_registration_set_ready(nr, ready);

  return nua_stack_process_response(nh, cr, orq, sip, TAG_END());
}

/* ---------------------------------------------------------------------- */
/* 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_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, TAG_END());

    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", TAG_END());
    else
      nua_stack_event(nua, NULL, NULL, nua_i_network_changed,
                  SIP_200_OK, TAG_END());

    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, 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;

#if 1
    contact = nua_handle_contact_by_via(nh, home, NULL, v2, protocol, NULL);
#else
    contact = sip_contact_create_from_via_with_transport(home, v2, NULL, protocol);
#endif
    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_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 *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 (!public && nr->nr_public)
      public = nr;
      if (!any)
      any = nr;
    }
    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);

  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;
    }
    else {
      if (!sipswise && ((sips_aor || sips_uri) ? 
                  nr->nr_secure : !nr->nr_secure))
      sipswise = 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;

  /* 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;
  }

  return nr->nr_contact;
}

/** 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 : 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(nr, msg, sip, 
                                    add_contact, 
                                    add_service_route);
}

/** Add a Contact header to response.
 *
 * @param nh
 * @param msg response message
 * @param sip response headers
 * @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(nr, msg, sip, 1, 0);
}

/** Add a Contact (and Route) header to request */
static 
int nua_registration_add_contact_and_route(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 = nua_registration_contact(nr);
    if (!m || msg_header_add_dup(msg, (msg_pub_t *)sip, (void const *)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,
                            NULL, nr0->nr_via, tport, NULL);
    }
  }

  if (!m)
    return -1;

  nr->nr_contact = m;
  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)
{
  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 */
}

/**@internal
 * Fix contacts for un-REGISTER.
 *
 * Remove (possible non-zero) "expires" parameters from contacts and extra
 * contacts, add Expire: 0.
 */
static
void unregister_expires_contacts(msg_t *msg, sip_t *sip)
{
  sip_contact_t *m;
  int unregister_all;

  if (msg == NULL || sip == NULL)
    return;

  /* Remove payload */
  while (sip->sip_payload)
    sip_header_remove(msg, sip, (sip_header_t *)sip->sip_payload);
  while (sip->sip_content_type)
    sip_header_remove(msg, sip, (sip_header_t *)sip->sip_content_type);

  for (m = sip->sip_contact; m; m = m->m_next) {
    if (m->m_url->url_type == url_any)
      break;
    msg_header_remove_param(m->m_common, "expires");
#if 0
    msg_header_add_param(msg_home(msg), m->m_common, "expires=0");
#endif
  }

  unregister_all = m && (m != sip->sip_contact || m->m_next);

  sip_add_tl(msg, sip,
             /* Remove existing contacts */
             TAG_IF(unregister_all, SIPTAG_CONTACT(NONE)),
             /* Add '*' contact: 0 */
             TAG_IF(unregister_all, SIPTAG_CONTACT_STR("*")),
             SIPTAG_EXPIRES_STR("0"),
             TAG_END());
}


/** 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_tags(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_tags(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,
                               char const *extra_username,
                               sip_via_t const *v,
                               char const *transport,
                               char const *m_param,
                               ...)
{
  su_strlst_t *l;
  char const *s;
  char const *scheme = "sip:", *host, *port, *maddr, *comp;
  int one = 1;
  char _transport[16];
  va_list va;
  sip_contact_t *m;

  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)) {
    scheme = "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);

      for (s = _transport; *s && *s != ';'; s++)
      if (isupper(*s))
        *s = tolower(*s);

      transport = _transport;
    }
  }

  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, scheme);
  s = NH_PGET(nh, m_username);
  if (s) su_strlst_append(l, s);
  if (extra_username) su_strlst_append(l, s);
  if (s || extra_username)
    su_strlst_append(l, "@");
  su_strlst_append(l, host);
  if (port)
    su_strlst_append(l, ":"), su_strlst_append(l, port);
  if (transport)
    su_strlst_append(l, ";transport="), su_strlst_append(l, transport);
  if (maddr)
    su_strlst_append(l, ";maddr="), su_strlst_append(l, maddr);
  if (comp)
    su_strlst_append(l, ";comp="), su_strlst_append(l, comp);
  s = NH_PGET(nh, m_params);
  if (s) 
    su_strlst_append(l, s[0] == ';' ? "" : ";"), su_strlst_append(l, s);
  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);

  m = sip_contact_make(home, su_strlst_join(l, su_strlst_home(l), ""));
  
  su_strlst_destroy(l);
  
  return m;
}

/** @internal Return a string describing our features. */
static char *nua_handle_features(nua_handle_t *nh)
{
  char *retval = NULL;
  su_strlst_t *l = su_strlst_create(NULL);
  su_home_t *home = su_strlst_home(l);

  if (!l)
    return NULL;

  if (NH_PGET(nh, m_features)) {
    char const *m_features = NH_PGET(nh, m_features);

    if (m_features[0] != ';')
      su_strlst_append(l, ";");

    su_strlst_append(l, m_features);
  }

  if (NH_PGET(nh, callee_caps)) {
    sip_allow_t const *allow = NH_PGET(nh, allow);

    if (allow) {
      /* Skip ";" if this is first one */
      su_strlst_append(l, ";methods=\"" + (su_strlst_len(l) == 0));
      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++);
      }
    }
  }

  if (su_strlst_len(l))
    retval = su_strlst_join(l, nh->nh_home, "");

  su_strlst_destroy(l);

  return retval;
}

static int nua_stack_outbound_features(nua_handle_t *nh, outbound_t *ob)
{
  char *features;
  int retval;

  if (!nh)
    return -1;
  if (!ob)
    return 0;

  features = nua_handle_features(nh);
  retval = outbound_set_features(ob, features);
  su_free(nh->nh_home, features);

  return retval;
}

Generated by  Doxygen 1.6.0   Back to index