2 * @file sipe-subscriptions.c
6 * Copyright (C) 2010-2013 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include "sipe-common.h"
29 #include "sip-transport.h"
30 #include "sipe-backend.h"
31 #include "sipe-buddy.h"
32 #include "sipe-core.h"
33 #include "sipe-core-private.h"
34 #include "sipe-dialog.h"
35 #include "sipe-notify.h"
36 #include "sipe-schedule.h"
37 #include "sipe-subscriptions.h"
38 #include "sipe-utils.h"
40 /* RFC3265 subscription */
41 struct sip_subscription
{
42 struct sip_dialog dialog
;
46 static void sipe_subscription_free(struct sip_subscription
*subscription
)
48 if (!subscription
) return;
50 g_free(subscription
->event
);
51 /* NOTE: use cast to prevent BAD_FREE warning from Coverity */
52 sipe_dialog_free((struct sip_dialog
*) subscription
);
55 void sipe_subscriptions_init(struct sipe_core_private
*sipe_private
)
57 sipe_private
->subscriptions
= g_hash_table_new_full(g_str_hash
,
60 (GDestroyNotify
)sipe_subscription_free
);
63 static void sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key
,
64 gpointer value
, gpointer user_data
)
66 struct sip_subscription
*subscription
= value
;
67 struct sip_dialog
*dialog
= &subscription
->dialog
;
68 struct sipe_core_private
*sipe_private
= user_data
;
69 gchar
*contact
= get_contact(sipe_private
);
70 gchar
*hdr
= g_strdup_printf(
73 "Contact: %s\r\n", subscription
->event
, contact
);
76 /* Rate limit to max. 25 requests per seconds */
77 g_usleep(1000000 / 25);
79 sip_transport_subscribe(sipe_private
,
89 void sipe_subscriptions_unsubscribe(struct sipe_core_private
*sipe_private
)
92 g_hash_table_foreach(sipe_private
->subscriptions
,
98 void sipe_subscriptions_destroy(struct sipe_core_private
*sipe_private
)
100 g_hash_table_destroy(sipe_private
->subscriptions
);
103 static void sipe_subscription_remove(struct sipe_core_private
*sipe_private
,
106 if (g_hash_table_lookup(sipe_private
->subscriptions
, key
)) {
107 g_hash_table_remove(sipe_private
->subscriptions
, key
);
108 SIPE_DEBUG_INFO("sipe_subscription_remove: %s", key
);
113 * Generate subscription key
115 * @param event event name (must not by @c NULL)
116 * @param uri presence URI (ignored if @c event != "presence")
118 * @return key string. Must be g_free()'d after use.
120 static gchar
*sipe_subscription_key(const gchar
*event
,
123 if (!g_ascii_strcasecmp(event
, "presence"))
124 /* Subscription is identified by <presence><uri> key */
125 return(sipe_utils_presence_key(uri
));
127 /* Subscription is identified by <event> key */
128 return(g_strdup_printf("<%s>", event
));
131 void sipe_subscription_terminate(struct sipe_core_private
*sipe_private
,
135 gchar
*key
= sipe_subscription_key(event
, who
);
136 sipe_subscription_remove(sipe_private
, key
);
140 static gboolean
process_subscribe_response(struct sipe_core_private
*sipe_private
,
142 struct transaction
*trans
)
144 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
145 const gchar
*event
= sipmsg_find_header(msg
, "Event");
147 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
149 struct sipmsg
*request_msg
= trans
->msg
;
150 event
= sipmsg_find_header(request_msg
, "Event");
154 gchar
*key
= sipe_subscription_key(event
, with
);
156 /* 200 OK; 481 Call Leg Does Not Exist */
157 if (msg
->response
== 200 || msg
->response
== 481)
158 sipe_subscription_remove(sipe_private
, key
);
160 /* create/store subscription dialog if not yet */
161 if (msg
->response
== 200) {
162 struct sip_subscription
*subscription
= g_new0(struct sip_subscription
, 1);
163 g_hash_table_insert(sipe_private
->subscriptions
,
167 subscription
->dialog
.callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
168 subscription
->dialog
.cseq
= sipmsg_parse_cseq(msg
);
169 subscription
->dialog
.with
= g_strdup(with
);
170 subscription
->event
= g_strdup(event
);
171 sipe_dialog_parse(&subscription
->dialog
, msg
, TRUE
);
173 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s",
181 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
182 process_incoming_notify(sipe_private
, msg
, FALSE
, FALSE
);
188 * common subscription code
190 void sipe_subscribe(struct sipe_core_private
*sipe_private
,
194 const gchar
*addheaders
,
196 struct sip_dialog
*dialog
)
198 gchar
*contact
= get_contact(sipe_private
);
199 gchar
*hdr
= g_strdup_printf(
202 "Supported: com.microsoft.autoextend\r\n"
203 "Supported: ms-benotify\r\n"
204 "Proxy-Require: ms-benotify\r\n"
205 "Supported: ms-piggyback-first-notify\r\n"
210 addheaders
? addheaders
: "",
215 sip_transport_subscribe(sipe_private
,
220 process_subscribe_response
);
226 * common subscription code for self-subscriptions
228 static void sipe_subscribe_self(struct sipe_core_private
*sipe_private
,
231 const gchar
*addheaders
,
233 struct sip_dialog
*dialog
)
235 gchar
*self
= sip_uri_self(sipe_private
);
237 sipe_subscribe(sipe_private
,
248 static struct sip_dialog
*sipe_subscribe_dialog(struct sipe_core_private
*sipe_private
,
251 struct sip_dialog
*dialog
= g_hash_table_lookup(sipe_private
->subscriptions
,
253 SIPE_DEBUG_INFO("sipe_subscribe_dialog: dialog for '%s' is %s", key
, dialog
? "not NULL" : "NULL");
257 static void sipe_subscribe_presence_buddy(struct sipe_core_private
*sipe_private
,
259 const gchar
*request
,
262 gchar
*key
= sipe_utils_presence_key(uri
);
264 sip_transport_subscribe(sipe_private
,
268 sipe_subscribe_dialog(sipe_private
, key
),
269 process_subscribe_response
);
274 void sipe_subscribe_presence_wpending(struct sipe_core_private
*sipe_private
,
275 SIPE_UNUSED_PARAMETER
void *unused
)
277 gchar
*key
= sipe_subscription_key("presence.wpending", NULL
);
279 sipe_subscribe_self(sipe_private
,
281 "text/xml+msrtc.wpending",
284 sipe_subscribe_dialog(sipe_private
, key
));
290 * Subscribe roaming ACL
292 void sipe_subscribe_roaming_acl(struct sipe_core_private
*sipe_private
)
294 sipe_subscribe_self(sipe_private
,
295 "vnd-microsoft-roaming-ACL",
296 "application/vnd-microsoft-roaming-acls+xml",
303 * Subscribe roaming contacts
305 void sipe_subscribe_roaming_contacts(struct sipe_core_private
*sipe_private
)
307 sipe_subscribe_self(sipe_private
,
308 "vnd-microsoft-roaming-contacts",
309 "application/vnd-microsoft-roaming-contacts+xml",
318 void sipe_subscribe_roaming_provisioning(struct sipe_core_private
*sipe_private
)
320 sipe_subscribe_self(sipe_private
,
321 "vnd-microsoft-provisioning",
322 "application/vnd-microsoft-roaming-provisioning+xml",
329 * Subscription for provisioning information to help with initial
330 * configuration. This subscription is a one-time query (denoted by the
331 * Expires header, which asks for 0 seconds for the subscription lifetime).
332 * This subscription asks for server configuration, meeting policies, and
333 * policy settings that Communicator must enforce.
335 void sipe_subscribe_roaming_provisioning_v2(struct sipe_core_private
*sipe_private
)
337 sipe_subscribe_self(sipe_private
,
338 "vnd-microsoft-provisioning-v2",
339 "application/vnd-microsoft-roaming-provisioning-v2+xml",
341 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n",
342 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
343 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
344 "<provisioningGroup name=\"ucPolicy\"/>"
345 "</provisioningGroupList>",
350 * To request for presence information about the user, access level settings
351 * that have already been configured by the user to control who has access to
352 * what information, and the list of contacts who currently have outstanding
355 * We wait for (BE)NOTIFY messages with some info change (categories,
356 * containers, subscribers)
358 void sipe_subscribe_roaming_self(struct sipe_core_private
*sipe_private
)
360 sipe_subscribe_self(sipe_private
,
361 "vnd-microsoft-roaming-self",
362 "application/vnd-microsoft-roaming-self+xml",
363 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n",
364 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
365 "<roaming type=\"categories\"/>"
366 "<roaming type=\"containers\"/>"
367 "<roaming type=\"subscribers\"/></roamingList>",
372 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
373 * The user sends a single SUBSCRIBE request to the subscribed contact.
374 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
377 void sipe_subscribe_presence_single(struct sipe_core_private
*sipe_private
,
380 gchar
*to
= sip_uri((gchar
*)buddy_name
);
381 gchar
*tmp
= get_contact(sipe_private
);
383 gchar
*content
= NULL
;
384 gchar
*autoextend
= "";
385 gchar
*content_type
= "";
386 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sipe_private
->buddies
, to
);
387 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
389 if (sbuddy
) sbuddy
->just_added
= FALSE
;
391 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
392 content_type
= "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
394 autoextend
= "Supported: com.microsoft.autoextend\r\n";
397 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"
398 "Supported: ms-piggyback-first-notify\r\n"
399 "%s%sSupported: ms-benotify\r\n"
400 "Proxy-Require: ms-benotify\r\n"
401 "Event: presence\r\n"
407 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
408 content
= g_strdup_printf("<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
409 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
410 "<resource uri=\"%s\"%s\n"
412 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
413 "<category name=\"calendarData\"/>\n"
414 "<category name=\"contactCard\"/>\n"
415 "<category name=\"note\"/>\n"
416 "<category name=\"state\"/>\n"
420 sipe_private
->username
,
427 sipe_subscribe_presence_buddy(sipe_private
, to
, request
, content
);
435 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
436 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
437 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
438 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
439 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
441 static void sipe_subscribe_presence_batched_to(struct sipe_core_private
*sipe_private
,
442 gchar
*resources_uri
,
445 gchar
*contact
= get_contact(sipe_private
);
450 gchar
*autoextend
= "";
453 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
454 require
= ", categoryList";
455 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
456 content_type
= "application/msrtc-adrl-categorylist+xml";
457 content
= g_strdup_printf("<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
458 "<action name=\"subscribe\" id=\"63792024\">\n"
459 "<adhocList>\n%s</adhocList>\n"
460 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
461 "<category name=\"calendarData\"/>\n"
462 "<category name=\"contactCard\"/>\n"
463 "<category name=\"note\"/>\n"
464 "<category name=\"state\"/>\n"
468 sipe_private
->username
,
471 autoextend
= "Supported: com.microsoft.autoextend\r\n";
472 content_type
= "application/adrl+xml";
473 content
= g_strdup_printf("<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
474 "<create xmlns=\"\">\n%s</create>\n"
476 sipe_private
->username
,
477 sipe_private
->username
,
480 g_free(resources_uri
);
482 request
= g_strdup_printf("Require: adhoclist%s\r\n"
483 "Supported: eventlist\r\n"
484 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
485 "Supported: ms-piggyback-first-notify\r\n"
486 "%sSupported: ms-benotify\r\n"
487 "Proxy-Require: ms-benotify\r\n"
488 "Event: presence\r\n"
489 "Content-Type: %s\r\n"
498 sipe_subscribe_presence_buddy(sipe_private
, to
, request
, content
);
505 struct presence_batched_routed
{
510 static void sipe_subscribe_presence_batched_routed_free(gpointer payload
)
512 struct presence_batched_routed
*data
= payload
;
513 GSList
*buddies
= data
->buddies
;
515 g_free(buddies
->data
);
516 buddies
= buddies
->next
;
518 g_slist_free(data
->buddies
);
523 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private
*sipe_private
,
526 struct presence_batched_routed
*data
= payload
;
527 GSList
*buddies
= data
->buddies
;
528 gchar
*resources_uri
= g_strdup("");
530 gchar
*tmp
= resources_uri
;
531 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
533 buddies
= buddies
->next
;
535 sipe_subscribe_presence_batched_to(sipe_private
, resources_uri
,
536 g_strdup(data
->host
));
539 void sipe_subscribe_presence_batched_schedule(struct sipe_core_private
*sipe_private
,
540 const gchar
*action_name
,
545 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
546 payload
->host
= g_strdup(who
);
547 payload
->buddies
= buddies
;
548 sipe_schedule_seconds(sipe_private
,
552 sipe_subscribe_presence_batched_routed
,
553 sipe_subscribe_presence_batched_routed_free
);
554 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who
, timeout
);
557 static void sipe_subscribe_resource_uri_with_context(const gchar
*name
,
559 gchar
**resources_uri
)
561 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
562 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
563 gchar
*tmp
= *resources_uri
;
565 if (sbuddy
) sbuddy
->just_added
= FALSE
; /* should be enought to include context one time */
567 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp
, name
, context
);
571 static void sipe_subscribe_resource_uri(const char *name
,
572 SIPE_UNUSED_PARAMETER gpointer value
,
573 gchar
**resources_uri
)
575 gchar
*tmp
= *resources_uri
;
576 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
580 void sipe_subscribe_presence_batched(struct sipe_core_private
*sipe_private
)
582 gchar
*to
= sip_uri_self(sipe_private
);
583 gchar
*resources_uri
= g_strdup("");
584 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
585 g_hash_table_foreach(sipe_private
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
587 g_hash_table_foreach(sipe_private
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
590 sipe_subscribe_presence_batched_to(sipe_private
, resources_uri
, to
);
593 void sipe_subscribe_poolfqdn_resource_uri(const char *host
,
595 struct sipe_core_private
*sipe_private
)
597 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
598 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host
);
599 payload
->host
= g_strdup(host
);
600 payload
->buddies
= server
;
601 sipe_subscribe_presence_batched_routed(sipe_private
,
603 sipe_subscribe_presence_batched_routed_free(payload
);