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

1057 lines
26 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 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 test_proxy.c
* @brief Extremely simple proxy and registrar for testing nua
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Thu Nov 3 22:49:46 EET 2005
*/
#include "config.h"
#include <string.h>
struct proxy;
struct proxy_transaction;
struct registration_entry;
struct binding;
#define SU_ROOT_MAGIC_T struct proxy
#define NTA_LEG_MAGIC_T struct proxy
#define NTA_OUTGOING_MAGIC_T struct proxy_transaction
#define NTA_INCOMING_MAGIC_T struct proxy_transaction
#include <sofia-sip/su_wait.h>
#include <sofia-sip/nta.h>
#include <sofia-sip/sip_header.h>
#include <sofia-sip/sip_status.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/auth_module.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/msg_addr.h>
#include <stdlib.h>
#include <assert.h>
#define LIST_PROTOS(STORAGE, PREFIX, T) \
STORAGE void PREFIX ##_insert(T **list, T *node), \
PREFIX ##_remove(T *node)
#define LIST_BODIES(STORAGE, PREFIX, T, NEXT, PREV) \
STORAGE void PREFIX ##_insert(T **list, T *node) \
{ \
if ((node->NEXT = *list)) { \
node->PREV = node->NEXT->PREV; \
node->NEXT->PREV = &node->NEXT; \
} \
else \
node->PREV = list; \
*list = node; \
} \
STORAGE void PREFIX ##_remove(T *node) \
{ \
if (node->PREV) \
if ((*node->PREV = node->NEXT)) \
node->NEXT->PREV = node->PREV; \
node->PREV = NULL; \
} \
extern int LIST_DUMMY_VARIABLE
#include <test_proxy.h>
struct proxy {
su_home_t home[1];
su_root_t *parent;
su_clone_r clone;
tagi_t *tags;
su_root_t *root;
auth_mod_t *auth;
nta_agent_t *agent;
url_t const *uri;
nta_leg_t *defleg;
nta_leg_t *example_net;
nta_leg_t *example_org;
nta_leg_t *example_com;
sip_contact_t *transport_contacts;
struct proxy_transaction *stateless;
struct proxy_transaction *transactions;
struct registration_entry *entries;
struct {
sip_time_t min_expires, expires, max_expires;
sip_time_t session_expires, min_se;
} prefs;
};
LIST_PROTOS(static, registration_entry, struct registration_entry);
static struct registration_entry *registration_entry_new(struct proxy *,
url_t const *);
static void registration_entry_destroy(struct registration_entry *e);
struct registration_entry
{
struct registration_entry *next, **prev;
struct proxy *proxy; /* backpointer */
url_t *aor; /* address-of-record */
struct binding *bindings; /* list of bindings */
sip_contact_t *contacts;
};
struct binding
{
struct binding *next, **prev;
sip_contact_t *contact; /* bindings */
sip_time_t registered, expires; /* When registered and when expires */
sip_call_id_t *call_id;
uint32_t cseq;
};
static struct binding *binding_new(su_home_t *home,
sip_contact_t *contact,
sip_call_id_t *call_id,
uint32_t cseq,
sip_time_t registered,
sip_time_t expires);
static void binding_destroy(su_home_t *home, struct binding *b);
static int binding_is_active(struct binding const *b)
{
return b->expires > sip_now();
}
LIST_PROTOS(static, proxy_transaction, struct proxy_transaction);
struct proxy_transaction *proxy_transaction_new(struct proxy *);
static void proxy_transaction_destroy(struct proxy_transaction *t);
struct proxy_transaction
{
struct proxy_transaction *next, **prev;
struct proxy *proxy; /* backpointer */
sip_request_t *rq; /* request line */
nta_incoming_t *server; /* server transaction */
nta_outgoing_t *client; /* client transaction */
};
static sip_contact_t *create_transport_contacts(struct proxy *p);
static int proxy_request(struct proxy *proxy,
nta_leg_t *leg,
nta_incoming_t *irq,
sip_t const *sip);
static int proxy_ack_cancel(struct proxy_transaction *t,
nta_incoming_t *irq,
sip_t const *sip);
static int proxy_response(struct proxy_transaction *t,
nta_outgoing_t *client,
sip_t const *sip);
static int process_register(struct proxy *proxy,
nta_incoming_t *irq,
sip_t const *sip);
static int domain_request(struct proxy *proxy,
nta_leg_t *leg,
nta_incoming_t *irq,
sip_t const *sip);
static int process_options(struct proxy *proxy,
nta_incoming_t *irq,
sip_t const *sip);
static struct registration_entry *
registration_entry_find(struct proxy const *proxy, url_t const *uri);
static auth_challenger_t registrar_challenger[1];
static auth_challenger_t proxy_challenger[1];
/* Proxy entry point */
static int
test_proxy_init(su_root_t *root, struct proxy *proxy)
{
struct proxy_transaction *t;
auth_challenger_t _proxy_challenger[1] =
{{
SIP_407_PROXY_AUTH_REQUIRED,
sip_proxy_authenticate_class,
sip_proxy_authentication_info_class
}};
auth_challenger_t _registrar_challenger[1] =
{{
SIP_401_UNAUTHORIZED,
sip_www_authenticate_class,
sip_authentication_info_class
}};
*proxy_challenger = *_proxy_challenger;
*registrar_challenger = *_registrar_challenger;
proxy->root = root;
proxy->auth = auth_mod_create(root, TAG_NEXT(proxy->tags));
proxy->agent = nta_agent_create(root,
URL_STRING_MAKE("sip:0.0.0.0:*"),
NULL, NULL,
NTATAG_UA(0),
NTATAG_SERVER_RPORT(1),
NTATAG_CLIENT_RPORT(1),
TAG_END());
proxy->transport_contacts = create_transport_contacts(proxy);
proxy->defleg = nta_leg_tcreate(proxy->agent,
proxy_request,
proxy,
NTATAG_NO_DIALOG(1),
TAG_END());
proxy->example_net = nta_leg_tcreate(proxy->agent,
domain_request,
proxy,
NTATAG_NO_DIALOG(1),
URLTAG_URL("sip:example.net"),
TAG_END());
proxy->example_org = nta_leg_tcreate(proxy->agent,
domain_request,
proxy,
NTATAG_NO_DIALOG(1),
URLTAG_URL("sip:example.org"),
TAG_END());
proxy->example_com = nta_leg_tcreate(proxy->agent,
domain_request,
proxy,
NTATAG_NO_DIALOG(1),
URLTAG_URL("sip:example.com"),
TAG_END());
proxy->prefs.min_expires = 30;
proxy->prefs.expires = 3600;
proxy->prefs.max_expires = 3600;
proxy->prefs.session_expires = 180;
proxy->prefs.min_se = 90;
if (!proxy->defleg ||
!proxy->example_net || !proxy->example_org || !proxy->example_com)
return -1;
t = su_zalloc(proxy->home, sizeof *t);
if (!t)
return -1;
proxy->stateless = t;
t->proxy = proxy;
t->server = nta_incoming_default(proxy->agent);
t->client = nta_outgoing_default(proxy->agent, proxy_response, t);
if (!t->client || !t->server)
return -1;
proxy->uri = nta_agent_contact(proxy->agent)->m_url;
return 0;
}
static void
test_proxy_deinit(su_root_t *root, struct proxy *proxy)
{
struct proxy_transaction *t;
auth_mod_destroy(proxy->auth);
if ((t = proxy->stateless)) {
nta_incoming_destroy(t->server), t->server = NULL;
nta_outgoing_destroy(t->client), t->client = NULL;
}
nta_agent_destroy(proxy->agent);
while (proxy->entries)
registration_entry_destroy(proxy->entries);
free(proxy->tags);
}
/* Create tst proxy object */
struct proxy *test_proxy_create(su_root_t *root,
tag_type_t tag, tag_value_t value, ...)
{
struct proxy *p = su_home_new(sizeof *p);
if (p) {
ta_list ta;
p->parent = root;
ta_start(ta, tag, value);
p->tags = tl_llist(ta_tags(ta));
ta_end(ta);
if (su_clone_start(root,
p->clone,
p,
test_proxy_init,
test_proxy_deinit) == -1)
su_home_unref(p->home), p = NULL;
}
return p;
}
/* Destroy the proxy object */
void test_proxy_destroy(struct proxy *p)
{
if (p) {
su_clone_wait(p->parent, p->clone);
su_home_unref(p->home);
}
}
/* Return the proxy URI */
url_t const *test_proxy_uri(struct proxy const *p)
{
return p ? p->uri : NULL;
}
void test_proxy_set_expiration(struct proxy *p,
sip_time_t min_expires,
sip_time_t expires,
sip_time_t max_expires)
{
if (p) {
p->prefs.min_expires = min_expires;
p->prefs.expires = expires;
p->prefs.max_expires = max_expires;
}
}
void test_proxy_get_expiration(struct proxy *p,
sip_time_t *return_min_expires,
sip_time_t *return_expires,
sip_time_t *return_max_expires)
{
if (p) {
if (return_min_expires) *return_min_expires = p->prefs.min_expires;
if (return_expires) *return_expires = p->prefs.expires;
if (return_max_expires) *return_max_expires = p->prefs.max_expires;
}
}
/* ---------------------------------------------------------------------- */
static sip_contact_t *create_transport_contacts(struct proxy *p)
{
su_home_t *home = p->home;
sip_via_t *v;
sip_contact_t *retval = NULL, **mm = &retval;
if (!p->agent)
return NULL;
for (v = nta_agent_via(p->agent); v; v = v->v_next) {
char const *proto = v->v_protocol;
if (v->v_next &&
strcasecmp(v->v_host, v->v_next->v_host) == 0 &&
str0cmp(v->v_port, v->v_next->v_port) == 0 &&
((proto == sip_transport_udp &&
v->v_next->v_protocol == sip_transport_tcp) ||
(proto == sip_transport_tcp &&
v->v_next->v_protocol == sip_transport_udp)))
/* We have udp/tcp pair, insert URL without tport parameter */
*mm = sip_contact_create_from_via_with_transport(home, v, NULL, NULL);
if (*mm) mm = &(*mm)->m_next;
*mm = sip_contact_create_from_via_with_transport(home, v, NULL, proto);
if (*mm) mm = &(*mm)->m_next;
}
return retval;
}
/* ---------------------------------------------------------------------- */
static int challenge_request(struct proxy *, nta_incoming_t *, sip_t const *);
/** Forward request */
static
int proxy_request(struct proxy *proxy,
nta_leg_t *leg,
nta_incoming_t *irq,
sip_t const *sip)
{
url_t const *request_uri, *target;
struct proxy_transaction *t = NULL;
sip_request_t *rq = NULL;
sip_max_forwards_t *mf;
sip_method_t method = sip->sip_request->rq_method;
sip_session_expires_t *x = NULL, x0[1];
sip_min_se_t *min_se = NULL, min_se0[1];
mf = sip->sip_max_forwards;
if (mf && mf->mf_count <= 1) {
if (sip->sip_request->rq_method == sip_method_options) {
return process_options(proxy, irq, sip);
}
nta_incoming_treply(irq, SIP_483_TOO_MANY_HOPS, TAG_END());
return 483;
}
if (method != sip_method_ack && method != sip_method_cancel &&
str0casecmp(sip->sip_from->a_url->url_host, "example.net") == 0) {
/* Challenge everything but CANCEL and ACK coming from Mr. C */
int status = challenge_request(proxy, irq, sip);
if (status)
return status;
}
if (method == sip_method_invite) {
if (!sip->sip_min_se ||
sip->sip_min_se->min_delta < proxy->prefs.min_se) {
min_se = sip_min_se_init(min_se0);
min_se->min_delta = proxy->prefs.min_se;
}
if (!sip->sip_session_expires) {
x = sip_session_expires_init(x0);
x->x_delta = proxy->prefs.session_expires;
}
else if (sip->sip_session_expires->x_delta < proxy->prefs.min_se
&& sip_has_supported(sip->sip_supported, "timer")) {
if (min_se == NULL)
min_se = sip->sip_min_se; assert(min_se);
nta_incoming_treply(irq, SIP_422_SESSION_TIMER_TOO_SMALL,
SIPTAG_MIN_SE(min_se),
TAG_END());
return 422;
}
}
/* We don't do any route processing */
request_uri = sip->sip_request->rq_url;
if (!request_uri->url_host ||
(strcasecmp(request_uri->url_host, "example.org") &&
strcasecmp(request_uri->url_host, "example.net") &&
strcasecmp(request_uri->url_host, "example.com"))) {
target = request_uri;
}
else {
struct registration_entry *e;
struct binding *b;
if (sip->sip_request->rq_method == sip_method_register)
return process_register(proxy, irq, sip);
e = registration_entry_find(proxy, request_uri);
if (e == NULL) {
nta_incoming_treply(irq, SIP_404_NOT_FOUND, TAG_END());
return 404;
}
for (b = e->bindings; b; b = b->next)
if (binding_is_active(b))
break;
if (b == NULL) {
nta_incoming_treply(irq, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END());
return 480;
}
target = b->contact->m_url;
}
t = proxy_transaction_new(proxy);
if (t == NULL) {
nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
return 500;
}
nta_incoming_bind(t->server = irq, proxy_ack_cancel, t);
rq = sip_request_create(proxy->home,
sip->sip_request->rq_method,
sip->sip_request->rq_method_name,
(url_string_t *)target,
NULL);
if (rq == NULL) {
nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
proxy_transaction_destroy(t);
return 500;
}
t->rq = rq;
/* Forward request */
t->client = nta_outgoing_mcreate(proxy->agent, proxy_response, t, NULL,
nta_incoming_getrequest(irq),
/* rewrite request */
SIPTAG_REQUEST(rq),
SIPTAG_SESSION_EXPIRES(x),
SIPTAG_MIN_SE(min_se),
TAG_END());
if (t->client == NULL) {
proxy_transaction_destroy(t);
nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
return 500;
}
else if (sip->sip_request->rq_method == sip_method_ack)
proxy_transaction_destroy(t);
return 0;
}
static
int challenge_request(struct proxy *p,
nta_incoming_t *irq,
sip_t const *sip)
{
int status;
auth_status_t *as;
msg_t *msg;
as = auth_status_new(p->home);
if (!as)
return 500;
as->as_method = sip->sip_request->rq_method_name;
msg = nta_incoming_getrequest(irq);
as->as_source = msg_addrinfo(msg);
as->as_user_uri = sip->sip_from->a_url;
as->as_display = sip->sip_from->a_display;
if (sip->sip_payload)
as->as_body = sip->sip_payload->pl_data,
as->as_bodylen = sip->sip_payload->pl_len;
auth_mod_check_client(p->auth, as, sip->sip_proxy_authorization,
proxy_challenger);
if ((status = as->as_status)) {
nta_incoming_treply(irq,
as->as_status, as->as_phrase,
SIPTAG_HEADER((void *)as->as_info),
SIPTAG_HEADER((void *)as->as_response),
TAG_END());
}
else if (as->as_match) {
msg_header_remove(msg, NULL, as->as_match);
}
msg_destroy(msg);
su_home_unref(as->as_home);
return status;
}
int proxy_ack_cancel(struct proxy_transaction *t,
nta_incoming_t *irq,
sip_t const *sip)
{
if (sip == NULL) {
proxy_transaction_destroy(t);
return 0;
}
if (sip->sip_request->rq_method == sip_method_cancel) {
/* We don't care about response to CANCEL (or ACK)
* so we give NULL as callback pointer (and nta immediately
* destroys transaction object or marks it disposable)
*/
if (nta_outgoing_tcancel(t->client, NULL, NULL, TAG_END()))
return 200;
else
return 500;
}
else {
return 500;
}
}
int proxy_response(struct proxy_transaction *t,
nta_outgoing_t *client,
sip_t const *sip)
{
int final;
if (sip) {
msg_t *response = nta_outgoing_getresponse(client);
final = sip->sip_status->st_status >= 200;
sip_via_remove(response, sip_object(response));
nta_incoming_mreply(t->server, response);
}
else {
final = 1;
nta_incoming_treply(t->server, SIP_408_REQUEST_TIMEOUT, TAG_END());
}
if (final)
proxy_transaction_destroy(t);
return 0;
}
struct proxy_transaction *
proxy_transaction_new(struct proxy *proxy)
{
struct proxy_transaction *t;
t = su_zalloc(proxy->home, sizeof *t);
if (t) {
t->proxy = proxy;
proxy_transaction_insert(&proxy->transactions, t);
}
return t;
}
static
void proxy_transaction_destroy(struct proxy_transaction *t)
{
if (t == t->proxy->stateless)
return;
proxy_transaction_remove(t);
nta_incoming_destroy(t->server);
nta_outgoing_destroy(t->client);
su_free(t->proxy->home, t->rq);
su_free(t->proxy->home, t);
}
LIST_BODIES(static, proxy_transaction, struct proxy_transaction, next, prev);
/* ---------------------------------------------------------------------- */
static
int domain_request(struct proxy *proxy,
nta_leg_t *leg,
nta_incoming_t *irq,
sip_t const *sip)
{
sip_method_t method = sip->sip_request->rq_method;
if (method == sip_method_register)
return process_register(proxy, irq, sip);
if (method == sip_method_options)
return process_options(proxy, irq, sip);
return 501;
}
static
int process_options(struct proxy *proxy,
nta_incoming_t *irq,
sip_t const *sip)
{
nta_incoming_treply(irq, SIP_200_OK,
SIPTAG_CONTACT(proxy->transport_contacts),
TAG_END());
return 200;
}
/* ---------------------------------------------------------------------- */
static int process_register2(struct proxy *p, auth_status_t *as,
nta_incoming_t *irq, sip_t const *sip);
static int set_status(auth_status_t *as, int status, char const *phrase);
static int validate_contacts(struct proxy *p, auth_status_t *as,
sip_t const *sip);
static int check_out_of_order(struct proxy *p, auth_status_t *as,
struct registration_entry *e, sip_t const *);
static int binding_update(struct proxy *p,
auth_status_t *as,
struct registration_entry *e,
sip_t const *sip);
sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings);
int process_register(struct proxy *proxy,
nta_incoming_t *irq,
sip_t const *sip)
{
auth_status_t *as;
msg_t *msg;
int status;
as = auth_status_new(proxy->home);
if (!as)
return 500;
as->as_method = sip->sip_request->rq_method_name;
msg = nta_incoming_getrequest(irq);
as->as_source = msg_addrinfo(msg);
msg_destroy(msg);
as->as_user_uri = sip->sip_from->a_url;
as->as_display = sip->sip_from->a_display;
if (sip->sip_payload)
as->as_body = sip->sip_payload->pl_data,
as->as_bodylen = sip->sip_payload->pl_len;
process_register2(proxy, as, irq, sip);
assert(as->as_status >= 200);
nta_incoming_treply(irq,
as->as_status, as->as_phrase,
SIPTAG_HEADER((void *)as->as_info),
SIPTAG_HEADER((void *)as->as_response),
TAG_END());
status = as->as_status;
su_home_unref(as->as_home);
return status;
}
static int process_register2(struct proxy *p,
auth_status_t *as,
nta_incoming_t *irq,
sip_t const *sip)
{
struct registration_entry *e = NULL;
auth_mod_check_client(p->auth, as, sip->sip_authorization,
registrar_challenger);
if (as->as_status)
return as->as_status;
assert(as->as_response == NULL);
if (validate_contacts(p, as, sip))
return as->as_status;
e = registration_entry_find(p, sip->sip_to->a_url);
if (!sip->sip_contact) {
as->as_response = (msg_header_t *)e->contacts;
return set_status(as, SIP_200_OK);
}
if (e && check_out_of_order(p, as, e, sip))
return as->as_status;
if (!e)
e = registration_entry_new(p, sip->sip_to->a_url);
if (!e)
return set_status(as, SIP_500_INTERNAL_SERVER_ERROR);
if (binding_update(p, as, e, sip))
return as->as_status;
msg_header_free(p->home, (void *)e->contacts);
e->contacts = binding_contacts(p->home, e->bindings);
as->as_response = (msg_header_t *)e->contacts;
return set_status(as, SIP_200_OK);
}
static int set_status(auth_status_t *as, int status, char const *phrase)
{
return as->as_phrase = phrase, as->as_status = status;
}
static int validate_contacts(struct proxy *p,
auth_status_t *as,
sip_t const *sip)
{
sip_contact_t const *m;
sip_time_t expires;
sip_time_t now = sip_now();
for (m = sip->sip_contact; m; m = m->m_next) {
if (m->m_url->url_type == url_any) {
if (!sip->sip_expires ||
sip->sip_expires->ex_delta ||
sip->sip_expires->ex_time ||
sip->sip_contact->m_next)
return set_status(as, SIP_400_BAD_REQUEST);
else
return 0;
}
expires = sip_contact_expires(m, sip->sip_expires, sip->sip_date,
p->prefs.expires, now);
if (expires > 0 && expires < p->prefs.min_expires) {
as->as_response = (msg_header_t *)
sip_min_expires_format(as->as_home, "%u",
(unsigned)p->prefs.min_expires);
return set_status(as, SIP_423_INTERVAL_TOO_BRIEF);
}
}
return 0;
}
/** Check for out-of-order register request */
static
int check_out_of_order(struct proxy *p,
auth_status_t *as,
struct registration_entry *e,
sip_t const *sip)
{
struct binding const *b;
sip_call_id_t const *id;
sip_contact_t *m;
if (e == NULL || !sip->sip_contact)
return 0;
id = sip->sip_call_id;
/* RFC 3261 subsection 10.3 step 6 and step 7 (p. 66): */
/* Check for reordered register requests */
for (b = e->bindings; b; b = b->next) {
if (binding_is_active(b) &&
strcmp(sip->sip_call_id->i_id, b->call_id->i_id) == 0 &&
sip->sip_cseq->cs_seq <= b->cseq) {
for (m = sip->sip_contact; m; m = m->m_next) {
if (m->m_url->url_type == url_any ||
url_cmp_all(m->m_url, b->contact->m_url) == 0)
return set_status(as, SIP_500_INTERNAL_SERVER_ERROR);
}
}
}
return 0;
}
static struct registration_entry *
registration_entry_find(struct proxy const *proxy, url_t const *uri)
{
struct registration_entry *e;
/* Our routing table */
for (e = proxy->entries; e; e = e->next) {
if (url_cmp(uri, e->aor) == 0)
return e;
}
return NULL;
}
static struct registration_entry *
registration_entry_new(struct proxy *proxy, url_t const *aor)
{
struct registration_entry *e;
e = su_zalloc(proxy->home, sizeof *e);
if (!e)
return NULL;
e->proxy = proxy;
e->aor = url_hdup(proxy->home, aor);
if (!e->aor) {
su_free(proxy->home, e);
return NULL;
}
registration_entry_insert(&proxy->entries, e);
return e;
}
static void
registration_entry_destroy(struct registration_entry *e)
{
if (e) {
registration_entry_remove(e);
su_free(e->proxy->home, e->aor);
while (e->bindings)
binding_destroy(e->proxy->home, e->bindings);
msg_header_free(e->proxy->home, (void *)e->contacts);
su_free(e->proxy->home, e);
}
}
LIST_BODIES(static, registration_entry, struct registration_entry, next, prev);
/* ---------------------------------------------------------------------- */
/* Bindings */
static
struct binding *binding_new(su_home_t *home,
sip_contact_t *contact,
sip_call_id_t *call_id,
uint32_t cseq,
sip_time_t registered,
sip_time_t expires)
{
struct binding *b;
b = su_zalloc(home, sizeof *b);
if (b) {
sip_contact_t m[1];
*m = *contact; m->m_next = NULL;
b->contact = sip_contact_dup(home, m);
b->call_id = sip_call_id_dup(home, call_id);
b->cseq = cseq;
b->registered = registered;
b->expires = expires;
if (!b->contact || !b->call_id)
binding_destroy(home, b), b = NULL;
if (b)
msg_header_remove_param(b->contact->m_common, "expires");
}
return b;
}
static
void binding_destroy(su_home_t *home, struct binding *b)
{
if (b->prev) {
if ((*b->prev = b->next))
b->next->prev = b->prev;
}
msg_header_free(home, (void *)b->contact);
msg_header_free(home, (void *)b->call_id);
su_free(home, b);
}
static
int binding_update(struct proxy *p,
auth_status_t *as,
struct registration_entry *e,
sip_t const *sip)
{
struct binding *b, *old, *next, *last, *bindings = NULL, **bb = &bindings;
sip_contact_t *m;
sip_time_t expires;
sip_time_t now = sip_now();
assert(sip->sip_contact);
/* Create new bindings */
for (m = sip->sip_contact; m; m = m->m_next) {
if (m->m_url->url_type == url_any)
break;
expires = sip_contact_expires(m, sip->sip_expires, sip->sip_date,
p->prefs.expires, now);
if (expires > p->prefs.max_expires)
expires = p->prefs.max_expires;
msg_header_remove_param(m->m_common, "expires");
b = binding_new(p->home, m, sip->sip_call_id, sip->sip_cseq->cs_seq,
now, now + expires);
if (!b)
break;
*bb = b, b->prev = bb, bb = &b->next;
}
last = NULL;
if (m == NULL) {
/* Merge new bindings with old ones */
for (old = e->bindings; old; old = next) {
next = old->next;
for (b = bindings; b != last; b = b->next) {
if (url_cmp_all(old->contact->m_url, b->contact->m_url) != 0)
continue;
if (strcmp(old->call_id->i_id, b->call_id->i_id) == 0) {
b->registered = old->registered;
}
binding_destroy(p->home, old);
break;
}
}
for (bb = &e->bindings; *bb; bb = &(*bb)->next)
;
if ((*bb = bindings))
bindings->prev = bb;
}
else if (m->m_url->url_type == url_any) {
/* Unregister all */
for (b = e->bindings; b; b = b->next) {
b->expires = now;
}
}
else {
/* Infernal error */
for (old = bindings; old; old = next) {
next = old->next;
binding_destroy(p->home, old);
}
return set_status(as, SIP_500_INTERNAL_SERVER_ERROR);
}
return 0;
}
sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings)
{
sip_contact_t *retval = NULL, **mm = &retval;
struct binding *b;
sip_time_t now = sip_now();
for (b = bindings; b; b = b->next) {
char const *expires;
if (b->expires <= now)
continue;
*mm = sip_contact_copy(home, b->contact);
if (*mm) {
expires = su_sprintf(home, "expires=%u", (unsigned)(b->expires - now));
msg_header_add_param(home, (*mm)->m_common, expires);
mm = &(*mm)->m_next;
}
}
return retval;
}