/**
* @file sipe-subscriptions.c
*
* pidgin-sipe
*
* Copyright (C) 2010-2016 SIPE Project <http://sipe.sourceforge.net/>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "sipe-common.h"
#include "sipmsg.h"
#include "sip-transport.h"
#include "sipe-backend.h"
#include "sipe-buddy.h"
#include "sipe-core.h"
#include "sipe-core-private.h"
#include "sipe-dialog.h"
#include "sipe-mime.h"
#include "sipe-nls.h"
#include "sipe-notify.h"
#include "sipe-schedule.h"
#include "sipe-subscriptions.h"
#include "sipe-ucs.h"
#include "sipe-utils.h"
#include "sipe-xml.h"
/* RFC3265 subscription */
struct sip_subscription {
struct sip_dialog dialog;
gchar *event;
GSList *buddies; /* batched subscriptions */
};
static void sipe_subscription_free(struct sip_subscription *subscription)
{
if (!subscription) return;
g_free(subscription->event);
sipe_utils_slist_free_full(subscription->buddies, g_free);
/* NOTE: use cast to prevent BAD_FREE warning from Coverity */
sipe_dialog_free((struct sip_dialog *) subscription);
}
void sipe_subscriptions_init(struct sipe_core_private *sipe_private)
{
sipe_private->subscriptions = g_hash_table_new_full(g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify)sipe_subscription_free);
}
static void sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
gpointer value, gpointer user_data)
{
struct sip_subscription *subscription = value;
struct sip_dialog *dialog = &subscription->dialog;
struct sipe_core_private *sipe_private = user_data;
gchar *contact = get_contact(sipe_private);
gchar *hdr = g_strdup_printf(
"Event: %s\r\n"
"Expires: 0\r\n"
"Contact: %s\r\n", subscription->event, contact);
g_free(contact);
/* Rate limit to max. 25 requests per seconds */
g_usleep(1000000 / 25);
sip_transport_subscribe(sipe_private,
dialog->with,
hdr,
NULL,
dialog,
NULL);
g_free(hdr);
}
void sipe_subscriptions_unsubscribe(struct sipe_core_private *sipe_private)
{
/* unsubscribe all */
g_hash_table_foreach(sipe_private->subscriptions,
sipe_unsubscribe_cb,
sipe_private);
}
void sipe_subscriptions_destroy(struct sipe_core_private *sipe_private)
{
g_hash_table_destroy(sipe_private->subscriptions);
}
static void sipe_subscription_remove(struct sipe_core_private *sipe_private,
const gchar *key)
{
if (g_hash_table_lookup(sipe_private->subscriptions, key)) {
g_hash_table_remove(sipe_private->subscriptions, key);
SIPE_DEBUG_INFO("sipe_subscription_remove: %s", key);
}
}
/**
* Generate subscription key
*
* @param event event name (must not by @c NULL)
* @param uri presence URI (ignored if @c event != "presence")
*
* @return key string. Must be g_free()'d after use.
*/
static gchar *sipe_subscription_key(const gchar *event,
const gchar *uri)
{
if (!g_ascii_strcasecmp(event, "presence"))
/* Subscription is identified by <presence><uri> key */
return(sipe_utils_presence_key(uri));
else
/* Subscription is identified by <event> key */
return(g_strdup_printf("<%s>", event));
}
static struct sip_dialog *sipe_subscribe_dialog(struct sipe_core_private *sipe_private,
const gchar *key)
{
struct sip_dialog *dialog = g_hash_table_lookup(sipe_private->subscriptions,
key);
SIPE_DEBUG_INFO("sipe_subscribe_dialog: dialog for '%s' is %s", key, dialog ? "not NULL" : "NULL");
return(dialog);
}
static void sipe_subscription_expiration(struct sipe_core_private *sipe_private,
struct sipmsg *msg,
const gchar *event);
static gboolean process_subscribe_response(struct sipe_core_private *sipe_private,
struct sipmsg *msg,
struct transaction *trans)
{
const gchar *event = sipmsg_find_header(msg, "Event");
/* No Event header - error or 2005 Public IM Connectivity (PIC) */
if (!event) {
struct sipmsg *request_msg = trans->msg;
event = sipmsg_find_header(request_msg, "Event");
}
if (event) {
gchar *with = parse_from(sipmsg_find_header(msg, "To"));
const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
gboolean terminated = subscription_state && strstr(subscription_state, "terminated");
gchar *key = sipe_subscription_key(event, with);
/*
* @TODO: does the server send this only for one-off
* subscriptions, i.e. the ones which anyway
* have "Expires: 0"?
*/
if (terminated)
SIPE_DEBUG_INFO("process_subscribe_response: subscription '%s' to '%s' was terminated",
event, with);
/* 400 Bad request */
if (msg->response == 400) {
SIPE_DEBUG_INFO("process_subscribe_response: subscription '%s' to '%s' failed",
event, with);
sipe_subscription_remove(sipe_private, key);
if (sipe_strcase_equal(event, "presence")) {
sipe_backend_notify_error(SIPE_CORE_PUBLIC,
_("Presence subscription failed!"),
_("One or more buddies will therefore permanently show as offline.\n\nPlease check that there are no corrupted SIP URIs in your contacts list."));
}
/* 481 Call Leg Does Not Exist */
} else if ((msg->response == 481) || terminated) {
sipe_subscription_remove(sipe_private, key);
/* 488 Not acceptable here */
} else if (msg->response == 488) {
SIPE_DEBUG_INFO("process_subscribe_response: subscription '%s' to '%s' was rejected",
event, with);
sipe_subscription_remove(sipe_private, key);
if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) &&
sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts")) {
SIPE_DEBUG_INFO_NOFORMAT("no contact list available - assuming Lync 2013+ and Unified Contact Store (UCS)");
SIPE_CORE_PRIVATE_FLAG_SET(LYNC2013);
sipe_ucs_init(sipe_private, TRUE);
}
/* create/store subscription dialog if not yet */
} else if (msg->response == 200) {
struct sip_dialog *dialog = sipe_subscribe_dialog(sipe_private, key);
if (!dialog) {
struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for event '%s'",
key);
g_hash_table_insert(sipe_private->subscriptions,
key,
subscription);
key = NULL; /* table takes ownership of key */
subscription->dialog.callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
subscription->dialog.cseq = sipmsg_parse_cseq(msg);
subscription->dialog.with = g_strdup(with);
subscription->event = g_strdup(event);
dialog = &subscription->dialog;
}
sipe_dialog_parse(dialog, msg, TRUE);
sipe_subscription_expiration(sipe_private, msg, event);
}
g_free(key);
g_free(with);
}
if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
process_incoming_notify(sipe_private, msg);
return(TRUE);
}
/**
* common subscription code
*/
static void sipe_subscribe(struct sipe_core_private *sipe_private,
const gchar *uri,
const gchar *event,
const gchar *accept,
const gchar *addheaders,
const gchar *body,
struct sip_dialog *dialog)
{
gchar *contact = get_contact(sipe_private);
gchar *hdr = g_strdup_printf(
"Event: %s\r\n"
"Accept: %s\r\n"
"Supported: com.microsoft.autoextend\r\n"
"Supported: ms-benotify\r\n"
"Proxy-Require: ms-benotify\r\n"
"Supported: ms-piggyback-first-notify\r\n"
"%s"
"Contact: %s\r\n",
event,
accept,
addheaders ? addheaders : "",
contact);
g_free(contact);
sip_transport_subscribe(sipe_private,
uri,
hdr,
body,
dialog,
process_subscribe_response);
g_free(hdr);
}
/**
* common subscription code for self-subscriptions
*/
static void sipe_subscribe_self(struct sipe_core_private *sipe_private,
const gchar *event,
const gchar *accept,
const gchar *addheaders,
const gchar *body)
{
gchar *self = sip_uri_self(sipe_private);
gchar *key = sipe_subscription_key(event, self);
struct sip_dialog *dialog = sipe_subscribe_dialog(sipe_private, key);
sipe_subscribe(sipe_private,
self,
event,
accept,
addheaders,
body,
dialog);
g_free(key);
g_free(self);
}
static void sipe_subscribe_presence_wpending(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER void *unused)
{
sipe_subscribe_self(sipe_private,
"presence.wpending",
"text/xml+msrtc.wpending",
NULL,
NULL);
}
/**
* Subscribe roaming ACL
*/
static void sipe_subscribe_roaming_acl(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER void *unused)
{
sipe_subscribe_self(sipe_private,
"vnd-microsoft-roaming-ACL",
"application/vnd-microsoft-roaming-acls+xml",
NULL,
NULL);
}
/**
* Subscribe roaming contacts
*/
static void sipe_subscribe_roaming_contacts(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER void *unused)
{
const gchar *addheaders = NULL;
/* indicate that we support Unified Contact Store (UCS) */
if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
addheaders = "Supported: ms-ucs\r\n";
sipe_subscribe_self(sipe_private,
"vnd-microsoft-roaming-contacts",
"application/vnd-microsoft-roaming-contacts+xml",
addheaders,
NULL);
}
/**
* OCS 2005 version
*/
static void sipe_subscribe_roaming_provisioning(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER void *unused)
{
sipe_subscribe_self(sipe_private,
"vnd-microsoft-provisioning",
"application/vnd-microsoft-roaming-provisioning+xml",
"Expires: 0\r\n",
NULL);
}
/**
* Subscription for provisioning information to help with initial
* configuration. This subscription is a one-time query (denoted by the
* Expires header, which asks for 0 seconds for the subscription lifetime).
* This subscription asks for server configuration, meeting policies, and
* policy settings that Communicator must enforce.
*/
static void sipe_subscribe_roaming_provisioning_v2(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER void *unused)
{
sipe_subscribe_self(sipe_private,
"vnd-microsoft-provisioning-v2",
"application/vnd-microsoft-roaming-provisioning-v2+xml",
"Expires: 0\r\n"
"Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n",
"<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
" <provisioningGroup name=\"ServerConfiguration\"/>"
" <provisioningGroup name=\"meetingPolicy\"/>"
" <provisioningGroup name=\"persistentChatConfiguration\"/>"
" <provisioningGroup name=\"ucPolicy\"/>"
"</provisioningGroupList>");
}
/**
* To request for presence information about the user, access level settings
* that have already been configured by the user to control who has access to
* what information, and the list of contacts who currently have outstanding
* subscriptions.
*
* We wait for (BE)NOTIFY messages with some info change (categories,
* containers, subscribers)
*/
static void sipe_subscribe_roaming_self(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER void *unused)
{
sipe_subscribe_self(sipe_private,
"vnd-microsoft-roaming-self",
"application/vnd-microsoft-roaming-self+xml",
"Content-Type: application/vnd-microsoft-roaming-self+xml\r\n",
"<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
"<roaming type=\"categories\"/>"
"<roaming type=\"containers\"/>"
"<roaming type=\"subscribers\"/></roamingList>");
}
static void sipe_presence_timeout_mime_cb(gpointer user_data,
SIPE_UNUSED_PARAMETER const GSList *fields,
const gchar *body,
gsize length)
{
GSList **buddies = user_data;
sipe_xml *xml = sipe_xml_parse(body, length);
if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
const gchar *uri = sipe_xml_attribute(xml, "uri");
const sipe_xml *xn_category;
/**
* automaton: presence is never expected to change
*
* see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
*/
for (xn_category = sipe_xml_child(xml, "category");
xn_category;
xn_category = sipe_xml_twin(xn_category)) {
if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
"contactCard")) {
const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
if (node) {
char *boolean = sipe_xml_data(node);
if (sipe_strequal(boolean, "true")) {
SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
uri);
uri = NULL;
}
g_free(boolean);
}
break;
}
}
if (uri) {
*buddies = g_slist_append(*buddies, sip_uri(uri));
}
}
sipe_xml_free(xml);
}
static void sipe_subscribe_presence_batched_schedule(struct sipe_core_private *sipe_private,
const gchar *action_name,
const gchar *who,
GSList *buddies,
int timeout);
static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
struct sipmsg *msg,
const gchar *who,
int timeout)
{
const char *ctype = sipmsg_find_header(msg, "Content-Type");
gchar *action_name = sipe_utils_presence_key(who);
SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
if (ctype &&
strstr(ctype, "multipart") &&
(strstr(ctype, "application/rlmi+xml") ||
strstr(ctype, "application/msrtc-event-categories+xml"))) {
GSList *buddies = NULL;
sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
if (buddies)
sipe_subscribe_presence_batched_schedule(sipe_private,
action_name,
who,
buddies,
timeout);
} else {
sipe_schedule_seconds(sipe_private,
action_name,
g_strdup(who),
timeout,
sipe_subscribe_presence_single_cb,
g_free);
SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d seconds", who, timeout);
}
g_free(action_name);
}
/**
* @param expires not respected if set to negative value (E.g. -1)
*/
void sipe_subscribe_conference(struct sipe_core_private *sipe_private,
const gchar *id,
gboolean expires)
{
sipe_subscribe(sipe_private,
id,
"conference",
"application/conference-info+xml",
expires ? "Expires: 0\r\n" : NULL,
NULL,
NULL);
}
/**
* code for presence subscription
*/
static void sipe_subscribe_presence_buddy(struct sipe_core_private *sipe_private,
const gchar *uri,
const gchar *request,
const gchar *body)
{
gchar *key = sipe_utils_presence_key(uri);
sip_transport_subscribe(sipe_private,
uri,
request,
body,
sipe_subscribe_dialog(sipe_private, key),
process_subscribe_response);
g_free(key);
}
/**
* if to == NULL: initial single subscription
* OCS2005: send to URI
* OCS2007: send to self URI
*
* if to != NULL:
* Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
* The user sends a single SUBSCRIBE request to the subscribed contact.
* The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
*
*/
void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
const gchar *uri,
const gchar *to)
{
gchar *self = NULL;
gchar *contact = get_contact(sipe_private);
gchar *request;
gchar *content = NULL;
const gchar *additional = "";
const gchar *content_type = "";
struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
uri);
if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
content = g_strdup_printf("<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
"<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
"<resource uri=\"%s\"%s\n"
"</adhocList>\n"
"<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
"<category name=\"calendarData\"/>\n"
"<category name=\"contactCard\"/>\n"
"<category name=\"note\"/>\n"
"<category name=\"state\"/>\n"
"</categoryList>\n"
"</action>\n"
"</batchSub>",
sipe_private->username,
uri,
sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>");
if (!to) {
additional = "Require: adhoclist, categoryList\r\n" \
"Supported: eventlist\r\n";
to = self = sip_uri_self(sipe_private);
}
} else {
additional = "Supported: com.microsoft.autoextend\r\n";
if (!to)
to = uri;
}
if (sbuddy)
sbuddy->just_added = FALSE;
request = g_strdup_printf("Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
"Supported: ms-piggyback-first-notify\r\n"
"%s%sSupported: ms-benotify\r\n"
"Proxy-Require: ms-benotify\r\n"
"Event: presence\r\n"
"Contact: %s\r\n",
additional,
content_type,
contact);
g_free(contact);
sipe_subscribe_presence_buddy(sipe_private, to, request, content);
g_free(content);
g_free(self);
g_free(request);
}
void sipe_subscribe_presence_single_cb(struct sipe_core_private *sipe_private,
gpointer uri)
{
sipe_subscribe_presence_single(sipe_private, uri, NULL);
}
/**
* Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
* Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
* The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
* A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
* This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
*/
static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
gchar *resources_uri,
const gchar *to)
{
gchar *contact = get_contact(sipe_private);
gchar *request;
gchar *content;
const gchar *require = "";
const gchar *accept = "";
const gchar *autoextend = "";
const gchar *content_type;
if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
require = ", categoryList";
accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
content_type = "application/msrtc-adrl-categorylist+xml";
content = g_strdup_printf("<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
"<action name=\"subscribe\" id=\"63792024\">\n"
"<adhocList>\n%s</adhocList>\n"
"<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
"<category name=\"calendarData\"/>\n"
"<category name=\"contactCard\"/>\n"
"<category name=\"note\"/>\n"
"<category name=\"state\"/>\n"
"</categoryList>\n"
"</action>\n"
"</batchSub>",
sipe_private->username,
resources_uri);
} else {
autoextend = "Supported: com.microsoft.autoextend\r\n";
content_type = "application/adrl+xml";
content = g_strdup_printf("<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
"<create xmlns=\"\">\n%s</create>\n"
"</adhoclist>\n",
sipe_private->username,
sipe_private->username,
resources_uri);
}
g_free(resources_uri);
request = g_strdup_printf("Require: adhoclist%s\r\n"
"Supported: eventlist\r\n"
"Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
"Supported: ms-piggyback-first-notify\r\n"
"%sSupported: ms-benotify\r\n"
"Proxy-Require: ms-benotify\r\n"
"Event: presence\r\n"
"Content-Type: %s\r\n"
"Contact: %s\r\n",
require,
accept,
autoextend,
content_type,
contact);
g_free(contact);
sipe_subscribe_presence_buddy(sipe_private, to, request, content);
g_free(content);
g_free(request);
}
struct presence_batched_routed {
gchar *host;
const GSList *buddies; /* points to subscription->buddies */
};
static void sipe_subscribe_presence_batched_routed_free(gpointer payload)
{
struct presence_batched_routed *data = payload;
g_free(data->host);
g_free(payload);
}
static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
gpointer payload)
{
struct presence_batched_routed *data = payload;
const GSList *buddies = data->buddies;
gchar *resources_uri = g_strdup("");
while (buddies) {
gchar *tmp = resources_uri;
resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
g_free(tmp);
buddies = buddies->next;
}
sipe_subscribe_presence_batched_to(sipe_private,
resources_uri,
data->host);
}
static void sipe_subscribe_presence_batched_schedule(struct sipe_core_private *sipe_private,
const gchar *action_name,
const gchar *who,
GSList *buddies,
int timeout)
{
struct sip_subscription *subscription = g_hash_table_lookup(sipe_private->subscriptions,
action_name);
struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
if (subscription->buddies) {
/* merge old and new list */
GSList *entry = buddies;
while (entry) {
subscription->buddies = sipe_utils_slist_insert_unique_sorted(subscription->buddies,
g_strdup(entry->data),
(GCompareFunc) g_ascii_strcasecmp,
g_free);
entry = entry->next;
}
sipe_utils_slist_free_full(buddies, g_free);
} else {
/* no list yet, simply take ownership of whole list */
subscription->buddies = buddies;
}
payload->host = g_strdup(who);
payload->buddies = subscription->buddies;
sipe_schedule_seconds(sipe_private,
action_name,
payload,
timeout,
sipe_subscribe_presence_batched_routed,
sipe_subscribe_presence_batched_routed_free);
SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
}
static void sipe_subscribe_resource_uri_with_context(const gchar *name,
gpointer value,
gchar **resources_uri)
{
struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
gchar *tmp = *resources_uri;
/* should be enough to include context one time */
if (sbuddy)
sbuddy->just_added = FALSE;
*resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
g_free(tmp);
}
static void sipe_subscribe_resource_uri(const char *name,
SIPE_UNUSED_PARAMETER gpointer value,
gchar **resources_uri)
{
gchar *tmp = *resources_uri;
*resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
g_free(tmp);
}
/**
* A callback for g_hash_table_foreach
*/
static void schedule_buddy_resubscription_cb(char *buddy_name,
SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
struct sipe_core_private *sipe_private)
{
guint time_range = (sipe_buddy_count(sipe_private) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
/*
* g_hash_table_size() can never return 0, otherwise this function
* wouldn't be called :-) But to keep Coverity happy...
*/
if (time_range) {
gchar *action_name = sipe_utils_presence_key(buddy_name);
guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
sipe_schedule_mseconds(sipe_private,
action_name,
g_strdup(buddy_name),
timeout,
sipe_subscribe_presence_single_cb,
g_free);
g_free(action_name);
}
}
void sipe_subscribe_presence_initial(struct sipe_core_private *sipe_private)
{
/*
* Subscribe to buddies, but only do it once.
* We'll resubsribe to them based on the Expire field values.
*/
if (!SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
/* Only try to subscribe if we have any buddies */
if (sipe_buddy_count(sipe_private) > 0) {
if (SIPE_CORE_PRIVATE_FLAG_IS(BATCHED_SUPPORT)) {
gchar *to = sip_uri_self(sipe_private);
gchar *resources_uri = g_strdup("");
if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
sipe_buddy_foreach(sipe_private,
(GHFunc) sipe_subscribe_resource_uri_with_context,
&resources_uri);
} else {
sipe_buddy_foreach(sipe_private,
(GHFunc) sipe_subscribe_resource_uri,
&resources_uri);
}
sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
g_free(to);
} else {
sipe_buddy_foreach(sipe_private,
(GHFunc) schedule_buddy_resubscription_cb,
sipe_private);
}
}
SIPE_CORE_PRIVATE_FLAG_SET(SUBSCRIBED_BUDDIES);
}
}
void sipe_subscribe_poolfqdn_resource_uri(const char *host,
GSList *server,
struct sipe_core_private *sipe_private)
{
struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
payload->host = g_strdup(host);
payload->buddies = server;
sipe_subscribe_presence_batched_routed(sipe_private,
payload);
sipe_subscribe_presence_batched_routed_free(payload);
sipe_utils_slist_free_full(server, g_free);
}
/*
* subscription expiration handling
*/
struct event_subscription_data {
const gchar *event;
sipe_schedule_action callback;
guint flags;
};
#define EVENT_OCS2005 0x00000001
#define EVENT_OCS2007 0x00000002
static const struct event_subscription_data events_table[] =
{
/*
* For 2007+ it does not make sence to subscribe to:
*
* presence.wpending
* vnd-microsoft-roaming-ACL
* vnd-microsoft-provisioning (not v2)
*
* These are only needed as backward compatibility for older clients
*
* For 2005- we publish our initial statuses only after we received
* our existing UserInfo data in response to self subscription.
* Only in this case we won't override existing UserInfo data
* set earlier or by other client on our behalf.
*
* For 2007+ we publish our initial statuses and calendar data only
* after we received our existing publications in roaming_self.
* Only in this case we know versions of current publications made
* on our behalf.
*/
{ "presence.wpending", sipe_subscribe_presence_wpending,
EVENT_OCS2005 },
{ "vnd-microsoft-roaming-ACL", sipe_subscribe_roaming_acl,
EVENT_OCS2005 },
{ "vnd-microsoft-roaming-contacts", sipe_subscribe_roaming_contacts,
EVENT_OCS2005 | EVENT_OCS2007 },
{ "vnd-microsoft-provisioning", sipe_subscribe_roaming_provisioning,
EVENT_OCS2005 },
{ "vnd-microsoft-provisioning-v2", sipe_subscribe_roaming_provisioning_v2,
EVENT_OCS2007 },
{ "vnd-microsoft-roaming-self", sipe_subscribe_roaming_self,
EVENT_OCS2007 },
{ NULL, NULL, 0 }
};
static void sipe_subscription_expiration(struct sipe_core_private *sipe_private,
struct sipmsg *msg,
const gchar *event)
{
const gchar *expires_header = sipmsg_find_header(msg, "Expires");
guint timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
if (timeout) {
/* 2 min ahead of expiration */
if (timeout > 240) timeout -= 120;
if (sipe_strcase_equal(event, "presence")) {
gchar *who = parse_from(sipmsg_find_header(msg, "To"));
if (SIPE_CORE_PRIVATE_FLAG_IS(BATCHED_SUPPORT)) {
sipe_process_presence_timeout(sipe_private, msg, who, timeout);
} else {
gchar *action_name = sipe_utils_presence_key(who);
sipe_schedule_seconds(sipe_private,
action_name,
g_strdup(who),
timeout,
sipe_subscribe_presence_single_cb,
g_free);
g_free(action_name);
SIPE_DEBUG_INFO("Resubscription single contact '%s' in %d seconds", who, timeout);
}
g_free(who);
} else {
const struct event_subscription_data *esd;
for (esd = events_table; esd->event; esd++) {
if (sipe_strcase_equal(event, esd->event)) {
gchar *action_name = g_strdup_printf("<%s>", event);
sipe_schedule_seconds(sipe_private,
action_name,
NULL,
timeout,
esd->callback,
NULL);
g_free(action_name);
SIPE_DEBUG_INFO("Resubscription to event '%s' in %d seconds", event, timeout);
break;
}
}
}
}
}
/*
* Initial event subscription
*/
void sipe_subscription_self_events(struct sipe_core_private *sipe_private)
{
const guint mask = SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? EVENT_OCS2007 : EVENT_OCS2005;
const struct event_subscription_data *esd;
/* subscribe to those events which are selected for
* this version and are allowed by the server */
for (esd = events_table; esd->event; esd++)
if ((esd->flags & mask) &&
(g_slist_find_custom(sipe_private->allowed_events,
esd->event,
(GCompareFunc) g_ascii_strcasecmp) != NULL))
(*esd->callback)(sipe_private, NULL);
}
/*
Local Variables:
mode: c
c-file-style: "bsd"
indent-tabs-mode: t
tab-width: 8
End:
*/