mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
526 lines
13 KiB
526 lines
13 KiB
#include "rl_subscription.h"
|
|
#include "../../mem/mem.h"
|
|
#include "../../mem/shm_mem.h"
|
|
#include <xcap/resource_list.h>
|
|
#include "rls_mod.h"
|
|
#include "result_codes.h"
|
|
#include <cds/dstring.h>
|
|
#include <cds/logger.h>
|
|
#include <presence/qsa.h>
|
|
#include <presence/pres_doc.h>
|
|
#include <presence/pidf.h>
|
|
#include <cds/list.h>
|
|
#include "rls_data.h"
|
|
#include "rls_auth.h"
|
|
|
|
/* shared structure holding the data */
|
|
typedef struct {
|
|
virtual_subscription_t *first;
|
|
virtual_subscription_t *last;
|
|
/* hash, ... */
|
|
notifier_domain_t *domain;
|
|
qsa_content_type_t *ct_presence_info;
|
|
qsa_content_type_t *ct_raw;
|
|
} vs_data_t;
|
|
|
|
static vs_data_t *vsd = NULL;
|
|
|
|
/******** global functions (initialization) ********/
|
|
|
|
int vs_init()
|
|
{
|
|
static str presence_info = STR_STATIC_INIT(CT_PRESENCE_INFO);
|
|
static str raw = STR_STATIC_INIT(CT_RAW);
|
|
|
|
vsd = (vs_data_t*)mem_alloc(sizeof(vs_data_t));
|
|
if (!vsd) {
|
|
LOG(L_ERR, "vs_init(): memory allocation error\n");
|
|
return -1;
|
|
}
|
|
vsd->first = NULL;
|
|
vsd->last = NULL;
|
|
vsd->domain = qsa_get_default_domain();
|
|
if (!vsd->domain) {
|
|
LOG(L_ERR, "vs_init(): can't register notifier domain\n");
|
|
return -1;
|
|
}
|
|
DEBUG_LOG("QSA (vs) domain: %p\n", vsd->domain);
|
|
|
|
vsd->ct_presence_info = register_content_type(vsd->domain,
|
|
&presence_info, (destroy_function_f)free_presentity_info);
|
|
if (!vsd->ct_presence_info) {
|
|
ERR("can't register QSA content type\n");
|
|
return -1;
|
|
}
|
|
else TRACE("RLS_PRESENCE_INFO: %p\n", vsd->ct_presence_info);
|
|
|
|
vsd->ct_raw = register_content_type(vsd->domain,
|
|
&raw, (destroy_function_f)free_raw_presence_info);
|
|
if (!vsd->ct_raw) {
|
|
ERR("can't register QSA content type\n");
|
|
return -1;
|
|
}
|
|
else TRACE("RLS_RAW: %p\n", vsd->ct_raw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int vs_destroy()
|
|
{
|
|
/* virtual subscriptions are freed in rls_free */
|
|
if (vsd) {
|
|
qsa_release_domain(vsd->domain);
|
|
vsd->domain = NULL;
|
|
mem_free(vsd);
|
|
vsd = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/******** Helper functions ********/
|
|
|
|
/* sets new documents (frees them if equal) */
|
|
static void set_vs_document(virtual_subscription_t *vs,
|
|
str_t *new_doc,
|
|
str_t *new_content_type)
|
|
{
|
|
if (str_case_equals(&vs->state_document, new_doc) == 0) {
|
|
/* DEBUG("new document is equal to the older one\n"); */
|
|
str_free_content(new_doc);
|
|
}
|
|
else {
|
|
str_free_content(&vs->state_document);
|
|
if (new_doc) vs->state_document = *new_doc;
|
|
else str_clear(&vs->state_document);
|
|
vs->changed = 1;
|
|
}
|
|
|
|
if (str_case_equals(&vs->content_type, new_content_type) == 0) {
|
|
/* DEBUG("new content-type is equal to the older one\n"); */
|
|
str_free_content(new_content_type);
|
|
}
|
|
else {
|
|
str_free_content(&vs->content_type);
|
|
if (new_content_type) vs->content_type = *new_content_type;
|
|
else str_clear(&vs->content_type);
|
|
vs->changed = 1;
|
|
}
|
|
}
|
|
|
|
/* duplicates documents if changed */
|
|
static int set_vs_document_dup(virtual_subscription_t *vs,
|
|
str_t *new_doc,
|
|
str_t *new_content_type)
|
|
{
|
|
if (str_case_equals(&vs->state_document, new_doc) == 0) {
|
|
/* DEBUG("new document is equal to the older one\n"); */
|
|
}
|
|
else {
|
|
str_free_content(&vs->state_document);
|
|
str_dup(&vs->state_document, new_doc);
|
|
vs->changed = 1;
|
|
}
|
|
|
|
if (str_case_equals(&vs->content_type, new_content_type) == 0) {
|
|
/* DEBUG("new content-type is equal to the older one\n"); */
|
|
}
|
|
else {
|
|
str_free_content(&vs->content_type);
|
|
str_dup(&vs->content_type, new_content_type);
|
|
vs->changed = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void propagate_change(virtual_subscription_t *vs)
|
|
{
|
|
if (vs->subscription->type == rls_internal_subscription) {
|
|
/* propagate change to higher level */
|
|
rls_generate_notify(vs->subscription, 1);
|
|
}
|
|
else {
|
|
/* external subscriptions will send NOTIFY only sometimes
|
|
* => we mark it as changed now */
|
|
vs->subscription->changed++;
|
|
/* FIXME: put this subscription in some queue? (needs remove from it
|
|
* when freeing!) */
|
|
if (rls) rls->changed_subscriptions++; /* change indicator */
|
|
}
|
|
}
|
|
|
|
void process_rls_notification(virtual_subscription_t *vs, client_notify_info_t *info)
|
|
{
|
|
presentity_info_t *pinfo;
|
|
raw_presence_info_t *raw;
|
|
str_t new_doc = STR_NULL;
|
|
str_t new_type = STR_NULL;
|
|
subscription_status_t old_status;
|
|
|
|
if ((!vs) || (!info)) return;
|
|
|
|
DBG("Processing notification for VS %p\n", vs);
|
|
/* FIXME: put information from more sources together ? */
|
|
|
|
old_status = vs->status;
|
|
switch (info->status) {
|
|
case qsa_subscription_active:
|
|
vs->status = subscription_active;
|
|
break;
|
|
case qsa_subscription_pending:
|
|
vs->status = subscription_pending;
|
|
break;
|
|
case qsa_subscription_rejected:
|
|
vs->status = subscription_terminated;
|
|
break;
|
|
case qsa_subscription_terminated:
|
|
vs->status = subscription_terminated;
|
|
break;
|
|
}
|
|
if (old_status != vs->status) vs->changed = 1;
|
|
|
|
if (info->content_type == vsd->ct_raw) {
|
|
DEBUG("Processing raw notification\n");
|
|
|
|
raw = (raw_presence_info_t*)info->data;
|
|
if (!raw) return;
|
|
|
|
/* document MUST be duplicated !!! */
|
|
if (set_vs_document_dup(vs, &raw->pres_doc, &raw->content_type) < 0) {
|
|
ERR("can't set new status document for VS %p\n", vs);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
if (info->content_type == vsd->ct_presence_info) {
|
|
DEBUG("Processing structured notification\n");
|
|
|
|
pinfo = (presentity_info_t*)info->data;
|
|
if (!pinfo) {
|
|
str_clear(&new_doc);
|
|
str_clear(&new_type);
|
|
}
|
|
else {
|
|
if (create_pidf_document(pinfo, &new_doc, &new_type) < 0) {
|
|
ERR("can't create PIDF document\n");
|
|
str_free_content(&vs->state_document);
|
|
str_free_content(&vs->content_type);
|
|
return;
|
|
}
|
|
set_vs_document(vs, &new_doc, &new_type);
|
|
}
|
|
}
|
|
else {
|
|
if (info->content_type)
|
|
ERR("received unacceptable notification (%.*s)\n",
|
|
FMT_STR(info->content_type->name));
|
|
else ERR("received unacceptable notification without content type\n");
|
|
str_free_content(&vs->state_document);
|
|
str_free_content(&vs->content_type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (vs->changed) propagate_change(vs);
|
|
}
|
|
|
|
void process_internal_notify(virtual_subscription_t *vs,
|
|
str_t *new_state_document,
|
|
str_t *new_content_type)
|
|
{
|
|
if (!vs) return;
|
|
|
|
DBG("Processing internal notification for VS %p\n", vs);
|
|
|
|
/* don't copy document - use it directly */
|
|
set_vs_document(vs, new_state_document, new_content_type);
|
|
if (vs->changed) propagate_change(vs);
|
|
}
|
|
|
|
#if 0
|
|
|
|
static void mark_as_modified(virtual_subscription_t *vs)
|
|
{
|
|
rl_subscription_t *rls = vs->subscription;
|
|
|
|
switch (rls->type) {
|
|
case rls_external_subscription:
|
|
if (sm_subscription_pending(&rls->u.external) == 0) {
|
|
/* pending subscription will not be notified */
|
|
return;
|
|
}
|
|
break;
|
|
case rls_internal_subscription:
|
|
/* FIXME: something like above? */
|
|
break;
|
|
}
|
|
|
|
/* NOTIFY should be send only for nonpending subscriptions (or active?)*/
|
|
|
|
vs->subscription->changed++;
|
|
DEBUG_LOG("RL subscription status changed (%p, %d)\n",
|
|
rls, rls->changed);
|
|
}
|
|
|
|
static void vs_timer_cb(unsigned int ticks, void *param)
|
|
{
|
|
virtual_subscription_t *vs;
|
|
int changed = 0;
|
|
int cntr = 0;
|
|
time_t start, stop;
|
|
|
|
start = time(NULL);
|
|
rls_lock();
|
|
|
|
/* process all messages for virtual subscriptions */
|
|
vs = vsd->first;
|
|
while (vs) {
|
|
if (process_vs_messages(vs) > 0) {
|
|
DEBUG_LOG("VS status changed\n");
|
|
mark_as_modified(vs);
|
|
changed = 1;
|
|
}
|
|
vs = vs->next;
|
|
cntr++; /* debugging purposes */
|
|
}
|
|
|
|
/* TRACE_LOG("processed messages for %d virtual subscription(s)\n", cntr); */
|
|
|
|
if (changed) {
|
|
/* this could be called from some rli_timer ? */
|
|
notify_all_modified();
|
|
}
|
|
|
|
rls_unlock();
|
|
stop = time(NULL);
|
|
|
|
if (stop - start > 1) WARN("vs_timer_cb took %d secs\n", (int) (stop - start));
|
|
}
|
|
|
|
#endif
|
|
|
|
static int add_to_vs_list(virtual_subscription_t *vs)
|
|
{
|
|
if (!vs) return RES_INTERNAL_ERR;
|
|
if (!vsd) {
|
|
LOG(L_ERR, "vs_add(): vsd not set!\n");
|
|
return RES_INTERNAL_ERR;
|
|
}
|
|
DOUBLE_LINKED_LIST_ADD(vsd->first, vsd->last, vs);
|
|
|
|
return RES_OK;
|
|
}
|
|
|
|
static int remove_from_vs_list(virtual_subscription_t *vs)
|
|
{
|
|
if (!vs) return RES_INTERNAL_ERR;
|
|
if (!vsd) {
|
|
LOG(L_ERR, "vs_remove(): vsd not set!\n");
|
|
return RES_INTERNAL_ERR;
|
|
}
|
|
|
|
DOUBLE_LINKED_LIST_REMOVE(vsd->first, vsd->last, vs);
|
|
|
|
return RES_OK;
|
|
}
|
|
|
|
int xcap_query_rls_services(xcap_query_params_t *xcap_params,
|
|
const str *uri, const str *package,
|
|
flat_list_t **dst)
|
|
{
|
|
if (dst) *dst = NULL;
|
|
|
|
if (reduce_xcap_needs)
|
|
return get_rls_from_full_doc(uri, xcap_params, package, dst);
|
|
else
|
|
return get_rls(uri, xcap_params, package, dst);
|
|
}
|
|
|
|
static int create_subscriptions(virtual_subscription_t *vs, int nesting_level)
|
|
{
|
|
/* create concrete local subscription */
|
|
str *package = NULL;
|
|
str *subscriber = NULL;
|
|
flat_list_t *flat = NULL;
|
|
|
|
package = rls_get_package(vs->subscription);
|
|
|
|
DEBUG_LOG("creating local subscription to %.*s\n", FMT_STR(vs->uri));
|
|
|
|
if ((nesting_level != 0) &&
|
|
(xcap_query_rls_services(&vs->subscription->xcap_params,
|
|
&vs->uri, package, &flat) == 0)) {
|
|
if (nesting_level > 0) nesting_level--;
|
|
/* it is resource list -> do internal subscription to RLS */
|
|
if (rls_create_internal_subscription(vs,
|
|
&vs->local_subscription_list, flat,
|
|
nesting_level) != 0) {
|
|
ERR("can't create internal subscription\n");
|
|
free_flat_list(flat);
|
|
return -1;
|
|
}
|
|
free_flat_list(flat);
|
|
|
|
vs->status = subscription_active;
|
|
/* FIXME: rls_authorize_subscription(vs->local_subscription_list); */
|
|
}
|
|
else {
|
|
/* fill QSA subscription data */
|
|
clear_subscription_data(&vs->local_subscription_pres_data);
|
|
vs->local_subscription_pres_data.dst = &rls->notify_mq;
|
|
vs->local_subscription_pres_data.record_id = vs->uri;
|
|
subscriber = rls_get_subscriber(vs->subscription);
|
|
vs->local_subscription_pres_data.subscriber_data = vs;
|
|
if (subscriber)
|
|
vs->local_subscription_pres_data.subscriber_id = *subscriber;
|
|
|
|
/* not RLS record -> do QSA subscription to given package */
|
|
vs->local_subscription_pres = subscribe(vsd->domain,
|
|
package, &vs->local_subscription_pres_data);
|
|
if (!vs->local_subscription_pres) {
|
|
LOG(L_ERR, "can't create local subscription (pres)!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******** VS manipulation ********/
|
|
|
|
int vs_create(str *uri,
|
|
virtual_subscription_t **dst,
|
|
display_name_t *dnames,
|
|
rl_subscription_t *subscription,
|
|
int nesting_level)
|
|
{
|
|
int res;
|
|
display_name_t *d;
|
|
|
|
if (!dst) return RES_INTERNAL_ERR;
|
|
*dst = NULL;
|
|
if (!uri) {
|
|
LOG(L_ERR, "vs_create(): no uri given\n");
|
|
return RES_INTERNAL_ERR;
|
|
}
|
|
if ((!uri->s) || (uri->len < 1)) {
|
|
LOG(L_ERR, "vs_create(): no uri given\n");
|
|
return RES_INTERNAL_ERR;
|
|
}
|
|
|
|
*dst = (virtual_subscription_t*)mem_alloc(sizeof(virtual_subscription_t) + uri->len + 1);
|
|
if (!(*dst)) {
|
|
LOG(L_ERR, "vs_create(): can't allocate memory\n");
|
|
return RES_MEMORY_ERR;
|
|
}
|
|
|
|
(*dst)->next = NULL;
|
|
(*dst)->prev = NULL;
|
|
vector_init(&(*dst)->display_names, sizeof(vs_display_name_t), 4);
|
|
memcpy((*dst)->uri_str, uri->s, uri->len);
|
|
(*dst)->uri.s = (*dst)->uri_str;
|
|
(*dst)->uri.len = uri->len;
|
|
(*dst)->state_document.len = 0;
|
|
(*dst)->state_document.s = NULL;
|
|
(*dst)->content_type.len = 0;
|
|
(*dst)->content_type.s = NULL;
|
|
(*dst)->status = subscription_pending;
|
|
(*dst)->local_subscription_pres = NULL;
|
|
(*dst)->local_subscription_list = NULL;
|
|
(*dst)->subscription = subscription;
|
|
(*dst)->changed = 0;
|
|
generate_db_id(&(*dst)->dbid, *dst);
|
|
|
|
add_to_vs_list(*dst);
|
|
|
|
DBG("created VS %p to %.*s\n", *dst, uri->len, uri->s);
|
|
|
|
res = create_subscriptions(*dst, nesting_level);
|
|
if (res != 0) {
|
|
vs_free(*dst);
|
|
return res;
|
|
}
|
|
|
|
/* TODO: remember the list of Accept headers from client subscribe
|
|
* it will be used for Back-End subscriptions */
|
|
|
|
/* add names */
|
|
if (dnames) {
|
|
d = SEQUENCE_FIRST(dnames);
|
|
while (d) {
|
|
vs_add_display_name((*dst), d->name, d->lang);
|
|
d = SEQUENCE_NEXT(d);
|
|
}
|
|
}
|
|
|
|
return RES_OK;
|
|
}
|
|
|
|
int vs_add_display_name(virtual_subscription_t *vs, const char *name, const char *lang)
|
|
{
|
|
vs_display_name_t dn;
|
|
|
|
if (name) {
|
|
dn.name.len = strlen(name);
|
|
if (dn.name.len > 0) {
|
|
dn.name.s = (char *)mem_alloc(dn.name.len);
|
|
if (!dn.name.s) dn.name.len = 0;
|
|
else memcpy(dn.name.s, name, dn.name.len);
|
|
}
|
|
}
|
|
else {
|
|
dn.name.len = 0;
|
|
dn.name.s = NULL;
|
|
}
|
|
|
|
if (lang) {
|
|
dn.lang.len = strlen(lang);
|
|
if (dn.lang.len > 0) {
|
|
dn.lang.s = (char *)mem_alloc(dn.lang.len);
|
|
if (!dn.lang.s) dn.lang.len = 0;
|
|
else memcpy(dn.lang.s, lang, dn.lang.len);
|
|
}
|
|
}
|
|
else {
|
|
dn.lang.len = 0;
|
|
dn.lang.s = NULL;
|
|
}
|
|
/* TRACE_LOG("adding display name: %s\n", name);*/
|
|
return vector_add(&vs->display_names, &dn);
|
|
}
|
|
|
|
void vs_free(virtual_subscription_t *vs)
|
|
{
|
|
int i, cnt;
|
|
vs_display_name_t dn;
|
|
|
|
if (vs) {
|
|
if (vs->local_subscription_pres)
|
|
unsubscribe(vsd->domain, vs->local_subscription_pres);
|
|
if (vs->local_subscription_list)
|
|
rls_remove(vs->local_subscription_list);
|
|
|
|
/* remove notification messages for given subscription */
|
|
destroy_notifications(vs->local_subscription_pres);
|
|
|
|
remove_from_vs_list(vs);
|
|
|
|
str_free_content(&vs->state_document);
|
|
str_free_content(&vs->content_type);
|
|
|
|
/* if ( (vs->package.len > 0) && (vs->package.s) )
|
|
mem_free(vs->package.s); */
|
|
|
|
cnt = vector_size(&vs->display_names);
|
|
for (i = 0; i < cnt; i++) {
|
|
if (vector_get(&vs->display_names, i, &dn) != 0) continue;
|
|
if (dn.name.s && (dn.name.len > 0)) mem_free(dn.name.s);
|
|
if (dn.lang.s && (dn.lang.len > 0)) mem_free(dn.lang.s);
|
|
}
|
|
vector_destroy(&vs->display_names);
|
|
|
|
mem_free(vs);
|
|
/* LOG(L_TRACE, "Virtual Subscription freed\n");*/
|
|
}
|
|
}
|
|
|