Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / core / sipe-subscriptions.c
blob2709bea6436ace3016fe4c997e5f5f559164ab3b
1 /**
2 * @file sipe-subscriptions.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2019 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
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <stdlib.h>
28 #include <string.h>
30 #include <glib.h>
32 #include "sipe-common.h"
33 #include "sipmsg.h"
34 #include "sip-transport.h"
35 #include "sipe-backend.h"
36 #include "sipe-buddy.h"
37 #include "sipe-core.h"
38 #include "sipe-core-private.h"
39 #include "sipe-dialog.h"
40 #include "sipe-mime.h"
41 #include "sipe-nls.h"
42 #include "sipe-notify.h"
43 #include "sipe-schedule.h"
44 #include "sipe-subscriptions.h"
45 #include "sipe-ucs.h"
46 #include "sipe-utils.h"
47 #include "sipe-xml.h"
49 /* RFC3265 subscription */
50 struct sip_subscription {
51 struct sip_dialog dialog;
52 gchar *event;
53 GSList *buddies; /* batched subscriptions */
56 static void sipe_subscription_free(struct sip_subscription *subscription)
59 if (!subscription) return;
61 g_free(subscription->event);
62 sipe_utils_slist_free_full(subscription->buddies, g_free);
64 /* NOTE: use cast to prevent BAD_FREE warning from Coverity */
65 sipe_dialog_free((struct sip_dialog *) subscription);
68 void sipe_subscriptions_init(struct sipe_core_private *sipe_private)
70 sipe_private->subscriptions = g_hash_table_new_full(g_str_hash,
71 g_str_equal,
72 g_free,
73 (GDestroyNotify)sipe_subscription_free);
76 static void sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key,
77 gpointer value, gpointer user_data)
79 struct sip_subscription *subscription = value;
80 struct sip_dialog *dialog = &subscription->dialog;
81 struct sipe_core_private *sipe_private = user_data;
82 gchar *contact = get_contact(sipe_private);
83 gchar *hdr = g_strdup_printf(
84 "Event: %s\r\n"
85 "Expires: 0\r\n"
86 "Contact: %s\r\n", subscription->event, contact);
87 g_free(contact);
89 /* Rate limit to max. 25 requests per seconds */
90 g_usleep(1000000 / 25);
92 sip_transport_subscribe(sipe_private,
93 dialog->with,
94 hdr,
95 NULL,
96 dialog,
97 NULL);
99 g_free(hdr);
102 void sipe_subscriptions_unsubscribe(struct sipe_core_private *sipe_private)
104 /* unsubscribe all */
105 g_hash_table_foreach(sipe_private->subscriptions,
106 sipe_unsubscribe_cb,
107 sipe_private);
111 void sipe_subscriptions_destroy(struct sipe_core_private *sipe_private)
113 g_hash_table_destroy(sipe_private->subscriptions);
116 static void sipe_subscription_remove(struct sipe_core_private *sipe_private,
117 const gchar *key)
119 if (g_hash_table_lookup(sipe_private->subscriptions, key)) {
120 g_hash_table_remove(sipe_private->subscriptions, key);
121 SIPE_DEBUG_INFO("sipe_subscription_remove: %s", key);
126 * Generate subscription key
128 * @param event event name (must not by @c NULL)
129 * @param uri presence URI (ignored if @c event != "presence")
131 * @return key string. Must be g_free()'d after use.
133 static gchar *sipe_subscription_key(const gchar *event,
134 const gchar *uri)
136 if (!g_ascii_strcasecmp(event, "presence"))
137 /* Subscription is identified by <presence><uri> key */
138 return(sipe_utils_presence_key(uri));
139 else
140 /* Subscription is identified by <event> key */
141 return(g_strdup_printf("<%s>", event));
144 static struct sip_dialog *sipe_subscribe_dialog(struct sipe_core_private *sipe_private,
145 const gchar *key)
147 struct sip_dialog *dialog = g_hash_table_lookup(sipe_private->subscriptions,
148 key);
149 SIPE_DEBUG_INFO("sipe_subscribe_dialog: dialog for '%s' is %s", key, dialog ? "not NULL" : "NULL");
150 return(dialog);
153 static void sipe_subscription_expiration(struct sipe_core_private *sipe_private,
154 struct sipmsg *msg,
155 const gchar *event);
156 static gboolean process_subscribe_response(struct sipe_core_private *sipe_private,
157 struct sipmsg *msg,
158 struct transaction *trans)
160 const gchar *event = sipmsg_find_event_header(msg);
162 /* No Event header - error or 2005 Public IM Connectivity (PIC) */
163 if (!event) {
164 struct sipmsg *request_msg = trans->msg;
165 event = sipmsg_find_event_header(request_msg);
168 if (event) {
169 gchar *with = sipmsg_parse_to_address(msg);
170 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
171 gboolean terminated = subscription_state && strstr(subscription_state, "terminated");
172 gchar *key = sipe_subscription_key(event, with);
175 * @TODO: does the server send this only for one-off
176 * subscriptions, i.e. the ones which anyway
177 * have "Expires: 0"?
179 if (terminated)
180 SIPE_DEBUG_INFO("process_subscribe_response: subscription '%s' to '%s' was terminated",
181 event, with);
183 /* 400 Bad request */
184 if (msg->response == 400) {
186 SIPE_DEBUG_INFO("process_subscribe_response: subscription '%s' to '%s' failed",
187 event, with);
189 sipe_subscription_remove(sipe_private, key);
191 if (sipe_strcase_equal(event, "presence")) {
192 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
193 _("Presence subscription failed!"),
194 _("One or more buddies will therefore permanently show as offline.\n\nPlease check that there are no corrupted SIP URIs in your contacts list."));
197 /* 481 Call Leg Does Not Exist */
198 } else if ((msg->response == 481) || terminated) {
199 sipe_subscription_remove(sipe_private, key);
201 /* 488 Not acceptable here */
202 } else if (msg->response == 488) {
204 SIPE_DEBUG_INFO("process_subscribe_response: subscription '%s' to '%s' was rejected",
205 event, with);
207 sipe_subscription_remove(sipe_private, key);
209 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) &&
210 sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts")) {
211 SIPE_DEBUG_INFO_NOFORMAT("no contact list available - assuming Lync 2013+ and Unified Contact Store (UCS)");
212 SIPE_CORE_PRIVATE_FLAG_SET(LYNC2013);
213 sipe_ucs_init(sipe_private, TRUE);
216 /* create/store subscription dialog if not yet */
217 } else if (msg->response == 200) {
218 struct sip_dialog *dialog = sipe_subscribe_dialog(sipe_private, key);
220 if (!dialog) {
221 struct sip_subscription *subscription = g_new0(struct sip_subscription, 1);
223 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for event '%s'",
224 key);
226 g_hash_table_insert(sipe_private->subscriptions,
227 key,
228 subscription);
229 key = NULL; /* table takes ownership of key */
231 subscription->dialog.callid = g_strdup(sipmsg_find_call_id_header(msg));
232 subscription->dialog.cseq = sipmsg_parse_cseq(msg);
233 subscription->dialog.with = g_strdup(with);
234 subscription->event = g_strdup(event);
236 dialog = &subscription->dialog;
239 sipe_dialog_parse(dialog, msg, TRUE);
241 sipe_subscription_expiration(sipe_private, msg, event);
243 g_free(key);
244 g_free(with);
247 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
248 process_incoming_notify(sipe_private, msg);
250 return(TRUE);
254 * common subscription code
256 static void sipe_subscribe(struct sipe_core_private *sipe_private,
257 const gchar *uri,
258 const gchar *event,
259 const gchar *accept,
260 const gchar *addheaders,
261 const gchar *body,
262 struct sip_dialog *dialog)
264 gchar *contact = get_contact(sipe_private);
265 gchar *hdr = g_strdup_printf(
266 "Event: %s\r\n"
267 "Accept: %s\r\n"
268 "Supported: com.microsoft.autoextend\r\n"
269 "Supported: ms-benotify\r\n"
270 "Proxy-Require: ms-benotify\r\n"
271 "Supported: ms-piggyback-first-notify\r\n"
272 "%s"
273 "Contact: %s\r\n",
274 event,
275 accept,
276 addheaders ? addheaders : "",
277 contact);
278 g_free(contact);
280 sip_transport_subscribe(sipe_private,
281 uri,
282 hdr,
283 body,
284 dialog,
285 process_subscribe_response);
286 g_free(hdr);
290 * common subscription code for self-subscriptions
292 static void sipe_subscribe_self(struct sipe_core_private *sipe_private,
293 const gchar *event,
294 const gchar *accept,
295 const gchar *addheaders,
296 const gchar *body)
298 gchar *self = sip_uri_self(sipe_private);
299 gchar *key = sipe_subscription_key(event, self);
300 struct sip_dialog *dialog = sipe_subscribe_dialog(sipe_private, key);
302 sipe_subscribe(sipe_private,
303 self,
304 event,
305 accept,
306 addheaders,
307 body,
308 dialog);
309 g_free(key);
310 g_free(self);
313 static void sipe_subscribe_presence_wpending(struct sipe_core_private *sipe_private,
314 SIPE_UNUSED_PARAMETER void *unused)
316 sipe_subscribe_self(sipe_private,
317 "presence.wpending",
318 "text/xml+msrtc.wpending",
319 NULL,
320 NULL);
324 * Subscribe roaming ACL
326 static void sipe_subscribe_roaming_acl(struct sipe_core_private *sipe_private,
327 SIPE_UNUSED_PARAMETER void *unused)
329 sipe_subscribe_self(sipe_private,
330 "vnd-microsoft-roaming-ACL",
331 "application/vnd-microsoft-roaming-acls+xml",
332 NULL,
333 NULL);
337 * Subscribe roaming contacts
339 static void sipe_subscribe_roaming_contacts(struct sipe_core_private *sipe_private,
340 SIPE_UNUSED_PARAMETER void *unused)
342 const gchar *addheaders = NULL;
344 /* indicate that we support Unified Contact Store (UCS) */
345 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
346 addheaders = "Supported: ms-ucs\r\n";
347 sipe_subscribe_self(sipe_private,
348 "vnd-microsoft-roaming-contacts",
349 "application/vnd-microsoft-roaming-contacts+xml",
350 addheaders,
351 NULL);
355 * OCS 2005 version
357 static void sipe_subscribe_roaming_provisioning(struct sipe_core_private *sipe_private,
358 SIPE_UNUSED_PARAMETER void *unused)
360 sipe_subscribe_self(sipe_private,
361 "vnd-microsoft-provisioning",
362 "application/vnd-microsoft-roaming-provisioning+xml",
363 "Expires: 0\r\n",
364 NULL);
368 * Subscription for provisioning information to help with initial
369 * configuration. This subscription is a one-time query (denoted by the
370 * Expires header, which asks for 0 seconds for the subscription lifetime).
371 * This subscription asks for server configuration, meeting policies, and
372 * policy settings that Communicator must enforce.
374 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_core_private *sipe_private,
375 SIPE_UNUSED_PARAMETER void *unused)
377 sipe_subscribe_self(sipe_private,
378 "vnd-microsoft-provisioning-v2",
379 "application/vnd-microsoft-roaming-provisioning-v2+xml",
380 "Expires: 0\r\n"
381 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n",
382 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
383 " <provisioningGroup name=\"ServerConfiguration\"/>"
384 " <provisioningGroup name=\"meetingPolicy\"/>"
385 " <provisioningGroup name=\"persistentChatConfiguration\"/>"
386 " <provisioningGroup name=\"ucPolicy\"/>"
387 "</provisioningGroupList>");
391 * To request for presence information about the user, access level settings
392 * that have already been configured by the user to control who has access to
393 * what information, and the list of contacts who currently have outstanding
394 * subscriptions.
396 * We wait for (BE)NOTIFY messages with some info change (categories,
397 * containers, subscribers)
399 static void sipe_subscribe_roaming_self(struct sipe_core_private *sipe_private,
400 SIPE_UNUSED_PARAMETER void *unused)
402 sipe_subscribe_self(sipe_private,
403 "vnd-microsoft-roaming-self",
404 "application/vnd-microsoft-roaming-self+xml",
405 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n",
406 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
407 "<roaming type=\"categories\"/>"
408 "<roaming type=\"containers\"/>"
409 "<roaming type=\"subscribers\"/></roamingList>");
412 static void sipe_presence_timeout_mime_cb(gpointer user_data,
413 SIPE_UNUSED_PARAMETER const GSList *fields,
414 const gchar *body,
415 gsize length)
417 GSList **buddies = user_data;
418 sipe_xml *xml = sipe_xml_parse(body, length);
420 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
421 const gchar *uri = sipe_xml_attribute(xml, "uri");
422 const sipe_xml *xn_category;
425 * automaton: presence is never expected to change
427 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
429 for (xn_category = sipe_xml_child(xml, "category");
430 xn_category;
431 xn_category = sipe_xml_twin(xn_category)) {
432 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
433 "contactCard")) {
434 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
435 if (node) {
436 char *boolean = sipe_xml_data(node);
437 if (sipe_strequal(boolean, "true")) {
438 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
439 uri);
440 uri = NULL;
442 g_free(boolean);
444 break;
448 if (uri) {
449 *buddies = g_slist_append(*buddies, sip_uri(uri));
453 sipe_xml_free(xml);
456 static void sipe_subscribe_presence_batched_schedule(struct sipe_core_private *sipe_private,
457 const gchar *action_name,
458 const gchar *who,
459 GSList *buddies,
460 int timeout);
461 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
462 struct sipmsg *msg,
463 const gchar *who,
464 int timeout)
466 const char *ctype = sipmsg_find_content_type_header(msg);
467 gchar *action_name = sipe_utils_presence_key(who);
469 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
471 if (ctype &&
472 strstr(ctype, "multipart") &&
473 (strstr(ctype, "application/rlmi+xml") ||
474 strstr(ctype, "application/msrtc-event-categories+xml"))) {
475 GSList *buddies = NULL;
477 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
479 if (buddies)
480 sipe_subscribe_presence_batched_schedule(sipe_private,
481 action_name,
482 who,
483 buddies,
484 timeout);
486 } else {
487 sipe_schedule_seconds(sipe_private,
488 action_name,
489 g_strdup(who),
490 timeout,
491 sipe_subscribe_presence_single_cb,
492 g_free);
493 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d seconds", who, timeout);
495 g_free(action_name);
499 * @param expires not respected if set to negative value (E.g. -1)
501 void sipe_subscribe_conference(struct sipe_core_private *sipe_private,
502 const gchar *id,
503 gboolean expires)
505 sipe_subscribe(sipe_private,
507 "conference",
508 "application/conference-info+xml",
509 expires ? "Expires: 0\r\n" : NULL,
510 NULL,
511 NULL);
515 * code for presence subscription
517 static void sipe_subscribe_presence_buddy(struct sipe_core_private *sipe_private,
518 const gchar *uri,
519 const gchar *request,
520 const gchar *body)
522 gchar *key = sipe_utils_presence_key(uri);
524 sip_transport_subscribe(sipe_private,
525 uri,
526 request,
527 body,
528 sipe_subscribe_dialog(sipe_private, key),
529 process_subscribe_response);
531 g_free(key);
535 * if to == NULL: initial single subscription
536 * OCS2005: send to URI
537 * OCS2007: send to self URI
539 * if to != NULL:
540 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
541 * The user sends a single SUBSCRIBE request to the subscribed contact.
542 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
545 void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
546 const gchar *uri,
547 const gchar *to)
549 gchar *self = NULL;
550 gchar *contact = get_contact(sipe_private);
551 gchar *request;
552 gchar *content = NULL;
553 const gchar *additional = "";
554 const gchar *content_type = "";
555 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
556 uri);
558 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
559 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
560 content = g_strdup_printf("<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
561 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
562 "<resource uri=\"%s\"%s\n"
563 "</adhocList>\n"
564 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
565 "<category name=\"calendarData\"/>\n"
566 "<category name=\"contactCard\"/>\n"
567 "<category name=\"note\"/>\n"
568 "<category name=\"state\"/>\n"
569 "</categoryList>\n"
570 "</action>\n"
571 "</batchSub>",
572 sipe_private->username,
573 uri,
574 sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>");
575 if (!to) {
576 additional = "Require: adhoclist, categoryList\r\n" \
577 "Supported: eventlist\r\n";
578 to = self = sip_uri_self(sipe_private);
581 } else {
582 additional = "Supported: com.microsoft.autoextend\r\n";
583 if (!to)
584 to = uri;
587 if (sbuddy)
588 sbuddy->just_added = FALSE;
590 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"
591 "Supported: ms-piggyback-first-notify\r\n"
592 "%s%sSupported: ms-benotify\r\n"
593 "Proxy-Require: ms-benotify\r\n"
594 "Event: presence\r\n"
595 "Contact: %s\r\n",
596 additional,
597 content_type,
598 contact);
599 g_free(contact);
601 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
603 g_free(content);
604 g_free(self);
605 g_free(request);
608 void sipe_subscribe_presence_single_cb(struct sipe_core_private *sipe_private,
609 gpointer uri)
611 sipe_subscribe_presence_single(sipe_private, uri, NULL);
616 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
617 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
618 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
619 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
620 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
622 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
623 gchar *resources_uri,
624 const gchar *to)
626 gchar *contact = get_contact(sipe_private);
627 gchar *request;
628 gchar *content;
629 const gchar *require = "";
630 const gchar *accept = "";
631 const gchar *autoextend = "";
632 const gchar *content_type;
634 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
635 require = ", categoryList";
636 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
637 content_type = "application/msrtc-adrl-categorylist+xml";
638 content = g_strdup_printf("<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
639 "<action name=\"subscribe\" id=\"63792024\">\n"
640 "<adhocList>\n%s</adhocList>\n"
641 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
642 "<category name=\"calendarData\"/>\n"
643 "<category name=\"contactCard\"/>\n"
644 "<category name=\"note\"/>\n"
645 "<category name=\"state\"/>\n"
646 "</categoryList>\n"
647 "</action>\n"
648 "</batchSub>",
649 sipe_private->username,
650 resources_uri);
651 } else {
652 autoextend = "Supported: com.microsoft.autoextend\r\n";
653 content_type = "application/adrl+xml";
654 content = g_strdup_printf("<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
655 "<create xmlns=\"\">\n%s</create>\n"
656 "</adhoclist>\n",
657 sipe_private->username,
658 sipe_private->username,
659 resources_uri);
661 g_free(resources_uri);
663 request = g_strdup_printf("Require: adhoclist%s\r\n"
664 "Supported: eventlist\r\n"
665 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
666 "Supported: ms-piggyback-first-notify\r\n"
667 "%sSupported: ms-benotify\r\n"
668 "Proxy-Require: ms-benotify\r\n"
669 "Event: presence\r\n"
670 "Content-Type: %s\r\n"
671 "Contact: %s\r\n",
672 require,
673 accept,
674 autoextend,
675 content_type,
676 contact);
677 g_free(contact);
679 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
681 g_free(content);
682 g_free(request);
685 struct presence_batched_routed {
686 gchar *host;
687 const GSList *buddies; /* points to subscription->buddies */
690 static void sipe_subscribe_presence_batched_routed_free(gpointer payload)
692 struct presence_batched_routed *data = payload;
693 g_free(data->host);
694 g_free(payload);
697 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
698 gpointer payload)
700 struct presence_batched_routed *data = payload;
701 const GSList *buddies = data->buddies;
702 gchar *resources_uri = g_strdup("");
703 while (buddies) {
704 gchar *tmp = resources_uri;
705 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
706 g_free(tmp);
707 buddies = buddies->next;
709 sipe_subscribe_presence_batched_to(sipe_private,
710 resources_uri,
711 data->host);
714 static void sipe_subscribe_presence_batched_schedule(struct sipe_core_private *sipe_private,
715 const gchar *action_name,
716 const gchar *who,
717 GSList *buddies,
718 int timeout)
720 struct sip_subscription *subscription = g_hash_table_lookup(sipe_private->subscriptions,
721 action_name);
722 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
724 if (subscription->buddies) {
725 /* merge old and new list */
726 GSList *entry = buddies;
727 while (entry) {
728 subscription->buddies = sipe_utils_slist_insert_unique_sorted(subscription->buddies,
729 g_strdup(entry->data),
730 (GCompareFunc) g_ascii_strcasecmp,
731 g_free);
732 entry = entry->next;
734 sipe_utils_slist_free_full(buddies, g_free);
735 } else {
736 /* no list yet, simply take ownership of whole list */
737 subscription->buddies = buddies;
740 payload->host = g_strdup(who);
741 payload->buddies = subscription->buddies;
742 sipe_schedule_seconds(sipe_private,
743 action_name,
744 payload,
745 timeout,
746 sipe_subscribe_presence_batched_routed,
747 sipe_subscribe_presence_batched_routed_free);
748 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
751 static void sipe_subscribe_resource_uri_with_context(const gchar *name,
752 gpointer value,
753 gchar **resources_uri)
755 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
756 const gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
757 gchar *tmp = *resources_uri;
759 /* should be enough to include context one time */
760 if (sbuddy)
761 sbuddy->just_added = FALSE;
763 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
764 g_free(tmp);
767 static void sipe_subscribe_resource_uri(const char *name,
768 SIPE_UNUSED_PARAMETER gpointer value,
769 gchar **resources_uri)
771 gchar *tmp = *resources_uri;
772 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
773 g_free(tmp);
777 * A callback for g_hash_table_foreach
779 static void schedule_buddy_resubscription_cb(char *buddy_name,
780 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
781 struct sipe_core_private *sipe_private)
783 guint time_range = (sipe_buddy_count(sipe_private) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
786 * g_hash_table_size() can never return 0, otherwise this function
787 * wouldn't be called :-) But to keep Coverity happy...
789 if (time_range) {
790 gchar *action_name = sipe_utils_presence_key(buddy_name);
791 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
793 sipe_schedule_mseconds(sipe_private,
794 action_name,
795 g_strdup(buddy_name),
796 timeout,
797 sipe_subscribe_presence_single_cb,
798 g_free);
799 g_free(action_name);
803 void sipe_subscribe_presence_initial(struct sipe_core_private *sipe_private)
806 * Subscribe to buddies, but only do it once.
807 * We'll resubsribe to them based on the Expire field values.
809 if (!SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
811 /* Only try to subscribe if we have any buddies */
812 if (sipe_buddy_count(sipe_private) > 0) {
813 if (SIPE_CORE_PRIVATE_FLAG_IS(BATCHED_SUPPORT)) {
814 gchar *to = sip_uri_self(sipe_private);
815 gchar *resources_uri = g_strdup("");
816 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
817 sipe_buddy_foreach(sipe_private,
818 (GHFunc) sipe_subscribe_resource_uri_with_context,
819 &resources_uri);
820 } else {
821 sipe_buddy_foreach(sipe_private,
822 (GHFunc) sipe_subscribe_resource_uri,
823 &resources_uri);
825 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
826 g_free(to);
828 } else {
829 sipe_buddy_foreach(sipe_private,
830 (GHFunc) schedule_buddy_resubscription_cb,
831 sipe_private);
835 SIPE_CORE_PRIVATE_FLAG_SET(SUBSCRIBED_BUDDIES);
839 void sipe_subscribe_poolfqdn_resource_uri(const char *host,
840 GSList *server,
841 struct sipe_core_private *sipe_private)
843 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
844 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
845 payload->host = g_strdup(host);
846 payload->buddies = server;
847 sipe_subscribe_presence_batched_routed(sipe_private,
848 payload);
849 sipe_subscribe_presence_batched_routed_free(payload);
850 sipe_utils_slist_free_full(server, g_free);
855 * subscription expiration handling
857 struct event_subscription_data {
858 const gchar *event;
859 sipe_schedule_action callback;
860 guint flags;
863 #define EVENT_OCS2005 0x00000001
864 #define EVENT_OCS2007 0x00000002
866 static const struct event_subscription_data events_table[] =
869 * For 2007+ it does not make sence to subscribe to:
871 * presence.wpending
872 * vnd-microsoft-roaming-ACL
873 * vnd-microsoft-provisioning (not v2)
875 * These are only needed as backward compatibility for older clients
877 * For 2005- we publish our initial statuses only after we received
878 * our existing UserInfo data in response to self subscription.
879 * Only in this case we won't override existing UserInfo data
880 * set earlier or by other client on our behalf.
882 * For 2007+ we publish our initial statuses and calendar data only
883 * after we received our existing publications in roaming_self.
884 * Only in this case we know versions of current publications made
885 * on our behalf.
887 { "presence.wpending", sipe_subscribe_presence_wpending,
888 EVENT_OCS2005 },
889 { "vnd-microsoft-roaming-ACL", sipe_subscribe_roaming_acl,
890 EVENT_OCS2005 },
891 { "vnd-microsoft-roaming-contacts", sipe_subscribe_roaming_contacts,
892 EVENT_OCS2005 | EVENT_OCS2007 },
893 { "vnd-microsoft-provisioning", sipe_subscribe_roaming_provisioning,
894 EVENT_OCS2005 },
895 { "vnd-microsoft-provisioning-v2", sipe_subscribe_roaming_provisioning_v2,
896 EVENT_OCS2007 },
897 { "vnd-microsoft-roaming-self", sipe_subscribe_roaming_self,
898 EVENT_OCS2007 },
899 { NULL, NULL, 0 }
902 static void sipe_subscription_expiration(struct sipe_core_private *sipe_private,
903 struct sipmsg *msg,
904 const gchar *event)
906 const gchar *expires_header = sipmsg_find_expires_header(msg);
907 guint timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
909 if (timeout) {
910 /* 2 min ahead of expiration */
911 if (timeout > 240) timeout -= 120;
913 if (sipe_strcase_equal(event, "presence")) {
914 gchar *who = sipmsg_parse_to_address(msg);
916 if (SIPE_CORE_PRIVATE_FLAG_IS(BATCHED_SUPPORT)) {
917 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
918 } else {
919 gchar *action_name = sipe_utils_presence_key(who);
920 sipe_schedule_seconds(sipe_private,
921 action_name,
922 g_strdup(who),
923 timeout,
924 sipe_subscribe_presence_single_cb,
925 g_free);
926 g_free(action_name);
927 SIPE_DEBUG_INFO("Resubscription single contact '%s' in %d seconds", who, timeout);
929 g_free(who);
931 } else {
932 const struct event_subscription_data *esd;
934 for (esd = events_table; esd->event; esd++) {
935 if (sipe_strcase_equal(event, esd->event)) {
936 gchar *action_name = g_strdup_printf("<%s>", event);
937 sipe_schedule_seconds(sipe_private,
938 action_name,
939 NULL,
940 timeout,
941 esd->callback,
942 NULL);
943 g_free(action_name);
944 SIPE_DEBUG_INFO("Resubscription to event '%s' in %d seconds", event, timeout);
945 break;
953 * Initial event subscription
955 void sipe_subscription_self_events(struct sipe_core_private *sipe_private)
957 const guint mask = SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? EVENT_OCS2007 : EVENT_OCS2005;
958 const struct event_subscription_data *esd;
960 /* subscribe to those events which are selected for
961 * this version and are allowed by the server */
962 for (esd = events_table; esd->event; esd++)
963 if ((esd->flags & mask) &&
964 (g_slist_find_custom(sipe_private->allowed_events,
965 esd->event,
966 (GCompareFunc) g_ascii_strcasecmp) != NULL))
967 (*esd->callback)(sipe_private, NULL);
971 Local Variables:
972 mode: c
973 c-file-style: "bsd"
974 indent-tabs-mode: t
975 tab-width: 8
976 End: