sip_dialog.c revision 40cb5e5daa7b80bb70fcf8dadfb20f9281566331
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include "sip_parse_uri.h"
#include "sip_msg.h"
#include "sip_hash.h"
#include "sip_miscdefs.h"
#include "sip_dialog.h"
#include "sip_parse_generic.h"
#define SIP_DLG_XCHG_FROM 0
#define SIP_DLG_XCHG_TO 1
/*
* Dialog state change callback function
*/
void (*sip_dlg_ulp_state_cb)(sip_dialog_t, sip_msg_t, int, int) = NULL;
void (*sip_ulp_dlg_del_cb)(sip_dialog_t, sip_msg_t, void *) = NULL;
boolean_t sip_incomplete_dialog(sip_dialog_t);
/*
* Exchange From/To header
*/
_sip_header_t *sip_dlg_xchg_from_to(sip_msg_t, int);
/*
* Complete dialog hash table
*/
sip_hash_t sip_dialog_hash[SIP_HASH_SZ];
/*
* Partial dialog hash table
*/
sip_hash_t sip_dialog_phash[SIP_HASH_SZ];
/*
* Route set structure
*/
typedef struct sip_dlg_route_set_s {
char *sip_dlg_route;
sip_str_t sip_dlg_ruri;
boolean_t sip_dlg_route_lr;
struct sip_dlg_route_set_s *sip_dlg_route_next;
}sip_dlg_route_set_t;
sip_dialog_t sip_seed_dialog(sip_conn_object_t, _sip_msg_t *,
boolean_t, int);
sip_dialog_t sip_complete_dialog(_sip_msg_t *, _sip_dialog_t *);
int sip_dialog_process(_sip_msg_t *, sip_dialog_t *);
void sip_dialog_delete(_sip_dialog_t *);
void sip_dialog_init();
sip_dialog_t sip_dialog_find(_sip_msg_t *);
boolean_t sip_dialog_match(void *, void *);
boolean_t sip_dialog_free(void *, void *, int *);
sip_dialog_t sip_update_dialog(sip_dialog_t, _sip_msg_t *);
char *sip_dialog_req_uri(sip_dialog_t);
static void sip_release_dialog_res(_sip_dialog_t *);
void sip_dlg_self_destruct(void *);
static int sip_dialog_get_route_set(_sip_dialog_t *, _sip_msg_t *,
int);
static void sip_dialog_free_rset(sip_dlg_route_set_t *);
/*
* Timer object for partial dialogs
*/
typedef struct sip_dialog_timer_obj_s {
_sip_dialog_t *dialog;
void (*func)(sip_dialog_t, sip_msg_t, void *);
} sip_dialog_timer_obj_t;
/*
* To avoid duplication all over the place
*/
static void
sip_release_dialog_res(_sip_dialog_t *dialog)
{
assert(dialog->sip_dlg_ref_cnt == 0);
if (SIP_IS_TIMER_RUNNING(dialog->sip_dlg_timer))
SIP_CANCEL_TIMER(dialog->sip_dlg_timer);
if (dialog->sip_dlg_call_id != NULL)
sip_free_header(dialog->sip_dlg_call_id);
if (dialog->sip_dlg_local_uri_tag != NULL)
sip_free_header(dialog->sip_dlg_local_uri_tag);
if (dialog->sip_dlg_remote_uri_tag != NULL)
sip_free_header(dialog->sip_dlg_remote_uri_tag);
if (dialog->sip_dlg_remote_target != NULL)
sip_free_header(dialog->sip_dlg_remote_target);
if (dialog->sip_dlg_route_set != NULL)
sip_free_header(dialog->sip_dlg_route_set);
if (dialog->sip_dlg_event != NULL)
sip_free_header(dialog->sip_dlg_event);
if (dialog->sip_dlg_req_uri.sip_str_ptr != NULL) {
free(dialog->sip_dlg_req_uri.sip_str_ptr);
dialog->sip_dlg_req_uri.sip_str_ptr = NULL;
dialog->sip_dlg_req_uri.sip_str_len = 0;
}
if (dialog->sip_dlg_rset.sip_str_ptr != NULL) {
free(dialog->sip_dlg_rset.sip_str_ptr);
dialog->sip_dlg_rset.sip_str_len = 0;
dialog->sip_dlg_rset.sip_str_ptr = NULL;
}
(void) pthread_mutex_destroy(&dialog->sip_dlg_mutex);
free(dialog);
}
/*
* Get the route information from the 'value' and add it to the route
* set.
*/
static sip_dlg_route_set_t *
sip_add_route_to_set(sip_hdr_value_t *value)
{
int vlen = 0;
sip_dlg_route_set_t *rset;
char *crlf;
const sip_param_t *uri_param;
int error;
rset = calloc(1, sizeof (*rset));
if (rset == NULL)
return (NULL);
rset->sip_dlg_route_next = NULL;
vlen = value->sip_value_end - value->sip_value_start;
/*
* check for CRLF
*/
crlf = value->sip_value_end - strlen(SIP_CRLF);
while (crlf != NULL && strncmp(crlf, SIP_CRLF, strlen(SIP_CRLF)) == 0) {
vlen -= strlen(SIP_CRLF);
crlf -= strlen(SIP_CRLF);
}
rset->sip_dlg_route = calloc(1, vlen + 1);
if (rset->sip_dlg_route == NULL) {
free(rset);
return (NULL);
}
/*
* loose routing
*/
rset->sip_dlg_route_lr = B_FALSE;
(void) strncpy(rset->sip_dlg_route, value->sip_value_start, vlen);
rset->sip_dlg_ruri.sip_str_ptr = rset->sip_dlg_route +
(value->cftr_uri.sip_str_ptr - value->sip_value_start);
rset->sip_dlg_ruri.sip_str_len = value->cftr_uri.sip_str_len;
rset->sip_dlg_route[vlen] = '\0';
assert(value->sip_value_parsed_uri != NULL);
/*
* Check if the 'lr' param is present for this route.
*/
uri_param = sip_get_uri_params(value->sip_value_parsed_uri, &error);
if (error != 0) {
free(rset->sip_dlg_route);
free(rset);
return (NULL);
}
if (uri_param != NULL) {
rset->sip_dlg_route_lr = sip_is_param_present(uri_param, "lr",
strlen("lr"));
}
return (rset);
}
/*
* Depending on the route-set, determine the request URI.
*/
char *
sip_dialog_req_uri(sip_dialog_t dialog)
{
const sip_str_t *req_uri;
char *uri;
_sip_dialog_t *_dialog;
_dialog = (_sip_dialog_t *)dialog;
if (_dialog->sip_dlg_route_set == NULL ||
_dialog->sip_dlg_req_uri.sip_str_ptr == NULL) {
const struct sip_value *val;
val = sip_get_header_value(_dialog->sip_dlg_remote_target,
NULL);
if (val == NULL)
return (NULL);
req_uri = &((sip_hdr_value_t *)val)->cftr_uri;
} else {
req_uri = &_dialog->sip_dlg_req_uri;
}
uri = (char *)malloc(req_uri->sip_str_len + 1);
if (uri == NULL)
return (NULL);
(void) strncpy(uri, req_uri->sip_str_ptr, req_uri->sip_str_len);
uri[req_uri->sip_str_len] = '\0';
return (uri);
}
/*
* Free the route set.
*/
void
sip_dialog_free_rset(sip_dlg_route_set_t *rset)
{
sip_dlg_route_set_t *next;
while (rset != NULL) {
next = rset->sip_dlg_route_next;
rset->sip_dlg_route_next = NULL;
free(rset->sip_dlg_route);
free(rset);
rset = next;
}
}
/*
* Recompute route-set
*/
static int
sip_dlg_recompute_rset(_sip_dialog_t *dialog, _sip_msg_t *sip_msg, int what)
{
int ret;
if (dialog->sip_dlg_route_set != NULL) {
sip_free_header(dialog->sip_dlg_route_set);
dialog->sip_dlg_route_set = NULL;
}
if (dialog->sip_dlg_req_uri.sip_str_ptr != NULL) {
free(dialog->sip_dlg_req_uri.sip_str_ptr);
dialog->sip_dlg_req_uri.sip_str_ptr = NULL;
dialog->sip_dlg_req_uri.sip_str_len = 0;
}
if (dialog->sip_dlg_rset.sip_str_ptr != NULL) {
free(dialog->sip_dlg_rset.sip_str_ptr);
dialog->sip_dlg_rset.sip_str_ptr = NULL;
dialog->sip_dlg_rset.sip_str_len = 0;
}
ret = sip_dialog_get_route_set(dialog, sip_msg, what);
return (ret);
}
/*
* If the route set is empty, the UAC MUST place the remote target URI
* into the Request-URI. The UAC MUST NOT add a Route header field to
* the request.
*
* If the route set is not empty, and the first URI in the route set
* contains the lr parameter (see Section 19.1.1), the UAC MUST place
* the remote target URI into the Request-URI and MUST include a Route
* header field containing the route set values in order, including all
* parameters.
*
* If the route set is not empty, and its first URI does not contain the
* lr parameter, the UAC MUST place the first URI from the route set
* into the Request-URI, stripping any parameters that are not allowed
* in a Request-URI. The UAC MUST add a Route header field containing
* the remainder of the route set values in order, including all
* parameters. The UAC MUST then place the remote target URI into the
* Route header field as the last value.
*/
int
sip_dialog_set_route_hdr(_sip_dialog_t *dialog, sip_dlg_route_set_t *rset_head,
int rcnt, int rlen)
{
size_t rset_len;
_sip_header_t *rhdr;
char *rset;
char *rp;
char *rsp;
int count;
sip_dlg_route_set_t *route;
boolean_t first = B_TRUE;
const sip_str_t *to_uri;
char *uri = NULL;
int rspl;
int rpl;
assert(rcnt > 0);
dialog->sip_dlg_rset.sip_str_len = rlen + rcnt - 1;
dialog->sip_dlg_rset.sip_str_ptr = malloc(rlen + rcnt);
if (dialog->sip_dlg_rset.sip_str_ptr == NULL)
return (ENOMEM);
rsp = dialog->sip_dlg_rset.sip_str_ptr;
rspl = rlen + rcnt;
route = rset_head;
rset_len = rlen;
if (!route->sip_dlg_route_lr) {
const struct sip_value *val;
val = sip_get_header_value(dialog->sip_dlg_remote_target, NULL);
to_uri = &((sip_hdr_value_t *)val)->cftr_uri;
uri = (char *)malloc(to_uri->sip_str_len + 1);
if (uri == NULL) {
free(dialog->sip_dlg_rset.sip_str_ptr);
dialog->sip_dlg_rset.sip_str_len = 0;
dialog->sip_dlg_rset.sip_str_ptr = NULL;
return (ENOMEM);
}
(void) strncpy(uri, to_uri->sip_str_ptr, to_uri->sip_str_len);
uri[to_uri->sip_str_len] = '\0';
rset_len = rlen - strlen(route->sip_dlg_route) + strlen(uri) +
SIP_SPACE_LEN + sizeof (char) + SIP_SPACE_LEN +
sizeof (char);
count = snprintf(rsp, rspl, "%s", route->sip_dlg_route);
dialog->sip_dlg_req_uri.sip_str_ptr = malloc(
route->sip_dlg_ruri.sip_str_len + 1);
if (dialog->sip_dlg_req_uri.sip_str_ptr == NULL) {
free(uri);
free(dialog->sip_dlg_rset.sip_str_ptr);
dialog->sip_dlg_rset.sip_str_len = 0;
dialog->sip_dlg_rset.sip_str_ptr = NULL;
return (ENOMEM);
}
(void) strncpy(dialog->sip_dlg_req_uri.sip_str_ptr, rsp +
(route->sip_dlg_ruri.sip_str_ptr - route->sip_dlg_route),
route->sip_dlg_ruri.sip_str_len);
dialog->sip_dlg_req_uri.sip_str_ptr[
route->sip_dlg_ruri.sip_str_len] = '\0';
dialog->sip_dlg_req_uri.sip_str_len =
route->sip_dlg_ruri.sip_str_len;
rsp += count;
rspl -= count;
route = route->sip_dlg_route_next;
}
/*
* rcnt - 1 is for the number of COMMAs
*/
rset_len += strlen(SIP_ROUTE) + SIP_SPACE_LEN + sizeof (char) +
SIP_SPACE_LEN + rcnt - 1;
rset = malloc(rset_len + 1);
if (rset == NULL) {
free(dialog->sip_dlg_rset.sip_str_ptr);
dialog->sip_dlg_rset.sip_str_len = 0;
dialog->sip_dlg_rset.sip_str_ptr = NULL;
return (ENOMEM);
}
rhdr = sip_new_header(rset_len + strlen(SIP_CRLF));
if (rhdr == NULL) {
free(rset);
free(dialog->sip_dlg_rset.sip_str_ptr);
dialog->sip_dlg_rset.sip_str_len = 0;
dialog->sip_dlg_rset.sip_str_ptr = NULL;
return (ENOMEM);
}
rp = rset;
rpl = rset_len + 1;
count = snprintf(rp, rpl, "%s %c ", SIP_ROUTE, SIP_HCOLON);
rp += count;
rpl -= count;
while (route != NULL) {
if (first) {
count = snprintf(rp, rpl, "%s", route->sip_dlg_route);
rp += count;
rpl -= count;
first = B_FALSE;
if (uri != NULL) {
count = snprintf(rsp, rspl, "%c%s",
SIP_COMMA, route->sip_dlg_route);
} else {
count = snprintf(rsp, rspl, "%s",
route->sip_dlg_route);
}
rsp += count;
rspl -= count;
} else {
count = snprintf(rp, rpl, "%c%s", SIP_COMMA,
route->sip_dlg_route);
rp += count;
rpl -= count;
count = snprintf(rsp, rspl, "%c%s", SIP_COMMA,
route->sip_dlg_route);
rsp += count;
rspl -= count;
}
route = route->sip_dlg_route_next;
}
assert(rsp <= dialog->sip_dlg_rset.sip_str_ptr +
dialog->sip_dlg_rset.sip_str_len);
dialog->sip_dlg_rset.sip_str_ptr[dialog->sip_dlg_rset.sip_str_len] =
'\0';
if (uri != NULL) {
if (first) {
count = snprintf(rp, rpl, "%c %s %c", SIP_LAQUOT,
uri, SIP_RAQUOT);
} else {
count = snprintf(rp, rpl, "%c%c %s %c", SIP_COMMA,
SIP_LAQUOT, uri, SIP_RAQUOT);
}
rp += count;
rpl -= count;
free(uri);
}
assert(rp <= rset + rset_len);
(void) snprintf(rhdr->sip_hdr_start, rset_len + strlen(SIP_CRLF) + 1,
"%s%s", rset, SIP_CRLF);
free(rset);
dialog->sip_dlg_route_set = (sip_header_t)rhdr;
sip_dialog_free_rset(rset_head);
return (0);
}
/*
* UAC Behavior
* The route set MUST be set to the list of URIs in the Record-Route
* header field from the response, taken in reverse order and preserving
* all URI parameters.
*
* UAS behavior
* The route set MUST be set to the list of URIs in the Record-Route
* header field from the request, taken in order and preserving all URI
* parameters.
*/
static int
sip_dialog_get_route_set(_sip_dialog_t *dialog, _sip_msg_t *sip_msg, int what)
{
sip_header_t rrhdr;
sip_hdr_value_t *value;
int error;
sip_dlg_route_set_t *rset_head = NULL;
sip_dlg_route_set_t *rset_tail = NULL;
sip_dlg_route_set_t *rset;
int rset_cnt = 0;
int rset_len = 0;
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
rrhdr = sip_search_for_header(sip_msg, SIP_RECORD_ROUTE, NULL);
while (rrhdr != NULL) {
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
value = (sip_hdr_value_t *)sip_get_header_value(rrhdr, &error);
while (value != NULL && error == 0) {
char *crlf;
if (value->sip_value_state == SIP_VALUE_BAD) {
value = (sip_hdr_value_t *)sip_get_next_value(
(sip_header_value_t)value, &error);
continue;
}
rset = sip_add_route_to_set(value);
if (rset == NULL)
goto r_error;
/*
* Add one for COMMA
*/
rset_cnt++;
rset_len += (value->sip_value_end -
value->sip_value_start);
/*
* Check for CRLF
*/
crlf = value->sip_value_end - strlen(SIP_CRLF);
while (crlf != NULL &&
strncmp(crlf, SIP_CRLF, strlen(SIP_CRLF)) == 0) {
rset_len -= strlen(SIP_CRLF);
crlf -= strlen(SIP_CRLF);
}
if (rset_head == NULL) {
assert(rset_tail == NULL);
rset_head = rset_tail = rset;
} else if (what == SIP_UAS_DIALOG) {
rset_tail->sip_dlg_route_next = rset;
rset_tail = rset;
} else if (what == SIP_UAC_DIALOG) {
rset->sip_dlg_route_next = rset_head;
rset_head = rset;
} else {
assert(0);
}
value = (sip_hdr_value_t *)sip_get_next_value(
(sip_header_value_t)value, &error);
}
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
rrhdr = sip_search_for_header(sip_msg, SIP_RECORD_ROUTE, rrhdr);
}
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
if (rset_cnt == 0)
return (0);
if (sip_dialog_set_route_hdr(dialog, rset_head, rset_cnt,
rset_len) != 0) {
goto r_error;
}
return (0);
r_error:
sip_dialog_free_rset(rset_head);
return (ENOMEM);
}
/*
* UAS behavior:
* The remote sequence number MUST be set to the value of the sequence
* number in the CSeq header field of the request. The local sequence
* number MUST be empty. The call identifier component of the dialog ID
* MUST be set to the value of the Call-ID in the request. The local
* tag component of the dialog ID MUST be set to the tag in the To field
* in the response to the request (which always includes a tag), and the
* remote tag component of the dialog ID MUST be set to the tag from the
* From field in the request. A UAS MUST be prepared to receive a
* request without a tag in the From field, in which case the tag is
* considered to have a value of null.
* The remote URI MUST be set to the URI in the From field, and the
* local URI MUST be set to the URI in the To field.
* The remote target MUST be set to the URI from the Contact header field
* of the request.
*
* UAC behavior:
* The local sequence number MUST be set to the value of the sequence
* number in the CSeq header field of the request. The remote sequence
* number MUST be empty (it is established when the remote UA sends a
* request within the dialog). The call identifier component of the
* dialog ID MUST be set to the value of the Call-ID in the request.
* The local tag component of the dialog ID MUST be set to the tag in
* the From field in the request, and the remote tag component of the
* dialog ID MUST be set to the tag in the To field of the response. A
* UAC MUST be prepared to receive a response without a tag in the To
* field, in which case the tag is considered to have a value of null.
* The remote URI MUST be set to the URI in the To field, and the local
* URI MUST be set to the URI in the From field.
* The remote target MUST be set to the URI from the Contact header field
* of the response.
*/
/*
* This is the routine that seeds a dialog.
*/
sip_dialog_t
sip_seed_dialog(sip_conn_object_t obj, _sip_msg_t *sip_msg,
boolean_t dlg_on_fork, int dlg_type)
{
_sip_dialog_t *dialog;
int cseq;
sip_header_t fhdr = NULL;
sip_header_t thdr = NULL;
sip_header_t chdr;
sip_header_t cihdr;
sip_header_t evhdr = NULL;
const struct sip_value *value;
sip_dialog_timer_obj_t *tim_obj = NULL;
const sip_str_t *callid;
sip_method_t method;
int timer1 = sip_timer_T1;
int error;
if (!sip_msg_is_request((sip_msg_t)sip_msg, &error))
return (NULL);
method = sip_get_request_method((sip_msg_t)sip_msg, &error);
/*
* Only INVITE and SUBSCRIBE supported
*/
if (error != 0 || (method != INVITE && method != SUBSCRIBE))
return (NULL);
/*
* A request outside of a dialog MUST NOT contain a To tag
*/
if (sip_get_to_tag((sip_msg_t)sip_msg, NULL) != NULL)
return (NULL);
if (dlg_type == SIP_UAS_DIALOG) {
thdr = sip_dlg_xchg_from_to((sip_msg_t)sip_msg,
SIP_DLG_XCHG_FROM);
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
} else {
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
fhdr = sip_search_for_header(sip_msg, SIP_FROM, NULL);
}
cihdr = sip_search_for_header(sip_msg, SIP_CALL_ID, NULL);
chdr = sip_search_for_header(sip_msg, SIP_CONTACT, NULL);
if (method == SUBSCRIBE)
evhdr = sip_search_for_header(sip_msg, SIP_EVENT, NULL);
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
if ((fhdr == NULL && thdr == NULL) || cihdr == NULL || chdr == NULL ||
(method == SUBSCRIBE && evhdr == NULL)) {
if (thdr != NULL)
sip_free_header(thdr);
return (NULL);
}
/*
* Sanity check since we just store the headers in the dialog
*/
if (sip_get_from_tag((sip_msg_t)sip_msg, NULL) == NULL ||
sip_get_from_uri_str((sip_msg_t)sip_msg, NULL) == NULL ||
((cseq = sip_get_callseq_num((sip_msg_t)sip_msg, NULL)) == -1) ||
(callid = sip_get_callid((sip_msg_t)sip_msg, NULL)) == NULL ||
sip_get_to_uri_str((sip_msg_t)sip_msg, NULL) == NULL ||
((value = sip_get_header_value(chdr, NULL)) == NULL) ||
sip_get_contact_uri_str((sip_header_value_t)value, NULL) == NULL) {
if (thdr != NULL)
sip_free_header(thdr);
return (NULL);
}
tim_obj = calloc(1, sizeof (sip_dialog_timer_obj_t));
if (tim_obj == NULL) {
if (thdr != NULL)
sip_free_header(thdr);
return (NULL);
}
dialog = calloc(1, sizeof (_sip_dialog_t));
if (dialog == NULL) {
if (thdr != NULL)
sip_free_header(thdr);
return (NULL);
}
/*
* We will take the TO header with the tag when we complete this
* dialog
*/
if (dlg_type == SIP_UAS_DIALOG) {
dialog->sip_dlg_remote_uri_tag = thdr;
/*
* We take the remote target from the incoming request on the
* UAS. For the UAC, we will take it from the response.
*/
if ((dialog->sip_dlg_remote_target = sip_dup_header(chdr)) ==
NULL) {
goto dia_err;
}
} else {
if ((dialog->sip_dlg_local_uri_tag = sip_dup_header(fhdr)) ==
NULL) {
goto dia_err;
}
}
if ((dialog->sip_dlg_call_id = sip_dup_header(cihdr)) == NULL)
goto dia_err;
if (method == SUBSCRIBE) {
dialog->sip_dlg_event = sip_dup_header(evhdr);
if (dialog->sip_dlg_event == NULL) {
goto dia_err;
}
}
dialog->sip_dlg_rset.sip_str_ptr = NULL;
dialog->sip_dlg_rset.sip_str_len = 0;
dialog->sip_dlg_req_uri.sip_str_ptr = NULL;
dialog->sip_dlg_req_uri.sip_str_len = 0;
/*
* Get the route set from the request, if present
*/
if (dlg_type == SIP_UAS_DIALOG &&
sip_dialog_get_route_set(dialog, sip_msg, dlg_type) != 0) {
goto dia_err;
}
if (dlg_type == SIP_UAC_DIALOG)
dialog->sip_dlg_local_cseq = cseq;
else
dialog->sip_dlg_remote_cseq = cseq;
dialog->sip_dlg_type = dlg_type;
dialog->sip_dlg_on_fork = dlg_on_fork;
dialog->sip_dlg_method = method;
/*
* Set the partial dialog timer with the INVITE timeout val
*/
if (sip_conn_timer1 != NULL)
timer1 = sip_conn_timer1(obj);
SIP_INIT_TIMER(dialog->sip_dlg_timer, 64 * timer1);
tim_obj->dialog = dialog;
/*
* Since at the client we never pass the partial dialog, we need not
* invoke the callback when the partial dialog self-destructs.
*/
if (dlg_type == SIP_UAS_DIALOG)
tim_obj->func = sip_ulp_dlg_del_cb;
SIP_SCHED_TIMER(dialog->sip_dlg_timer, (void *)tim_obj,
sip_dlg_self_destruct);
if (!SIP_IS_TIMER_RUNNING(dialog->sip_dlg_timer))
goto dia_err;
(void) pthread_mutex_init(&dialog->sip_dlg_mutex, NULL);
if (dlg_type == SIP_UAC_DIALOG) {
const sip_str_t *local_tag;
local_tag = sip_get_from_tag((sip_msg_t)sip_msg, NULL);
assert(local_tag != NULL);
sip_md5_hash(local_tag->sip_str_ptr, local_tag->sip_str_len,
callid->sip_str_ptr, callid->sip_str_len,
NULL, 0, NULL, 0, NULL, 0, NULL, 0,
(uchar_t *)dialog->sip_dlg_id);
/*
* Add it to the partial hash table
*/
if (sip_hash_add(sip_dialog_phash, (void *)dialog,
SIP_DIGEST_TO_HASH(dialog->sip_dlg_id)) != 0) {
goto dia_err;
}
}
SIP_DLG_REFCNT_INCR(dialog);
return ((sip_dialog_t)dialog);
dia_err:
sip_release_dialog_res(dialog);
if (SIP_IS_TIMER_RUNNING(dialog->sip_dlg_timer))
SIP_CANCEL_TIMER(dialog->sip_dlg_timer);
if (tim_obj != NULL)
free(tim_obj);
return (NULL);
}
/*
* When creating a dialog from a NOTIFY request, we need to get the FROM
* header for the dialog from the TO header of the NOTIFY.
*/
_sip_header_t *
sip_dlg_xchg_from_to(sip_msg_t sip_msg, int what)
{
int len;
_sip_header_t *newhdr;
int cnt;
const struct sip_header *hdr;
int hdrsize;
int error;
hdr = sip_get_header(sip_msg, what == SIP_DLG_XCHG_FROM ? SIP_FROM :
SIP_TO, NULL, &error);
if (error != 0 || hdr == NULL)
return (NULL);
if (sip_parse_goto_values((_sip_header_t *)hdr) != 0)
return (NULL);
len = hdr->sip_hdr_end - hdr->sip_hdr_current;
if (what == SIP_DLG_XCHG_FROM) {
hdrsize = len + strlen(SIP_TO) + SIP_SPACE_LEN + sizeof (char) +
SIP_SPACE_LEN;
} else {
hdrsize = len + strlen(SIP_FROM) + SIP_SPACE_LEN +
sizeof (char) + SIP_SPACE_LEN;
}
newhdr = sip_new_header(hdrsize);
if (newhdr == NULL)
return (NULL);
if (what == SIP_DLG_XCHG_FROM) {
cnt = snprintf(newhdr->sip_hdr_current, hdrsize + 1,
"%s %c ", SIP_TO, SIP_HCOLON);
} else {
cnt = snprintf(newhdr->sip_hdr_current, hdrsize + 1,
"%s %c ", SIP_FROM, SIP_HCOLON);
}
newhdr->sip_hdr_current += cnt;
(void) strncpy(newhdr->sip_hdr_current, hdr->sip_hdr_current, len);
newhdr->sip_hdr_current += len;
assert(newhdr->sip_hdr_current == newhdr->sip_hdr_end);
assert(hdr->sip_header_functions != NULL);
/*
* FROM and TO have common parsing functions
*/
newhdr->sip_header_functions = hdr->sip_header_functions;
newhdr->sip_hdr_current = newhdr->sip_hdr_start;
return (newhdr);
}
/*
* This is the response that completes the dialog that was created
* in sip_seed_dialog().
*/
sip_dialog_t
sip_complete_dialog(_sip_msg_t *sip_msg, _sip_dialog_t *dialog)
{
_sip_header_t *thdr;
_sip_header_t *evhdr = NULL;
_sip_header_t *substate = NULL;
sip_header_t chdr = NULL;
int resp_code;
const sip_str_t *ttag;
const sip_str_t *remtag;
const sip_str_t *callid;
const struct sip_value *val;
sip_method_t method;
int error = 0;
int prev_state;
boolean_t alloc_thdr = B_FALSE;
if (sip_msg_is_request((sip_msg_t)sip_msg, &error) && error == 0)
method = sip_get_request_method((sip_msg_t)sip_msg, &error);
else
method = sip_get_callseq_method((sip_msg_t)sip_msg, &error);
if (error != 0 || dialog == NULL ||
(sip_msg_is_request((sip_msg_t)sip_msg, &error) &&
(dialog->sip_dlg_method == INVITE || method != NOTIFY))) {
return (NULL);
}
if ((dialog->sip_dlg_type == SIP_UAC_DIALOG && method != NOTIFY &&
sip_get_callseq_num((sip_msg_t)sip_msg, NULL) !=
dialog->sip_dlg_local_cseq) ||
(dialog->sip_dlg_type == SIP_UAS_DIALOG && method != NOTIFY &&
sip_get_callseq_num((sip_msg_t)sip_msg, NULL) !=
dialog->sip_dlg_remote_cseq)) {
return (NULL);
}
if (method == NOTIFY) {
const sip_str_t *sstate;
thdr = sip_dlg_xchg_from_to((sip_msg_t)sip_msg,
SIP_DLG_XCHG_FROM);
if (thdr == NULL)
return (NULL);
alloc_thdr = B_TRUE;
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
chdr = sip_search_for_header(sip_msg, SIP_CONTACT, NULL);
if (chdr == NULL) {
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
sip_free_header(thdr);
return (NULL);
}
evhdr = sip_search_for_header(sip_msg, SIP_EVENT, NULL);
if (evhdr == NULL) {
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
sip_free_header(thdr);
return (NULL);
}
substate = sip_search_for_header(sip_msg,
SIP_SUBSCRIPTION_STATE, NULL);
if (substate == NULL) {
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
sip_free_header(thdr);
return (NULL);
}
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
sstate = sip_get_substate((sip_msg_t)sip_msg, &error);
if (sstate == NULL || error != 0) {
sip_free_header(thdr);
return (NULL);
}
if ((sstate->sip_str_len != strlen("pending") &&
sstate->sip_str_len != strlen("active")) ||
((sstate->sip_str_len == strlen("pending") &&
strncasecmp(sstate->sip_str_ptr, "pending",
strlen("pending")) != 0) ||
(sstate->sip_str_len == strlen("active") &&
strncasecmp(sstate->sip_str_ptr, "active",
strlen("active")) != 0))) {
sip_free_header(thdr);
return (NULL);
}
ttag = sip_get_from_tag((sip_msg_t)sip_msg, NULL);
} else {
if (dialog->sip_dlg_type == SIP_UAS_DIALOG) {
thdr = sip_dlg_xchg_from_to((sip_msg_t)sip_msg,
SIP_DLG_XCHG_TO);
alloc_thdr = B_TRUE;
} else {
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
thdr = sip_search_for_header(sip_msg, SIP_TO, NULL);
if (dialog->sip_dlg_remote_target == NULL) {
chdr = sip_search_for_header(sip_msg,
SIP_CONTACT, NULL);
}
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
}
if (thdr == NULL) {
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
return (NULL);
}
ttag = sip_get_to_tag((sip_msg_t)sip_msg, NULL);
}
if (ttag == NULL) {
if (alloc_thdr)
sip_free_header(thdr);
return (NULL);
}
prev_state = dialog->sip_dlg_state;
if (method == NOTIFY) {
int error;
const sip_str_t *dlg_id_val = NULL;
const sip_str_t *event;
const sip_str_t *id_val = NULL;
sip_header_value_t ev_val;
sip_hdr_value_t *dlg_ev_val = NULL;
event = sip_get_event((sip_msg_t)sip_msg, &error);
if (event == NULL || error != 0) {
sip_free_header(thdr);
return (NULL);
}
ev_val = (sip_header_value_t)sip_get_header_value(evhdr,
&error);
if (ev_val != NULL)
id_val = sip_get_param_value(ev_val, "id", &error);
if (error == 0) {
dlg_ev_val = (sip_hdr_value_t *)sip_get_header_value(
dialog->sip_dlg_event, &error);
}
if (dlg_ev_val == NULL || error != 0) {
sip_free_header(thdr);
return (NULL);
}
dlg_id_val = sip_get_param_value((sip_header_value_t)dlg_ev_val,
"id", &error);
if (error != 0 ||
dlg_ev_val->str_val_len != event->sip_str_len ||
strncmp(dlg_ev_val->str_val_ptr, event->sip_str_ptr,
event->sip_str_len != 0)) {
sip_free_header(thdr);
return (NULL);
}
if ((dlg_id_val == NULL && id_val != NULL) ||
(dlg_id_val != NULL && id_val == NULL)) {
sip_free_header(thdr);
return (NULL);
} else if (dlg_id_val != NULL && id_val != NULL) {
if (dlg_id_val->sip_str_len != id_val->sip_str_len ||
strncasecmp(dlg_id_val->sip_str_ptr,
id_val->sip_str_ptr, dlg_id_val->sip_str_len) !=
0) {
sip_free_header(thdr);
return (NULL);
}
}
if (dialog->sip_dlg_type == SIP_UAC_DIALOG) {
dialog->sip_dlg_remote_uri_tag = thdr;
if ((dialog->sip_dlg_remote_target =
sip_dup_header(chdr)) == NULL) {
sip_free_header(thdr);
return (NULL);
}
} else {
dialog->sip_dlg_local_uri_tag = thdr;
}
dialog->sip_dlg_state = SIP_DLG_CONFIRMED;
} else {
resp_code = sip_get_response_code((sip_msg_t)sip_msg, &error);
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
assert(dialog->sip_dlg_state == SIP_DLG_NEW);
if (dialog->sip_dlg_remote_target == NULL && chdr != NULL) {
assert(dialog->sip_dlg_type == SIP_UAC_DIALOG);
if ((dialog->sip_dlg_remote_target =
sip_dup_header(chdr)) == NULL) {
(void) pthread_mutex_unlock(
&dialog->sip_dlg_mutex);
sip_dialog_terminate(dialog,
(sip_msg_t)sip_msg);
if (alloc_thdr)
sip_free_header(thdr);
return (NULL);
}
if (sip_dialog_get_route_set(dialog, sip_msg,
dialog->sip_dlg_type) != 0) {
(void) pthread_mutex_unlock(
&dialog->sip_dlg_mutex);
sip_dialog_terminate(dialog,
(sip_msg_t)sip_msg);
if (alloc_thdr)
sip_free_header(thdr);
return (NULL);
}
}
if (SIP_PROVISIONAL_RESP(resp_code)) {
dialog->sip_dlg_state = SIP_DLG_EARLY;
} else if (SIP_OK_RESP(resp_code)) {
/*
* Per 12.1 the UAS must include the contact header
* for a dialog establishing response, so if we
* don't find one, we terminate it.
*/
if (dialog->sip_dlg_remote_target == NULL) {
(void) pthread_mutex_unlock(
&dialog->sip_dlg_mutex);
if (sip_ulp_dlg_del_cb != NULL) {
sip_ulp_dlg_del_cb(dialog,
(sip_msg_t)sip_msg, NULL);
}
sip_dialog_terminate(dialog,
(sip_msg_t)sip_msg);
if (alloc_thdr)
sip_free_header(thdr);
return (NULL);
}
dialog->sip_dlg_state = SIP_DLG_CONFIRMED;
} else {
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
if (sip_ulp_dlg_del_cb != NULL) {
sip_ulp_dlg_del_cb(dialog, (sip_msg_t)sip_msg,
NULL);
}
sip_dialog_terminate(dialog, (sip_msg_t)sip_msg);
if (alloc_thdr)
sip_free_header(thdr);
return (NULL);
}
if (dialog->sip_dlg_type == SIP_UAS_DIALOG) {
dialog->sip_dlg_local_uri_tag = thdr;
} else {
if ((dialog->sip_dlg_remote_uri_tag =
sip_dup_header(thdr)) == NULL) {
(void) pthread_mutex_unlock(
&dialog->sip_dlg_mutex);
sip_dialog_terminate(dialog,
(sip_msg_t)sip_msg);
return (NULL);
}
}
}
/*
* Cancel the partial dialog timer
*/
if (SIP_IS_TIMER_RUNNING(dialog->sip_dlg_timer))
SIP_CANCEL_TIMER(dialog->sip_dlg_timer);
if (dialog->sip_dlg_type == SIP_UAC_DIALOG) {
val = sip_get_header_value(dialog->sip_dlg_local_uri_tag,
&error);
} else {
val = sip_get_header_value(dialog->sip_dlg_remote_uri_tag,
&error);
}
assert(val != NULL && error == 0);
remtag = sip_get_param_value((sip_header_value_t)val, "tag", &error);
val = sip_get_header_value(dialog->sip_dlg_call_id, &error);
callid = &((sip_hdr_value_t *)val)->str_val;
/*
* Get an ID for this dialog
*/
if (dialog->sip_dlg_type == SIP_UAC_DIALOG) {
sip_md5_hash(remtag->sip_str_ptr, remtag->sip_str_len,
ttag->sip_str_ptr, ttag->sip_str_len,
callid->sip_str_ptr, callid->sip_str_len,
NULL, 0, NULL, 0, NULL, 0, (uchar_t *)dialog->sip_dlg_id);
} else {
sip_md5_hash(ttag->sip_str_ptr, ttag->sip_str_len,
remtag->sip_str_ptr, remtag->sip_str_len,
callid->sip_str_ptr, callid->sip_str_len,
NULL, 0, NULL, 0, NULL, 0, (uchar_t *)dialog->sip_dlg_id);
}
SIP_DLG_REFCNT_INCR(dialog);
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
/*
* Add it to the hash table
*/
if (sip_hash_add(sip_dialog_hash, (void *)dialog,
SIP_DIGEST_TO_HASH(dialog->sip_dlg_id)) != 0) {
/*
* So that sip_dialog_delete() does not try to remove
* this from the hash table.
*/
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
if (dialog->sip_dlg_type == SIP_UAS_DIALOG) {
sip_free_header(dialog->sip_dlg_local_uri_tag);
dialog->sip_dlg_local_uri_tag = NULL;
} else {
sip_free_header(dialog->sip_dlg_remote_uri_tag);
dialog->sip_dlg_remote_uri_tag = NULL;
}
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
sip_dialog_terminate(dialog, (sip_msg_t)sip_msg);
return (NULL);
}
if (sip_dlg_ulp_state_cb != NULL) {
sip_dlg_ulp_state_cb((sip_dialog_t)dialog,
(sip_msg_t)sip_msg, prev_state, dialog->sip_dlg_state);
}
return ((sip_dialog_t)dialog);
}
/*
* Check if this dialog is a match.
*/
boolean_t
sip_dialog_match(void *obj, void *hindex)
{
_sip_dialog_t *dialog = (_sip_dialog_t *)obj;
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
if (dialog->sip_dlg_state == SIP_DLG_DESTROYED) {
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_FALSE);
}
if (bcmp(dialog->sip_dlg_id, hindex,
sizeof (dialog->sip_dlg_id)) == 0) {
SIP_DLG_REFCNT_INCR(dialog);
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_TRUE);
}
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_FALSE);
}
/*
* Don't delete, just take it out of the hash
*/
boolean_t
sip_dialog_dontfree(void *obj, void *hindex, int *found)
{
_sip_dialog_t *dialog = (_sip_dialog_t *)obj;
*found = 0;
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
if (bcmp(dialog->sip_dlg_id, hindex, sizeof (dialog->sip_dlg_id))
== 0) {
*found = 1;
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_TRUE);
}
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_FALSE);
}
/*
* Free resources associated with the dialog, the object will be removed
* from the hash list by sip_hash_delete.
*/
boolean_t
sip_dialog_free(void *obj, void *hindex, int *found)
{
_sip_dialog_t *dialog = (_sip_dialog_t *)obj;
*found = 0;
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
if (bcmp(dialog->sip_dlg_id, hindex, sizeof (dialog->sip_dlg_id))
== 0) {
*found = 1;
assert(dialog->sip_dlg_state == SIP_DLG_DESTROYED);
if (dialog->sip_dlg_ref_cnt != 0) {
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_FALSE);
}
sip_release_dialog_res(dialog);
return (B_TRUE);
}
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
return (B_FALSE);
}
/*
* The UAS will receive the request from the transaction layer. If the
* request has a tag in the To header field, the UAS core computes the
* dialog identifier corresponding to the request and compares it with
* existing dialogs. If there is a match, this is a mid-dialog request.
*/
sip_dialog_t
sip_dialog_find(_sip_msg_t *sip_msg)
{
const sip_str_t *localtag;
const sip_str_t *remtag;
const sip_str_t *callid;
uint16_t digest[8];
_sip_dialog_t *dialog;
boolean_t is_request;
int error;
is_request = sip_msg_is_request((sip_msg_t)sip_msg, &error);
if (error != 0)
return (NULL);
if (is_request) {
localtag = sip_get_to_tag((sip_msg_t)sip_msg, &error);
if (error == 0)
remtag = sip_get_from_tag((sip_msg_t)sip_msg, &error);
} else {
remtag = sip_get_to_tag((sip_msg_t)sip_msg, &error);
if (error == 0)
localtag = sip_get_from_tag((sip_msg_t)sip_msg, &error);
}
if (error != 0)
return (NULL);
callid = sip_get_callid((sip_msg_t)sip_msg, &error);
if (error != 0 || remtag == NULL || localtag == NULL ||
callid == NULL) {
return (NULL);
}
sip_md5_hash(localtag->sip_str_ptr, localtag->sip_str_len,
remtag->sip_str_ptr, remtag->sip_str_len,
callid->sip_str_ptr, callid->sip_str_len,
NULL, 0, NULL, 0, NULL, 0, (uchar_t *)digest);
dialog = (_sip_dialog_t *)sip_hash_find(sip_dialog_hash,
(void *)digest, SIP_DIGEST_TO_HASH(digest), sip_dialog_match);
if (dialog == NULL) {
sip_md5_hash(localtag->sip_str_ptr, localtag->sip_str_len,
NULL, 0, callid->sip_str_ptr, callid->sip_str_len,
NULL, 0, NULL, 0, NULL, 0, (uchar_t *)digest);
dialog = (_sip_dialog_t *)sip_hash_find(sip_dialog_phash,
(void *)digest, SIP_DIGEST_TO_HASH(digest),
sip_dialog_match);
}
return ((sip_dialog_t)dialog);
}
/*
* We keep this partial dialog for the duration of the INVITE
* transaction timeout duration, i.e. Timer B.
*/
void
sip_dlg_self_destruct(void *args)
{
sip_dialog_timer_obj_t *tim_obj = (sip_dialog_timer_obj_t *)args;
_sip_dialog_t *dialog = (_sip_dialog_t *)tim_obj->dialog;
int index;
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
assert(dialog->sip_dlg_state == SIP_DLG_NEW);
dialog->sip_dlg_state = SIP_DLG_DESTROYED;
if (dialog->sip_dlg_type == SIP_UAC_DIALOG) {
index = SIP_DIGEST_TO_HASH(dialog->sip_dlg_id);
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
sip_hash_delete(sip_dialog_phash, (void *)dialog->sip_dlg_id,
index, sip_dialog_dontfree);
} else {
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
}
if (tim_obj->func != NULL)
tim_obj->func(dialog, NULL, NULL);
free(tim_obj);
SIP_DLG_REFCNT_DECR(dialog);
}
/*
* Terminate a dialog
*/
void
sip_dialog_terminate(_sip_dialog_t *dialog, sip_msg_t sip_msg)
{
int prev_state;
(void) pthread_mutex_lock(&dialog->sip_dlg_mutex);
prev_state = dialog->sip_dlg_state;
dialog->sip_dlg_state = SIP_DLG_DESTROYED;
(void) pthread_mutex_unlock(&dialog->sip_dlg_mutex);
if (sip_dlg_ulp_state_cb != NULL) {
sip_dlg_ulp_state_cb((sip_dialog_t)dialog, sip_msg, prev_state,
dialog->sip_dlg_state);
}
SIP_DLG_REFCNT_DECR(dialog);
}
/*
* Delete a dialog
*/
void
sip_dialog_delete(_sip_dialog_t *dialog)
{
int index;
/*
* partial dialog, not in the hash table
*/
if (dialog->sip_dlg_local_uri_tag == NULL ||
dialog->sip_dlg_remote_uri_tag == NULL) {
/*
* Cancel the partial dialog timer
*/
if (SIP_IS_TIMER_RUNNING(dialog->sip_dlg_timer))
SIP_CANCEL_TIMER(dialog->sip_dlg_timer);
sip_release_dialog_res(dialog);
return;
}
index = SIP_DIGEST_TO_HASH(dialog->sip_dlg_id);
sip_hash_delete(sip_dialog_hash, (void *)dialog->sip_dlg_id, index,
sip_dialog_free);
}
/*
* Get the remote target from the CONTACT header from the 200 OK response
*/
static boolean_t
sip_get_rtarg(_sip_dialog_t *dialog, _sip_msg_t *sip_msg)
{
sip_header_t chdr;
if (dialog->sip_dlg_remote_target != NULL)
return (B_TRUE);
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
chdr = sip_search_for_header(sip_msg, SIP_CONTACT, NULL);
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
if (chdr == NULL)
return (B_FALSE);
if ((dialog->sip_dlg_remote_target = sip_dup_header(chdr)) == NULL)
return (B_FALSE);
return (B_TRUE);
}
/*
* Process an incoming request/response
*/
/* ARGSUSED */
int
sip_dialog_process(_sip_msg_t *sip_msg, sip_dialog_t *sip_dialog)
{
boolean_t request;
_sip_dialog_t *_dialog;
int error;
request = sip_msg_is_request((sip_msg_t)sip_msg, &error);
if (error != 0)
return (EINVAL);
_dialog = (_sip_dialog_t *)*sip_dialog;
if (request) {
uint32_t cseq;
sip_method_t method;
cseq = sip_get_callseq_num((sip_msg_t)sip_msg, &error);
if (error != 0)
return (EINVAL);
method = sip_get_callseq_method((sip_msg_t)sip_msg, &error);
if (error != 0)
return (EINVAL);
if (sip_get_request_method((sip_msg_t)sip_msg, &error) !=
method) {
return (EINVAL);
}
(void) pthread_mutex_lock(&_dialog->sip_dlg_mutex);
/*
* Requests that do not change in any way the state
* of a dialog may be received within a dialog.
* They are processed as if they had been received
* outside the dialog.
* For dialogs that have been established with an
* INVITE, the only target refresh request defined is
* re-INVITE.
*/
if (_dialog->sip_dlg_method == INVITE &&
method == INVITE && _dialog->sip_dlg_remote_cseq != 0 &&
SIP_CSEQ_LT(cseq, _dialog->sip_dlg_remote_cseq)) {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
return (EPROTO);
}
/*
* Target-Refresh request
*/
if (_dialog->sip_dlg_method == INVITE && method == INVITE) {
sip_header_t chdr;
sip_header_t nchdr;
(void) pthread_mutex_lock(&sip_msg->sip_msg_mutex);
chdr = sip_search_for_header(sip_msg, SIP_CONTACT,
NULL);
(void) pthread_mutex_unlock(&sip_msg->sip_msg_mutex);
if (chdr != NULL &&
(nchdr = sip_dup_header(chdr)) != NULL) {
if (_dialog->sip_dlg_remote_target != NULL) {
sip_free_header(
_dialog->sip_dlg_remote_target);
}
_dialog->sip_dlg_remote_target = nchdr;
}
}
_dialog->sip_dlg_remote_cseq = cseq;
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
} else {
int resp_code;
sip_method_t method;
int error;
resp_code = sip_get_response_code((sip_msg_t)sip_msg, &error);
if (error == 0) {
method = sip_get_callseq_method((sip_msg_t)sip_msg,
&error);
}
if (error != 0)
return (error);
(void) pthread_mutex_lock(&_dialog->sip_dlg_mutex);
assert(_dialog->sip_dlg_state == SIP_DLG_EARLY ||
_dialog->sip_dlg_state == SIP_DLG_CONFIRMED);
/*
* Let the user delete the dialog if it is not a 1XX/2XX resp
* for an early INVITE dialog.
*/
if (SIP_OK_RESP(resp_code)) {
if (method == INVITE) {
if (!sip_get_rtarg(_dialog, sip_msg)) {
(void) pthread_mutex_unlock(
&_dialog->sip_dlg_mutex);
if (sip_ulp_dlg_del_cb != NULL) {
sip_ulp_dlg_del_cb(
(sip_dialog_t)_dialog,
(sip_msg_t)sip_msg, NULL);
}
sip_dialog_terminate(_dialog,
(sip_msg_t)sip_msg);
return (0);
}
if (_dialog->sip_dlg_state == SIP_DLG_EARLY) {
_dialog->sip_dlg_state =
SIP_DLG_CONFIRMED;
(void) pthread_mutex_unlock(
&_dialog->sip_dlg_mutex);
(void) sip_dlg_recompute_rset(_dialog,
sip_msg, SIP_UAC_DIALOG);
if (sip_dlg_ulp_state_cb != NULL) {
sip_dlg_ulp_state_cb(
(sip_dialog_t)_dialog,
sip_msg, SIP_DLG_EARLY,
_dialog->sip_dlg_state);
}
return (0);
}
}
}
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
}
return (0);
}
/*
* Copy partial dialog to create a complete dialog
*/
_sip_dialog_t *
sip_copy_partial_dialog(_sip_dialog_t *dialog)
{
_sip_dialog_t *new_dlg;
new_dlg = calloc(1, sizeof (_sip_dialog_t));
if (new_dlg == NULL)
return (NULL);
if (dialog->sip_dlg_req_uri.sip_str_ptr != NULL) {
new_dlg->sip_dlg_req_uri.sip_str_ptr =
malloc(dialog->sip_dlg_req_uri.sip_str_len + 1);
if (new_dlg->sip_dlg_req_uri.sip_str_ptr == NULL) {
free(new_dlg);
return (NULL);
}
(void) strncpy(new_dlg->sip_dlg_req_uri.sip_str_ptr,
dialog->sip_dlg_req_uri.sip_str_ptr,
dialog->sip_dlg_req_uri.sip_str_len);
new_dlg->sip_dlg_req_uri.sip_str_ptr[
dialog->sip_dlg_req_uri.sip_str_len] = '\0';
new_dlg->sip_dlg_req_uri.sip_str_len =
dialog->sip_dlg_req_uri.sip_str_len;
}
if (dialog->sip_dlg_route_set != NULL) {
assert(dialog->sip_dlg_rset.sip_str_ptr != NULL);
new_dlg->sip_dlg_rset.sip_str_ptr =
malloc(dialog->sip_dlg_rset.sip_str_len + 1);
if (new_dlg->sip_dlg_rset.sip_str_ptr == NULL) {
if (new_dlg->sip_dlg_req_uri.sip_str_ptr != NULL)
free(new_dlg->sip_dlg_req_uri.sip_str_ptr);
free(new_dlg);
return (NULL);
}
(void) strncpy(new_dlg->sip_dlg_rset.sip_str_ptr,
dialog->sip_dlg_rset.sip_str_ptr,
dialog->sip_dlg_rset.sip_str_len);
new_dlg->sip_dlg_rset.sip_str_ptr[
dialog->sip_dlg_rset.sip_str_len] = '\0';
new_dlg->sip_dlg_rset.sip_str_len =
dialog->sip_dlg_rset.sip_str_len;
new_dlg->sip_dlg_route_set =
sip_dup_header(dialog->sip_dlg_route_set);
if (new_dlg->sip_dlg_route_set == NULL) {
free(new_dlg->sip_dlg_rset.sip_str_ptr);
if (new_dlg->sip_dlg_req_uri.sip_str_ptr != NULL)
free(new_dlg->sip_dlg_req_uri.sip_str_ptr);
free(new_dlg);
return (NULL);
}
}
if ((new_dlg->sip_dlg_local_uri_tag =
sip_dup_header(dialog->sip_dlg_local_uri_tag)) == NULL ||
(new_dlg->sip_dlg_remote_target =
sip_dup_header(dialog->sip_dlg_remote_target)) == NULL ||
(new_dlg->sip_dlg_call_id =
sip_dup_header(dialog->sip_dlg_call_id)) == NULL) {
sip_release_dialog_res(new_dlg);
return (NULL);
}
if (dialog->sip_dlg_event != NULL) {
new_dlg->sip_dlg_event = sip_dup_header(dialog->sip_dlg_event);
if (new_dlg->sip_dlg_event == NULL) {
sip_release_dialog_res(new_dlg);
return (NULL);
}
}
new_dlg->sip_dlg_local_cseq = dialog->sip_dlg_local_cseq;
new_dlg->sip_dlg_type = dialog->sip_dlg_type;
new_dlg->sip_dlg_on_fork = B_FALSE;
(void) pthread_mutex_init(&new_dlg->sip_dlg_mutex, NULL);
return (new_dlg);
}
/*
* Update the dialog using the response
*/
sip_dialog_t
sip_update_dialog(sip_dialog_t dialog, _sip_msg_t *sip_msg)
{
_sip_dialog_t *_dialog;
boolean_t isreq;
sip_method_t method;
int resp_code = 0;
int prev_state;
boolean_t decr_ref = B_FALSE;
int error;
isreq = sip_msg_is_request((sip_msg_t)sip_msg, &error);
if (error != 0)
return (dialog);
_dialog = (_sip_dialog_t *)dialog;
(void) pthread_mutex_lock(&_dialog->sip_dlg_mutex);
if (isreq) {
method = sip_get_request_method((sip_msg_t)sip_msg, &error);
if (error != 0 || _dialog->sip_dlg_method != SUBSCRIBE ||
method != NOTIFY) {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
return (dialog);
}
} else {
resp_code = sip_get_response_code((sip_msg_t)sip_msg, &error);
if (error != 0) {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
return (dialog);
}
}
prev_state = _dialog->sip_dlg_state;
if (_dialog->sip_dlg_state == SIP_DLG_CONFIRMED) {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
} else if (_dialog->sip_dlg_state == SIP_DLG_EARLY) {
/*
* Let the user delete the dialog if it is not a 1XX/2XX resp
* for an early dialog.
*/
assert(!isreq);
if (SIP_OK_RESP(resp_code)) {
_dialog->sip_dlg_state = SIP_DLG_CONFIRMED;
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
(void) sip_dlg_recompute_rset(_dialog, sip_msg,
SIP_UAS_DIALOG);
if (sip_dlg_ulp_state_cb != NULL) {
sip_dlg_ulp_state_cb(dialog, (sip_msg_t)sip_msg,
prev_state, dialog->sip_dlg_state);
}
} else {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
}
} else if (_dialog->sip_dlg_state == SIP_DLG_NEW) {
if (!isreq && _dialog->sip_dlg_method == SUBSCRIBE &&
SIP_PROVISIONAL_RESP(resp_code)) {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
return (dialog);
}
if (_dialog->sip_dlg_type == SIP_UAC_DIALOG) {
_sip_dialog_t *new_dlg;
if (_dialog->sip_dlg_on_fork) {
new_dlg = sip_copy_partial_dialog(_dialog);
if (new_dlg == NULL) {
(void) pthread_mutex_unlock(
&_dialog->sip_dlg_mutex);
return (dialog);
}
/*
* This decr/incr dance is because the caller
* has incremented the ref on the partial
* dialog, we release it here and incr the
* ref on the new dialog which will be
* released by the caller.
*/
(void) pthread_mutex_unlock(
&_dialog->sip_dlg_mutex);
SIP_DLG_REFCNT_DECR(_dialog);
_dialog = new_dlg;
(void) pthread_mutex_lock(
&_dialog->sip_dlg_mutex);
SIP_DLG_REFCNT_INCR(_dialog);
} else {
int index;
/*
* take it out of the list so that further
* responses will not result in a dialog.
* We will have an extra refcount when we
* come back from sip_complete_dialog(), i.e.
* one when the partial dialog was created -
* in sip_seed_dialog(), one held by the caller
* and one that will be added by
* sip_complete_dialog(). We need to release
* the one added by the sip_seed_dialog(),
* since the one in sip_complete_dialog()
* is for the same purpose.
*/
if (SIP_IS_TIMER_RUNNING(
_dialog->sip_dlg_timer)) {
SIP_CANCEL_TIMER(
_dialog->sip_dlg_timer);
}
index = SIP_DIGEST_TO_HASH(dialog->sip_dlg_id);
(void) pthread_mutex_unlock(
&_dialog->sip_dlg_mutex);
sip_hash_delete(sip_dialog_phash,
(void *)_dialog->sip_dlg_id,
index, sip_dialog_dontfree);
(void) pthread_mutex_lock(
&_dialog->sip_dlg_mutex);
decr_ref = B_TRUE;
}
} else {
decr_ref = B_TRUE;
}
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
if ((dialog = sip_complete_dialog(sip_msg, _dialog)) ==
NULL) {
return (NULL);
}
if (decr_ref)
SIP_DLG_REFCNT_DECR(_dialog);
} else {
(void) pthread_mutex_unlock(&_dialog->sip_dlg_mutex);
}
return (dialog);
}
/*
* Initialize the hash table
*/
void
sip_dialog_init(void (*ulp_dlg_del) (sip_dialog_t, sip_msg_t, void *),
void (*ulp_state_cb)(sip_dialog_t, sip_msg_t, int, int))
{
int cnt;
for (cnt = 0; cnt < SIP_HASH_SZ; cnt++) {
sip_dialog_hash[cnt].hash_count = 0;
sip_dialog_hash[cnt].hash_head = NULL;
sip_dialog_hash[cnt].hash_tail = NULL;
(void) pthread_mutex_init(
&sip_dialog_hash[cnt].sip_hash_mutex, NULL);
sip_dialog_phash[cnt].hash_count = 0;
sip_dialog_phash[cnt].hash_head = NULL;
sip_dialog_phash[cnt].hash_tail = NULL;
(void) pthread_mutex_init(
&sip_dialog_phash[cnt].sip_hash_mutex, NULL);
}
if (ulp_dlg_del != NULL)
sip_ulp_dlg_del_cb = ulp_dlg_del;
if (ulp_state_cb != NULL)
sip_dlg_ulp_state_cb = ulp_state_cb;
}