#include "rl_subscription.h" #include "rls_mod.h" #include #include #include #include "result_codes.h" #include "rlmi_doc.h" #include #include #include "../../str.h" #include "../../id.h" #include "../../dprint.h" #include "../../mem/mem.h" #include "../../mem/shm_mem.h" #include "../../lock_alloc.h" #include "../../ut.h" #include "../../parser/hf.h" #include "../../parser/parse_from.h" #include "../../data_lump_rpl.h" typedef struct { dlg_id_t id; char buf[1]; } rls_notify_cb_param_t; subscription_manager_t *rls_manager = NULL; #define METHOD_NOTIFY "NOTIFY" /************* Helper functions for TM callback ************/ static rls_notify_cb_param_t *create_notify_cb_param(rl_subscription_t *s) { rls_notify_cb_param_t *cbd; int size; dlg_t *dlg; if (!s) return NULL; if (s->type != rls_external_subscription) return NULL; dlg = s->u.external.dialog; if (!dlg) return NULL; size = sizeof(*cbd) + dlg->id.call_id.len + dlg->id.rem_tag.len + dlg->id.loc_tag.len; cbd = (rls_notify_cb_param_t*)mem_alloc(size); if (!cbd) { ERR("can't allocate memory (%d bytes)\n", size); return NULL; } cbd->id.call_id.s = cbd->buf; cbd->id.call_id.len = dlg->id.call_id.len; cbd->id.rem_tag.s = cbd->id.call_id.s + cbd->id.call_id.len; cbd->id.rem_tag.len = dlg->id.rem_tag.len; cbd->id.loc_tag.s = cbd->id.rem_tag.s + cbd->id.rem_tag.len; cbd->id.loc_tag.len = dlg->id.loc_tag.len; /* copy data */ if (dlg->id.call_id.s) memcpy(cbd->id.call_id.s, dlg->id.call_id.s, dlg->id.call_id.len); if (dlg->id.rem_tag.s) memcpy(cbd->id.rem_tag.s, dlg->id.rem_tag.s, dlg->id.rem_tag.len); if (dlg->id.loc_tag.s) memcpy(cbd->id.loc_tag.s, dlg->id.loc_tag.s, dlg->id.loc_tag.len); return cbd; } static void destroy_subscription(rls_notify_cb_param_t *cbd) { int res; rl_subscription_t *s = NULL; rls_lock(); res = rls_find_subscription(&cbd->id.rem_tag, &cbd->id.loc_tag, &cbd->id.call_id, &s); if ((res != RES_OK) || (!s)) { /* subscription NOT found */ rls_unlock(); return; } rls_remove(s); rls_unlock(); } /************* Helper functions for RL subscription manipulation ************/ str_t * rls_get_uri(rl_subscription_t *s) { if (!s) return NULL; if (s->type == rls_external_subscription) { return &((s)->u.external.record_id); } else { return s->u.internal.record_id; } return NULL; } str_t * rls_get_package(rl_subscription_t *s) { static str presence = STR_STATIC_INIT("presence"); str_t *package = NULL; if (!s) return NULL; if (s->type == rls_external_subscription) package = &((s)->u.external.package); else package = s->u.internal.package; if (!package) package = &presence; return package; } str_t * rls_get_subscriber(rl_subscription_t *subscription) { if (!subscription) return NULL; switch (subscription->type) { case rls_external_subscription: return &subscription->u.external.subscriber; case rls_internal_subscription: return subscription->u.internal.subscriber_id; } return NULL; } int add_virtual_subscriptions(rl_subscription_t *ss, flat_list_t *flat, int nesting_level) { flat_list_t *e; /* xcap_query_t xcap; */ virtual_subscription_t *vs; int res = 0; str s; /* TODO: create virtual subscriptions using Accept headers * ... (for remote subscriptions) */ /* go through flat list and find/create virtual subscriptions */ e = flat; while (e) { s.s = e->uri; if (s.s) s.len = strlen(s.s); else s.len = 0; res = vs_create(&s, &vs, e->names, ss, nesting_level); if (res != RES_OK) { /* FIXME: remove already added members? */ return res; } ptr_vector_add(&ss->vs, vs); e = e->next; } return RES_OK; } int create_virtual_subscriptions(rl_subscription_t *ss, int nesting_level) { flat_list_t *flat = NULL; int res = 0; str_t *ss_uri = NULL; str_t *ss_package = NULL; ss_uri = rls_get_uri(ss); ss_package = rls_get_package(ss); res = xcap_query_rls_services(&ss->xcap_params, ss_uri, ss_package, &flat); if (res != RES_OK) return res; /* go through flat list and find/create virtual subscriptions */ res = add_virtual_subscriptions(ss, flat, nesting_level); DEBUG_LOG("rli_create_content(): freeing flat list\n"); free_flat_list(flat); return RES_OK; } static void clear_change_flags(rl_subscription_t *s) { int i, cnt; virtual_subscription_t *vs; cnt = ptr_vector_size(&s->vs); for (i = 0; i < cnt; i++) { vs = ptr_vector_get(&s->vs, i); if (!vs) continue; vs->changed = 0; } s->changed = 0; } /************* RL subscription manipulation function ************/ rl_subscription_t *rls_alloc_subscription(rls_subscription_type_t type) { rl_subscription_t *s; s = (rl_subscription_t*)mem_alloc(sizeof(rl_subscription_t)); if (!s) { LOG(L_ERR, "rls_alloc_subscription(): can't allocate memory\n"); return NULL; } memset(s, 0, sizeof(*s)); s->u.external.status = subscription_uninitialized; s->u.external.usr_data = s; s->doc_version = 0; s->changed = 0; s->type = type; s->dbid[0] = 0; /* s->first_vs = NULL; s->last_vs = NULL; */ str_clear(&s->from_uid); ptr_vector_init(&s->vs, 4); return s; } int rls_create_subscription(struct sip_msg *m, rl_subscription_t **dst, flat_list_t *flat, xcap_query_params_t *params) { rl_subscription_t *s; str from_uid = STR_NULL; int res; if (!dst) return RES_INTERNAL_ERR; *dst = NULL; s = rls_alloc_subscription(rls_external_subscription); if (!s) { LOG(L_ERR, "rls_create_new(): can't allocate memory\n"); return RES_MEMORY_ERR; } generate_db_id(&s->dbid, s); res = sm_init_subscription_nolock(rls_manager, &s->u.external, m); if (res != RES_OK) { rls_free(s); return res; } if (params) { if (dup_xcap_params(&s->xcap_params, params) < 0) { ERR("can't duplicate xcap_params\n"); rls_free(s); return -1; } } /* store pointer to this RL subscription as user data * of (low level) subscription */ s->u.external.usr_data = s; if (get_from_uid(&from_uid, m) < 0) str_clear(&s->from_uid); else str_dup(&s->from_uid, &from_uid); /* res = set_rls_info(m, s, xcap_root); if (res != 0) { rls_free(s); return res; }*/ res = add_virtual_subscriptions(s, flat, max_list_nesting_level); if (res != 0) { rls_free(s); return res; } if (use_db) { if (rls_db_add(s) != 0) { rls_free(s); return RES_INTERNAL_ERR; /* FIXME RES_DB_ERR */ } } *dst = s; return RES_OK; } int rls_find_subscription(str *from_tag, str *to_tag, str *call_id, rl_subscription_t **dst) { subscription_data_t *s; int res; *dst = NULL; res = sm_find_subscription(rls_manager, from_tag, to_tag, call_id, &s); if ((res == RES_OK) && (s)) { if (!s->usr_data) { LOG(L_ERR, "found subscription without filled usr_data\n"); return RES_INTERNAL_ERR; } else { *dst = (rl_subscription_t*)s->usr_data; return RES_OK; } } return RES_NOT_FOUND; } int rls_refresh_subscription(struct sip_msg *m, rl_subscription_t *s) { int res; if (!s) return RES_INTERNAL_ERR; if (s->type != rls_external_subscription) return RES_INTERNAL_ERR; res = sm_refresh_subscription_nolock(rls_manager, &s->u.external, m); if (use_db) rls_db_update(s); return res; } void rls_remove(rl_subscription_t *s) { if (!s) return; if (use_db) rls_db_remove(s); rls_free(s); } void rls_free(rl_subscription_t *s) { int i, cnt; virtual_subscription_t *vs; if (!s) return; if (use_db) rls_db_remove(s); cnt = ptr_vector_size(&s->vs); for (i = 0; i < cnt; i++) { vs = ptr_vector_get(&s->vs, i); if (!vs) continue; vs_free(vs); } if (s->type == rls_external_subscription) { sm_release_subscription_nolock(rls_manager, &s->u.external); /* free ONLY for external subscriptions */ free_xcap_params_content(&s->xcap_params); } else { /* release_internal_subscription(s); */ /* don't free xcap_params */ } ptr_vector_destroy(&s->vs); str_free_content(&s->from_uid); mem_free(s); } /* void rls_notify_cb(struct cell* t, struct sip_msg* msg, int code, void *param) */ static void rls_notify_cb(struct cell* t, int type, struct tmcb_params* params) { rls_notify_cb_param_t *cbd = NULL; if (!params) return; if (params->param) cbd = (rls_notify_cb_param_t *)*(params->param); if (!cbd) { ERR("BUG empty cbd parameter given to callback function\n"); return; } if (params->code >= 300) { /* what else can we do with 3xx ? */ int ignore = 0; switch (params->code) { case 408: if (rls_ignore_408_on_notify) ignore = 1; /* due to eyeBeam's problems with processing more NOTIFY * requests sent consequently without big delay */ break; } if (!ignore) { WARN("destroying subscription from callback due to %d response on NOTIFY\n", params->code); destroy_subscription(cbd); TRACE("subscription destroyed!!!\n"); } } mem_free(cbd); } static int rls_generate_notify_ext(rl_subscription_t *s, int full_info) { /* !!! the main mutex must be locked here !!! */ int res; str doc; dstring_t dstr; str headers, content_type; static str method = STR_STATIC_INIT(METHOD_NOTIFY); dlg_t *dlg; int exp_time = 0; char expiration[32]; str body = STR_STATIC_INIT(""); int removed = 0; dlg = s->u.external.dialog; if (!dlg) return -1; DEBUG("generating external notify\n"); str_clear(&doc); str_clear(&content_type); if (sm_subscription_pending(&s->u.external) != 0) { /* create the document only for non-pending subscriptions */ if (create_rlmi_document(&doc, &content_type, s, full_info) != 0) { return -1; } } exp_time = sm_subscription_expires_in(rls_manager, &s->u.external); sprintf(expiration, ";expires=%d\r\n", exp_time); dstr_init(&dstr, 256); dstr_append_zt(&dstr, "Subscription-State: "); switch (s->u.external.status) { case subscription_active: dstr_append_zt(&dstr, "active"); dstr_append_zt(&dstr, expiration); break; case subscription_pending: dstr_append_zt(&dstr, "pending"); dstr_append_zt(&dstr, expiration); break; case subscription_terminated_pending: case subscription_terminated: dstr_append_zt(&dstr, "terminated\r\n"); break; case subscription_terminated_pending_to: case subscription_terminated_to: dstr_append_zt(&dstr, "terminated;reason=timeout\r\n"); break; case subscription_uninitialized: dstr_append_zt(&dstr, "pending\r\n"); /* this is an error ! */ LOG(L_ERR, "sending NOTIFY for an unitialized subscription!\n"); break; } dstr_append_str(&dstr, &s->u.external.contact); /* required by RFC 3261 */ dstr_append_zt(&dstr, "Max-Forwards: 70\r\n"); dstr_append_zt(&dstr, "Event: "); dstr_append_str(&dstr, rls_get_package(s)); dstr_append_zt(&dstr, "\r\n"); dstr_append_zt(&dstr, "Require: eventlist\r\nContent-Type: "); dstr_append_str(&dstr, &content_type); dstr_append_zt(&dstr, "\r\n"); res = dstr_get_str(&dstr, &headers); dstr_destroy(&dstr); if (res >= 0) { /* DEBUG("sending NOTIFY message to %.*s (subscription %p)\n", dlg->rem_uri.len, ZSW(dlg->rem_uri.s), s); */ if (!is_str_empty(&doc)) body = doc; if (sm_subscription_terminated(&s->u.external) == 0) { /* doesn't matter if delivered or not, it will be freed otherwise !!! */ res = tmb.t_request_within(&method, &headers, &body, dlg, 0, 0); if (res >= 0) clear_change_flags(s); } else { rls_notify_cb_param_t *cbd = create_notify_cb_param(s); if (!cbd) { ERR("Can't create notify cb data! Freeing RL subscription.\n"); rls_remove(s); /* ?????? */ removed = 1; res = -13; } else { /* the subscritpion will be destroyed if NOTIFY delivery problems */ /* rls_unlock(); the callback locks this mutex ! */ /* !!!! FIXME: callbacks can't be safely used (may be called or not, * may free memory automaticaly or not) !!! */ res = tmb.t_request_within(&method, &headers, &body, dlg, rls_notify_cb, cbd); /* res = tmb.t_request_within(&method, &headers, &body, dlg, rls_notify_cb, s); */ /* rls_lock(); the callback locks this mutex ! */ if (res < 0) { /* does this mean, that the callback was not called ??? */ ERR("t_request_within FAILED: %d! Freeing RL subscription.\n", res); rls_remove(s); /* ?????? */ removed = 1; } else clear_change_flags(s); } } } if (doc.s) cds_free(doc.s); if (content_type.s) cds_free(content_type.s); if (headers.s) cds_free(headers.s); if ((!removed) && use_db) rls_db_update(s); if (res < 0) DEBUG("external notify NOT generated\n"); else DEBUG("external notify generated\n"); return res; } /* static raw_presence_info_t* rls2raw_presence_info(rl_subscription_t *s) { raw_presence_info_t *info; info = create_raw_presence_info(s->u.internal.record_id); if (!info) return info; str_clear(&info->pres_doc); str_clear(&info->content_type); DEBUG_LOG(" ... create RLMI document\n"); create_rlmi_document(&info->pres_doc, &info->content_type, s, 1); return info; } */ static int rls_generate_notify_int(rl_subscription_t *s, int full_info) { /* generate internal notification */ str_t doc, content_type; /* TRACE("generating internal list notify\n"); */ if (!s->u.internal.vs) return 1; DBG("generating internal rls notification\n"); /* raw = rls2raw_presence_info(s); */ if (create_rlmi_document(&doc, &content_type, s, full_info) < 0) { ERR("can't generate internal notification document\n"); return -1; } clear_change_flags(s); /* documents are given to VS (we don't care about them * more - no free, ... */ process_internal_notify(s->u.internal.vs, &doc, &content_type); return 0; } int rls_generate_notify(rl_subscription_t *s, int full_info) { /* !!! the main mutex must be locked here !!! */ DBG("generating rls notification\n"); if (!s) { ERR("called with subscription\n"); return -1; } switch (s->type) { case rls_external_subscription: return rls_generate_notify_ext(s, full_info); case rls_internal_subscription: return rls_generate_notify_int(s, full_info); } return -1; } int rls_prepare_subscription_response(rl_subscription_t *s, struct sip_msg *m) { /* char *hdr = "Supported: eventlist\r\n"; */ char *hdr = "Require: eventlist\r\n"; if (s->type != rls_external_subscription) return -1; if (!add_lump_rpl(m, hdr, strlen(hdr), LUMP_RPL_HDR)) return -1; return sm_prepare_subscription_response(rls_manager, &s->u.external, m); } /** returns the count of seconds remaining to subscription expiration */ int rls_subscription_expires_in(rl_subscription_t *s) { if (s->type == rls_external_subscription) return sm_subscription_expires_in(rls_manager, &s->u.external); else return -1; } /* static str_t notifier_name = { s: "rls", len: 3 }; */ /* static str_t pres_list_package = { s: "presence.list", len: 13 }; int is_presence_list_package(const str_t *package) { return (str_case_equals(package, &pres_list_package) == 0); }*/ int rls_create_internal_subscription(virtual_subscription_t *vs, rl_subscription_t **dst, flat_list_t *flat, int nesting_level) { rl_subscription_t *rls; /* try to make subscription and release it if internal subscription * not created */ if (dst) *dst = NULL; rls = rls_alloc_subscription(rls_internal_subscription); if (!rls) { ERR("processing INTERNAL RLS subscription - memory allocation error\n"); return -1; } rls->u.internal.record_id = &vs->uri; /* !!! NEVER !!! free this */ rls->u.internal.package = rls_get_package(vs->subscription); /* !!! NEVER !!! free this */ rls->u.internal.subscriber_id = rls_get_subscriber(vs->subscription); /* !!! NEVER !!! free this */ rls->xcap_params = vs->subscription->xcap_params; /* !!! NEVER free this !!! */ rls->u.internal.vs = vs; if (dst) *dst = rls; DBG("creating internal subscription to %.*s (VS %p)\n", FMT_STR(*rls->u.internal.record_id), rls->u.internal.vs); if (add_virtual_subscriptions(rls, flat, nesting_level) != 0) { rls_free(rls); if (dst) *dst = NULL; return -1; } rls_generate_notify(rls, 1); return 0; }