Fix #238: False "not delivered" in conference
[siplcs.git] / src / core / sipe-conf.c
blobcd71956e52993fa365e816f35703422ba074b7f9
1 /**
2 * @file sipe-conf.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2014 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 /**
26 * Documentation references:
28 * Microsoft DevNet: [MS-CONFIM]: Centralized Conference Control Protocol:
29 * Instant Messaging Extensions
30 * <http://msdn.microsoft.com/en-us/library/cc431500%28v=office.12%29.aspx>
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
42 #include <glib.h>
44 #include "sipe-common.h"
45 #include "sipmsg.h"
46 #include "sip-transport.h"
47 #include "sipe-backend.h"
48 #include "sipe-buddy.h"
49 #include "sipe-chat.h"
50 #include "sipe-conf.h"
51 #include "sipe-core.h"
52 #include "sipe-core-private.h"
53 #include "sipe-dialog.h"
54 #include "sipe-im.h"
55 #include "sipe-nls.h"
56 #include "sipe-session.h"
57 #include "sipe-subscriptions.h"
58 #include "sipe-user.h"
59 #include "sipe-utils.h"
60 #include "sipe-xml.h"
62 #ifdef HAVE_VV
63 #define ENTITY_VIEW_AUDIO_VIDEO "<msci:entity-view entity=\"audio-video\"/>"
64 #else
65 #define ENTITY_VIEW_AUDIO_VIDEO
66 #endif
68 /**
69 * Add Conference request to FocusFactory.
70 * @param focus_factory_uri (%s) Ex.: sip:bob7@boston.local;gruu;opaque=app:conf:focusfactory
71 * @param from (%s) Ex.: sip:bob7@boston.local
72 * @param request_id (%d) Ex.: 1094520
73 * @param conference_id (%s) Ex.: 8386E6AEAAA41E4AA6627BA76D43B6D1
74 * @param expiry_time (%s) Ex.: 2009-07-13T17:57:09Z , Default duration: 7 hours
76 #define SIPE_SEND_CONF_ADD \
77 "<?xml version=\"1.0\"?>"\
78 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" "\
79 "xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
80 "C3PVersion=\"1\" "\
81 "to=\"%s\" "\
82 "from=\"%s\" "\
83 "requestId=\"%d\">"\
84 "<addConference>"\
85 "<ci:conference-info xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" entity=\"\" xmlns:msci=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\">"\
86 "<ci:conference-description>"\
87 "<ci:subject/>"\
88 "<msci:conference-id>%s</msci:conference-id>"\
89 "<msci:expiry-time>%s</msci:expiry-time>"\
90 "<msci:admission-policy>openAuthenticated</msci:admission-policy>"\
91 "</ci:conference-description>"\
92 "<msci:conference-view>"\
93 "<msci:entity-view entity=\"chat\"/>"\
94 ENTITY_VIEW_AUDIO_VIDEO \
95 "</msci:conference-view>"\
96 "</ci:conference-info>"\
97 "</addConference>"\
98 "</request>"
101 * AddUser request to Focus.
102 * Params:
103 * focus_URI, from, request_id, focus_URI, from, endpoint_GUID
105 #define SIPE_SEND_CONF_ADD_USER \
106 "<?xml version=\"1.0\"?>"\
107 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
108 "C3PVersion=\"1\" "\
109 "to=\"%s\" "\
110 "from=\"%s\" "\
111 "requestId=\"%d\">"\
112 "<addUser>"\
113 "<conferenceKeys confEntity=\"%s\"/>"\
114 "<ci:user xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" entity=\"%s\">"\
115 "<ci:roles>"\
116 "<ci:entry>attendee</ci:entry>"\
117 "</ci:roles>"\
118 "<ci:endpoint entity=\"{%s}\" xmlns:msci=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\"/>"\
119 "</ci:user>"\
120 "</addUser>"\
121 "</request>"
124 * ModifyUserRoles request to Focus. Makes user a leader.
125 * @param focus_uri (%s)
126 * @param from (%s)
127 * @param request_id (%d)
128 * @param focus_uri (%s)
129 * @param who (%s)
131 #define SIPE_SEND_CONF_MODIFY_USER_ROLES \
132 "<?xml version=\"1.0\"?>"\
133 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
134 "C3PVersion=\"1\" "\
135 "to=\"%s\" "\
136 "from=\"%s\" "\
137 "requestId=\"%d\">"\
138 "<modifyUserRoles>"\
139 "<userKeys confEntity=\"%s\" userEntity=\"%s\"/>"\
140 "<user-roles xmlns=\"urn:ietf:params:xml:ns:conference-info\">"\
141 "<entry>presenter</entry>"\
142 "</user-roles>"\
143 "</modifyUserRoles>"\
144 "</request>"
147 * ModifyConferenceLock request to Focus. Locks/unlocks conference.
148 * @param focus_uri (%s)
149 * @param from (%s)
150 * @param request_id (%d)
151 * @param focus_uri (%s)
152 * @param locked (%s) "true" or "false" values applicable
154 #define SIPE_SEND_CONF_MODIFY_CONF_LOCK \
155 "<?xml version=\"1.0\"?>"\
156 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
157 "C3PVersion=\"1\" "\
158 "to=\"%s\" "\
159 "from=\"%s\" "\
160 "requestId=\"%d\">"\
161 "<modifyConferenceLock>"\
162 "<conferenceKeys confEntity=\"%s\"/>"\
163 "<locked>%s</locked>"\
164 "</modifyConferenceLock>"\
165 "</request>"
168 * ModifyConferenceLock request to Focus. Locks/unlocks conference.
169 * @param focus_uri (%s)
170 * @param from (%s)
171 * @param request_id (%d)
172 * @param focus_uri (%s)
173 * @param who (%s)
175 #define SIPE_SEND_CONF_DELETE_USER \
176 "<?xml version=\"1.0\"?>"\
177 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
178 "C3PVersion=\"1\" "\
179 "to=\"%s\" "\
180 "from=\"%s\" "\
181 "requestId=\"%d\">"\
182 "<deleteUser>"\
183 "<userKeys confEntity=\"%s\" userEntity=\"%s\"/>"\
184 "</deleteUser>"\
185 "</request>"
188 * Invite counterparty to join conference.
189 * @param focus_uri (%s)
190 * @param subject (%s) of conference
192 #define SIPE_SEND_CONF_INVITE \
193 "<Conferencing version=\"2.0\">"\
194 "<focus-uri>%s</focus-uri>"\
195 "<subject>%s</subject>"\
196 "<im available=\"true\">"\
197 "<first-im/>"\
198 "</im>"\
199 "</Conferencing>"
202 * Generates random GUID.
203 * This method is borrowed from pidgin's msnutils.c
205 static char *
206 rand_guid()
208 return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X",
209 rand() % 0xAAFF + 0x1111,
210 rand() % 0xAAFF + 0x1111,
211 rand() % 0xAAFF + 0x1111,
212 rand() % 0xAAFF + 0x1111,
213 rand() % 0xAAFF + 0x1111,
214 rand() % 0xAAFF + 0x1111,
215 rand() % 0xAAFF + 0x1111,
216 rand() % 0xAAFF + 0x1111);
219 /** Invite us to the focus callback */
220 static gboolean
221 process_invite_conf_focus_response(struct sipe_core_private *sipe_private,
222 struct sipmsg *msg,
223 SIPE_UNUSED_PARAMETER struct transaction *trans)
225 struct sip_session *session = NULL;
226 char *focus_uri = parse_from(sipmsg_find_header(msg, "To"));
228 session = sipe_session_find_conference(sipe_private, focus_uri);
230 if (!session) {
231 SIPE_DEBUG_INFO("process_invite_conf_focus_response: unable to find conf session with focus=%s", focus_uri);
232 g_free(focus_uri);
233 return FALSE;
236 if (!session->focus_dialog) {
237 SIPE_DEBUG_INFO_NOFORMAT("process_invite_conf_focus_response: session's focus_dialog is NULL");
238 g_free(focus_uri);
239 return FALSE;
242 sipe_dialog_parse(session->focus_dialog, msg, TRUE);
244 if (msg->response >= 200) {
245 /* send ACK to focus */
246 session->focus_dialog->cseq = 0;
247 sip_transport_ack(sipe_private, session->focus_dialog);
248 session->focus_dialog->outgoing_invite = NULL;
249 session->focus_dialog->is_established = TRUE;
252 if (msg->response >= 400) {
253 gchar *reason = sipmsg_get_ms_diagnostics_reason(msg);
255 SIPE_DEBUG_INFO_NOFORMAT("process_invite_conf_focus_response: INVITE response is not 200. Failed to join focus.");
256 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
257 _("Failed to join the conference"),
258 reason ? reason : _("no reason given"));
259 g_free(reason);
261 sipe_session_remove(sipe_private, session);
262 g_free(focus_uri);
263 return FALSE;
264 } else if (msg->response == 200) {
265 sipe_xml *xn_response = sipe_xml_parse(msg->body, msg->bodylen);
266 const gchar *code = sipe_xml_attribute(xn_response, "code");
267 if (sipe_strequal(code, "success")) {
268 /* subscribe to focus */
269 sipe_subscribe_conference(sipe_private,
270 session->chat_session->id,
271 FALSE);
272 #ifdef HAVE_VV
273 if (session->is_call)
274 sipe_core_media_connect_conference(SIPE_CORE_PUBLIC,
275 session->chat_session);
276 #endif
278 sipe_xml_free(xn_response);
281 g_free(focus_uri);
282 return TRUE;
285 static gchar *
286 parse_ocs_focus_uri(const gchar *uri)
288 const gchar *confkey;
289 size_t uri_len;
291 if (!uri)
292 return NULL;
294 // URI can have this prefix if it was typed in by the user
295 if (g_str_has_prefix(uri, "meet:") || g_str_has_prefix(uri, "conf:")) {
296 uri += 5;
299 uri_len = strlen(uri);
301 if (!uri || !g_str_has_prefix(uri, "sip:") ||
302 uri_len == 4 || g_strstr_len(uri, -1, "%")) {
303 return NULL;
306 confkey = g_strstr_len(uri, -1, "?");
307 if (confkey) {
308 /* TODO: Investigate how conf-key field should be used,
309 * ignoring for now */
310 uri_len = confkey - uri;
313 return g_strndup(uri, uri_len);
316 static gchar *
317 parse_lync_join_url(const gchar *uri)
319 gchar *focus_uri = NULL;
320 gchar **parts;
321 int parts_count = 0;
323 if (!uri)
324 return NULL;
326 if (g_str_has_prefix(uri, "https://")) {
327 uri += 8;
328 } else if (g_str_has_prefix(uri, "http://")) {
329 uri += 7;
332 parts = g_strsplit(uri, "/", 0);
334 for (parts_count = 0; parts[parts_count]; ++parts_count);
335 if (parts_count >= 3) {
336 gchar *base_url = parts[0];
337 gchar *conference_id = parts[parts_count - 1];
338 gchar *organizer_alias = parts[parts_count - 2];
340 gchar **url_parts = g_strsplit(base_url, ".", 0);
341 int url_parts_count = 0;
342 for (url_parts_count = 0; url_parts[url_parts_count]; ++url_parts_count);
344 if (url_parts_count >= 3) {
345 focus_uri = g_strdup_printf("sip:%s@%s.%s;gruu;opaque=app:conf:focus:id:%s",
346 organizer_alias,
347 url_parts[url_parts_count - 2], url_parts[url_parts_count - 1],
348 conference_id);
351 g_strfreev(url_parts);
354 g_strfreev(parts);
356 return focus_uri;
359 struct sip_session *
360 sipe_core_conf_create(struct sipe_core_public *sipe_public,
361 const gchar *uri)
363 gchar *uri_ue = sipe_utils_uri_unescape(uri);
364 gchar *focus_uri;
365 struct sip_session *session = NULL;
367 focus_uri = parse_ocs_focus_uri(uri_ue);
368 if (!focus_uri) {
369 focus_uri = parse_lync_join_url(uri_ue);
372 if (focus_uri) {
373 session = sipe_conf_create(SIPE_CORE_PRIVATE, NULL, focus_uri);
374 g_free(focus_uri);
375 } else {
376 gchar *error = g_strdup_printf(_("\"%s\" is not a valid conference URI"),
377 uri ? uri : "");
378 sipe_backend_notify_error(sipe_public,
379 _("Failed to join the conference"),
380 error);
381 g_free(error);
384 g_free(uri_ue);
386 return session;
389 /** Create new session with Focus URI */
390 struct sip_session *
391 sipe_conf_create(struct sipe_core_private *sipe_private,
392 struct sipe_chat_session *chat_session,
393 const gchar *focus_uri)
395 gchar *hdr;
396 gchar *contact;
397 gchar *body;
398 gchar *self;
399 struct sip_session *session = sipe_session_add_chat(sipe_private,
400 chat_session,
401 FALSE,
402 focus_uri);
404 session->focus_dialog = g_new0(struct sip_dialog, 1);
405 session->focus_dialog->callid = gencallid();
406 session->focus_dialog->with = g_strdup(session->chat_session->id);
407 session->focus_dialog->endpoint_GUID = rand_guid();
408 session->focus_dialog->ourtag = gentag();
410 contact = get_contact(sipe_private);
411 hdr = g_strdup_printf(
412 "Supported: ms-sender\r\n"
413 "Contact: %s\r\n"
414 "Content-Type: application/cccp+xml\r\n",
415 contact);
416 g_free(contact);
418 /* @TODO put request_id to queue to further compare with incoming one */
419 /* focus_URI, from, request_id, focus_URI, from, endpoint_GUID */
420 self = sip_uri_self(sipe_private);
421 body = g_strdup_printf(
422 SIPE_SEND_CONF_ADD_USER,
423 session->focus_dialog->with,
424 self,
425 session->request_id++,
426 session->focus_dialog->with,
427 self,
428 session->focus_dialog->endpoint_GUID);
430 session->focus_dialog->outgoing_invite =
431 sip_transport_invite(sipe_private,
432 hdr,
433 body,
434 session->focus_dialog,
435 process_invite_conf_focus_response);
436 g_free(body);
437 g_free(hdr);
439 /* Rejoin existing session? */
440 if (chat_session) {
441 SIPE_DEBUG_INFO("sipe_conf_create: rejoin '%s' (%s)",
442 chat_session->title,
443 chat_session->id);
444 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
445 chat_session->backend,
446 self,
447 chat_session->title);
449 g_free(self);
451 return(session);
454 /** Modify User Role */
455 void
456 sipe_conf_modify_user_role(struct sipe_core_private *sipe_private,
457 struct sip_session *session,
458 const gchar* who)
460 gchar *hdr;
461 gchar *body;
462 gchar *self;
464 if (!session->focus_dialog || !session->focus_dialog->is_established) {
465 SIPE_DEBUG_INFO_NOFORMAT("sipe_conf_modify_user_role: no dialog with focus, exiting.");
466 return;
469 hdr = g_strdup(
470 "Content-Type: application/cccp+xml\r\n");
472 /* @TODO put request_id to queue to further compare with incoming one */
473 self = sip_uri_self(sipe_private);
474 body = g_strdup_printf(
475 SIPE_SEND_CONF_MODIFY_USER_ROLES,
476 session->focus_dialog->with,
477 self,
478 session->request_id++,
479 session->focus_dialog->with,
480 who);
481 g_free(self);
483 sip_transport_info(sipe_private,
484 hdr,
485 body,
486 session->focus_dialog,
487 NULL);
488 g_free(body);
489 g_free(hdr);
493 * Check conference lock status
495 sipe_chat_lock_status sipe_core_chat_lock_status(struct sipe_core_public *sipe_public,
496 struct sipe_chat_session *chat_session)
498 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
499 sipe_chat_lock_status status = SIPE_CHAT_LOCK_STATUS_NOT_ALLOWED;
501 if (chat_session &&
502 (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE)) {
503 struct sip_session *session = sipe_session_find_chat(sipe_private,
504 chat_session);
505 if (session) {
506 gchar *self = sip_uri_self(sipe_private);
508 /* Only operators are allowed to change the lock status */
509 if (sipe_backend_chat_is_operator(chat_session->backend, self)) {
510 status = session->locked ?
511 SIPE_CHAT_LOCK_STATUS_LOCKED :
512 SIPE_CHAT_LOCK_STATUS_UNLOCKED;
515 g_free(self);
519 return(status);
523 * Modify Conference Lock
524 * Sends request to Focus.
525 * INFO method is a carrier of application/cccp+xml
527 void
528 sipe_core_chat_modify_lock(struct sipe_core_public *sipe_public,
529 struct sipe_chat_session *chat_session,
530 const gboolean locked)
532 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
533 gchar *hdr;
534 gchar *body;
535 gchar *self;
537 struct sip_session *session = sipe_session_find_chat(sipe_private,
538 chat_session);
540 if (!session) return;
541 if (!session->focus_dialog || !session->focus_dialog->is_established) {
542 SIPE_DEBUG_INFO_NOFORMAT("sipe_conf_modify_conference_lock: no dialog with focus, exiting.");
543 return;
546 hdr = g_strdup(
547 "Content-Type: application/cccp+xml\r\n");
549 /* @TODO put request_id to queue to further compare with incoming one */
550 self = sip_uri_self(sipe_private);
551 body = g_strdup_printf(
552 SIPE_SEND_CONF_MODIFY_CONF_LOCK,
553 session->focus_dialog->with,
554 self,
555 session->request_id++,
556 session->focus_dialog->with,
557 locked ? "true" : "false");
558 g_free(self);
560 sip_transport_info(sipe_private,
561 hdr,
562 body,
563 session->focus_dialog,
564 NULL);
565 g_free(body);
566 g_free(hdr);
569 /** Modify Delete User */
570 void
571 sipe_conf_delete_user(struct sipe_core_private *sipe_private,
572 struct sip_session *session,
573 const gchar* who)
575 gchar *hdr;
576 gchar *body;
577 gchar *self;
579 if (!session->focus_dialog || !session->focus_dialog->is_established) {
580 SIPE_DEBUG_INFO_NOFORMAT("sipe_conf_delete_user: no dialog with focus, exiting.");
581 return;
584 hdr = g_strdup(
585 "Content-Type: application/cccp+xml\r\n");
587 /* @TODO put request_id to queue to further compare with incoming one */
588 self = sip_uri_self(sipe_private);
589 body = g_strdup_printf(
590 SIPE_SEND_CONF_DELETE_USER,
591 session->focus_dialog->with,
592 self,
593 session->request_id++,
594 session->focus_dialog->with,
595 who);
596 g_free(self);
598 sip_transport_info(sipe_private,
599 hdr,
600 body,
601 session->focus_dialog,
602 NULL);
603 g_free(body);
604 g_free(hdr);
607 /** Invite counterparty to join conference callback */
608 static gboolean
609 process_invite_conf_response(struct sipe_core_private *sipe_private,
610 struct sipmsg *msg,
611 SIPE_UNUSED_PARAMETER struct transaction *trans)
613 struct sip_dialog *dialog = g_new0(struct sip_dialog, 1);
615 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
616 dialog->cseq = sipmsg_parse_cseq(msg);
617 dialog->with = parse_from(sipmsg_find_header(msg, "To"));
618 sipe_dialog_parse(dialog, msg, TRUE);
620 if (msg->response >= 200) {
621 /* send ACK to counterparty */
622 dialog->cseq--;
623 sip_transport_ack(sipe_private, dialog);
624 dialog->outgoing_invite = NULL;
625 dialog->is_established = TRUE;
628 if (msg->response >= 400) {
629 SIPE_DEBUG_INFO("process_invite_conf_response: INVITE response is not 200. Failed to invite %s.", dialog->with);
630 /* @TODO notify user of failure to invite counterparty */
631 sipe_dialog_free(dialog);
632 return FALSE;
635 if (msg->response >= 200) {
636 struct sip_session *session = sipe_session_find_im(sipe_private, dialog->with);
637 struct sip_dialog *im_dialog = sipe_dialog_find(session, dialog->with);
639 /* close IM session to counterparty */
640 if (im_dialog) {
641 sip_transport_bye(sipe_private, im_dialog);
642 sipe_dialog_remove(session, dialog->with);
646 sipe_dialog_free(dialog);
647 return TRUE;
651 * Invites counterparty to join conference.
653 void
654 sipe_invite_conf(struct sipe_core_private *sipe_private,
655 struct sip_session *session,
656 const gchar* who)
658 gchar *hdr;
659 gchar *contact;
660 gchar *body;
661 struct sip_dialog *dialog = NULL;
663 /* It will be short lived special dialog.
664 * Will not be stored in session.
666 dialog = g_new0(struct sip_dialog, 1);
667 dialog->callid = gencallid();
668 dialog->with = g_strdup(who);
669 dialog->ourtag = gentag();
671 contact = get_contact(sipe_private);
672 hdr = g_strdup_printf(
673 "Supported: ms-sender\r\n"
674 "Contact: %s\r\n"
675 "Content-Type: application/ms-conf-invite+xml\r\n",
676 contact);
677 g_free(contact);
679 body = g_strdup_printf(
680 SIPE_SEND_CONF_INVITE,
681 session->chat_session->id,
682 session->subject ? session->subject : ""
685 sip_transport_invite(sipe_private,
686 hdr,
687 body,
688 dialog,
689 process_invite_conf_response);
691 sipe_dialog_free(dialog);
692 g_free(body);
693 g_free(hdr);
696 /** Create conference callback */
697 static gboolean
698 process_conf_add_response(struct sipe_core_private *sipe_private,
699 struct sipmsg *msg,
700 struct transaction *trans)
702 if (msg->response >= 400) {
703 SIPE_DEBUG_INFO_NOFORMAT("process_conf_add_response: SERVICE response is not 200. Failed to create conference.");
704 /* @TODO notify user of failure to create conference */
705 return FALSE;
707 if (msg->response == 200) {
708 sipe_xml *xn_response = sipe_xml_parse(msg->body, msg->bodylen);
709 if (sipe_strequal("success", sipe_xml_attribute(xn_response, "code")))
711 gchar *who = trans->payload->data;
712 const sipe_xml *xn_conference_info = sipe_xml_child(xn_response, "addConference/conference-info");
713 struct sip_session *session = sipe_conf_create(sipe_private,
714 NULL,
715 sipe_xml_attribute(xn_conference_info,
716 "entity"));
718 SIPE_DEBUG_INFO("process_conf_add_response: session->focus_uri=%s",
719 session->chat_session->id);
721 session->pending_invite_queue = sipe_utils_slist_insert_unique_sorted(session->pending_invite_queue,
722 g_strdup(who),
723 (GCompareFunc)strcmp,
724 g_free);
726 sipe_xml_free(xn_response);
729 return TRUE;
733 * Creates conference.
735 void
736 sipe_conf_add(struct sipe_core_private *sipe_private,
737 const gchar* who)
739 gchar *hdr;
740 gchar *conference_id;
741 gchar *contact;
742 gchar *body;
743 gchar *self;
744 struct transaction *trans;
745 struct sip_dialog *dialog = NULL;
746 time_t expiry = time(NULL) + 7*60*60; /* 7 hours */
747 char *expiry_time;
748 struct transaction_payload *payload;
750 contact = get_contact(sipe_private);
751 hdr = g_strdup_printf(
752 "Supported: ms-sender\r\n"
753 "Contact: %s\r\n"
754 "Content-Type: application/cccp+xml\r\n",
755 contact);
756 g_free(contact);
758 expiry_time = sipe_utils_time_to_str(expiry);
759 self = sip_uri_self(sipe_private);
760 conference_id = genconfid();
761 body = g_strdup_printf(
762 SIPE_SEND_CONF_ADD,
763 sipe_private->focus_factory_uri,
764 self,
765 rand(),
766 conference_id,
767 expiry_time);
768 g_free(self);
769 g_free(conference_id);
770 g_free(expiry_time);
772 trans = sip_transport_service(sipe_private,
773 sipe_private->focus_factory_uri,
774 hdr,
775 body,
776 process_conf_add_response);
778 payload = g_new0(struct transaction_payload, 1);
779 payload->destroy = g_free;
780 payload->data = g_strdup(who);
781 trans->payload = payload;
783 sipe_dialog_free(dialog);
784 g_free(body);
785 g_free(hdr);
788 static void
789 accept_incoming_invite_conf(struct sipe_core_private *sipe_private,
790 gchar *focus_uri,
791 gboolean audio,
792 struct sipmsg *msg)
794 struct sip_session *session;
795 gchar *newTag = gentag();
796 const gchar *oldHeader = sipmsg_find_header(msg, "To");
797 gchar *newHeader;
799 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
800 g_free(newTag);
801 sipmsg_remove_header_now(msg, "To");
802 sipmsg_add_header_now(msg, "To", newHeader);
803 g_free(newHeader);
805 /* acknowledge invite */
806 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
808 /* add self to conf */
809 session = sipe_conf_create(sipe_private, NULL, focus_uri);
810 session->is_call = audio;
813 struct conf_accept_ctx {
814 gchar *focus_uri;
815 struct sipmsg *msg;
816 struct sipe_user_ask_ctx *ask_ctx;
819 static void
820 conf_accept_ctx_free(struct conf_accept_ctx *ctx)
822 g_return_if_fail(ctx != NULL);
824 sipmsg_free(ctx->msg);
825 g_free(ctx->focus_uri);
826 g_free(ctx);
829 static void
830 conf_accept_cb(struct sipe_core_private *sipe_private, struct conf_accept_ctx *ctx)
832 sipe_private->sessions_to_accept =
833 g_slist_remove(sipe_private->sessions_to_accept, ctx);
835 accept_incoming_invite_conf(sipe_private, ctx->focus_uri, TRUE, ctx->msg);
836 conf_accept_ctx_free(ctx);
839 static void
840 conf_decline_cb(struct sipe_core_private *sipe_private, struct conf_accept_ctx *ctx)
842 sipe_private->sessions_to_accept =
843 g_slist_remove(sipe_private->sessions_to_accept, ctx);
845 sip_transport_response(sipe_private,
846 ctx->msg,
847 603, "Decline", NULL);
849 conf_accept_ctx_free(ctx);
852 void
853 sipe_conf_cancel_unaccepted(struct sipe_core_private *sipe_private,
854 struct sipmsg *msg)
856 const gchar *callid1 = msg ? sipmsg_find_header(msg, "Call-ID") : NULL;
857 GSList *it = sipe_private->sessions_to_accept;
858 while (it) {
859 struct conf_accept_ctx *ctx = it->data;
860 const gchar *callid2 = NULL;
862 if (msg && ctx->msg)
863 callid2 = sipmsg_find_header(ctx->msg, "Call-ID");
865 if (sipe_strequal(callid1, callid2)) {
866 GSList *tmp;
868 if (ctx->msg)
869 sip_transport_response(sipe_private, ctx->msg,
870 487, "Request Terminated", NULL);
872 if (msg)
873 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
875 sipe_user_close_ask(ctx->ask_ctx);
876 conf_accept_ctx_free(ctx);
878 tmp = it;
879 it = it->next;
881 sipe_private->sessions_to_accept =
882 g_slist_delete_link(sipe_private->sessions_to_accept, tmp);
884 if (callid1)
885 break;
886 } else
887 it = it->next;
891 static void
892 ask_accept_voice_conference(struct sipe_core_private *sipe_private,
893 const gchar *focus_uri,
894 struct sipmsg *msg,
895 SipeUserAskCb accept_cb,
896 SipeUserAskCb decline_cb)
898 gchar **parts;
899 gchar *alias;
900 gchar *ask_msg;
901 const gchar *novv_note;
902 struct conf_accept_ctx *ctx;
904 #ifdef HAVE_VV
905 novv_note = "";
906 #else
907 novv_note = _("\n\nAs this client was not compiled with voice call "
908 "support, if you accept, you will be able to contact "
909 "the other participants only via IM session.");
910 #endif
912 parts = g_strsplit(focus_uri, ";", 2);
913 alias = sipe_buddy_get_alias(sipe_private, parts[0]);
915 ask_msg = g_strdup_printf(_("%s wants to invite you "
916 "to the conference call%s"),
917 alias ? alias : parts[0], novv_note);
919 g_free(alias);
920 g_strfreev(parts);
922 ctx = g_new0(struct conf_accept_ctx, 1);
923 sipe_private->sessions_to_accept =
924 g_slist_append(sipe_private->sessions_to_accept, ctx);
926 ctx->focus_uri = g_strdup(focus_uri);
927 ctx->msg = msg ? sipmsg_copy(msg) : NULL;
928 ctx->ask_ctx = sipe_user_ask(sipe_private, ask_msg,
929 _("Accept"), accept_cb,
930 _("Decline"), decline_cb,
931 ctx);
933 g_free(ask_msg);
936 void
937 process_incoming_invite_conf(struct sipe_core_private *sipe_private,
938 struct sipmsg *msg)
940 sipe_xml *xn_conferencing = sipe_xml_parse(msg->body, msg->bodylen);
941 const sipe_xml *xn_focus_uri = sipe_xml_child(xn_conferencing, "focus-uri");
942 const sipe_xml *xn_audio = sipe_xml_child(xn_conferencing, "audio");
943 gchar *focus_uri = sipe_xml_data(xn_focus_uri);
944 gboolean audio = sipe_strequal(sipe_xml_attribute(xn_audio, "available"), "true");
946 sipe_xml_free(xn_conferencing);
948 SIPE_DEBUG_INFO("We have received invitation to Conference. Focus URI=%s", focus_uri);
950 if (audio) {
951 sip_transport_response(sipe_private, msg, 180, "Ringing", NULL);
952 ask_accept_voice_conference(sipe_private, focus_uri, msg,
953 (SipeUserAskCb) conf_accept_cb,
954 (SipeUserAskCb) conf_decline_cb);
956 } else {
957 accept_incoming_invite_conf(sipe_private, focus_uri, FALSE, msg);
960 g_free(focus_uri);
963 #ifdef HAVE_VV
965 static void
966 call_accept_cb(struct sipe_core_private *sipe_private, struct conf_accept_ctx *ctx)
968 struct sip_session *session;
969 session = sipe_session_find_conference(sipe_private, ctx->focus_uri);
971 sipe_private->sessions_to_accept =
972 g_slist_remove(sipe_private->sessions_to_accept, ctx);
974 if (session) {
975 sipe_core_media_connect_conference(SIPE_CORE_PUBLIC,
976 session->chat_session);
979 conf_accept_ctx_free(ctx);
982 static void
983 call_decline_cb(struct sipe_core_private *sipe_private, struct conf_accept_ctx *ctx)
985 sipe_private->sessions_to_accept =
986 g_slist_remove(sipe_private->sessions_to_accept, ctx);
988 conf_accept_ctx_free(ctx);
991 #endif // HAVE_VV
993 void
994 sipe_process_conference(struct sipe_core_private *sipe_private,
995 struct sipmsg *msg)
997 sipe_xml *xn_conference_info;
998 const sipe_xml *node;
999 const sipe_xml *xn_subject;
1000 const gchar *focus_uri;
1001 struct sip_session *session;
1002 gboolean just_joined = FALSE;
1003 #ifdef HAVE_VV
1004 gboolean audio_was_added = FALSE;
1005 #endif
1007 if (msg->response != 0 && msg->response != 200) return;
1009 if (msg->bodylen == 0 || msg->body == NULL || !sipe_strequal(sipmsg_find_header(msg, "Event"), "conference")) return;
1011 xn_conference_info = sipe_xml_parse(msg->body, msg->bodylen);
1012 if (!xn_conference_info) return;
1014 focus_uri = sipe_xml_attribute(xn_conference_info, "entity");
1015 session = sipe_session_find_conference(sipe_private, focus_uri);
1017 if (!session) {
1018 SIPE_DEBUG_INFO("sipe_process_conference: unable to find conf session with focus=%s", focus_uri);
1019 return;
1022 if (!session->chat_session->backend) {
1023 gchar *self = sip_uri_self(sipe_private);
1025 /* create chat */
1026 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
1027 session->chat_session,
1028 session->chat_session->title,
1029 self);
1030 just_joined = TRUE;
1031 /* @TODO ask for full state (re-subscribe) if it was a partial one -
1032 * this is to obtain full list of conference participants.
1034 g_free(self);
1037 /* subject */
1038 if ((xn_subject = sipe_xml_child(xn_conference_info, "conference-description/subject"))) {
1039 g_free(session->subject);
1040 session->subject = sipe_xml_data(xn_subject);
1041 sipe_backend_chat_topic(session->chat_session->backend, session->subject);
1042 SIPE_DEBUG_INFO("sipe_process_conference: subject=%s", session->subject ? session->subject : "");
1045 /* IM MCU URI */
1046 if (!session->im_mcu_uri) {
1047 for (node = sipe_xml_child(xn_conference_info, "conference-description/conf-uris/entry");
1048 node;
1049 node = sipe_xml_twin(node))
1051 gchar *purpose = sipe_xml_data(sipe_xml_child(node, "purpose"));
1053 if (sipe_strequal("chat", purpose)) {
1054 g_free(purpose);
1055 session->im_mcu_uri = sipe_xml_data(sipe_xml_child(node, "uri"));
1056 SIPE_DEBUG_INFO("sipe_process_conference: im_mcu_uri=%s", session->im_mcu_uri);
1057 break;
1059 g_free(purpose);
1063 /* users */
1064 for (node = sipe_xml_child(xn_conference_info, "users/user"); node; node = sipe_xml_twin(node)) {
1065 const gchar *user_uri = sipe_xml_attribute(node, "entity");
1066 const gchar *state = sipe_xml_attribute(node, "state");
1067 gchar *role = sipe_xml_data(sipe_xml_child(node, "roles/entry"));
1068 gboolean is_operator = sipe_strequal(role, "presenter");
1069 gboolean is_in_im_mcu = FALSE;
1070 gchar *self = sip_uri_self(sipe_private);
1072 if (sipe_strequal("deleted", state)) {
1073 if (sipe_backend_chat_find(session->chat_session->backend, user_uri)) {
1074 sipe_backend_chat_remove(session->chat_session->backend,
1075 user_uri);
1077 } else {
1078 /* endpoints */
1079 const sipe_xml *endpoint;
1080 for (endpoint = sipe_xml_child(node, "endpoint"); endpoint; endpoint = sipe_xml_twin(endpoint)) {
1081 const gchar *session_type;
1082 gchar *status = sipe_xml_data(sipe_xml_child(endpoint, "status"));
1083 gboolean connected = sipe_strequal("connected", status);
1084 g_free(status);
1086 if (!connected)
1087 continue;
1089 session_type = sipe_xml_attribute(endpoint, "session-type");
1091 if (sipe_strequal("chat", session_type)) {
1092 is_in_im_mcu = TRUE;
1093 if (!sipe_backend_chat_find(session->chat_session->backend, user_uri)) {
1094 sipe_backend_chat_add(session->chat_session->backend,
1095 user_uri,
1096 !just_joined && g_ascii_strcasecmp(user_uri, self));
1098 if (is_operator) {
1099 sipe_backend_chat_operator(session->chat_session->backend,
1100 user_uri);
1102 } else if (sipe_strequal("audio-video", session_type)) {
1103 #ifdef HAVE_VV
1104 if (!session->is_call)
1105 audio_was_added = TRUE;
1106 #endif
1109 if (!is_in_im_mcu) {
1110 if (sipe_backend_chat_find(session->chat_session->backend, user_uri)) {
1111 sipe_backend_chat_remove(session->chat_session->backend,
1112 user_uri);
1116 g_free(role);
1117 g_free(self);
1120 #ifdef HAVE_VV
1121 if (audio_was_added) {
1122 session->is_call = TRUE;
1123 ask_accept_voice_conference(sipe_private, focus_uri, NULL,
1124 (SipeUserAskCb) call_accept_cb,
1125 (SipeUserAskCb) call_decline_cb);
1127 #endif
1129 /* entity-view, locked */
1130 for (node = sipe_xml_child(xn_conference_info, "conference-view/entity-view");
1131 node;
1132 node = sipe_xml_twin(node)) {
1134 const sipe_xml *xn_type = sipe_xml_child(node, "entity-state/media/entry/type");
1135 gchar *tmp = NULL;
1136 if (xn_type && sipe_strequal("chat", (tmp = sipe_xml_data(xn_type)))) {
1137 const sipe_xml *xn_locked = sipe_xml_child(node, "entity-state/locked");
1138 if (xn_locked) {
1139 gchar *locked = sipe_xml_data(xn_locked);
1140 gboolean prev_locked = session->locked;
1141 session->locked = sipe_strequal(locked, "true");
1142 if (prev_locked && !session->locked) {
1143 sipe_user_present_info(sipe_private, session,
1144 _("This conference is no longer locked. Additional participants can now join."));
1146 if (!prev_locked && session->locked) {
1147 sipe_user_present_info(sipe_private, session,
1148 _("This conference is locked. Nobody else can join the conference while it is locked."));
1151 SIPE_DEBUG_INFO("sipe_process_conference: session->locked=%s",
1152 session->locked ? "TRUE" : "FALSE");
1153 g_free(locked);
1156 g_free(tmp);
1158 sipe_xml_free(xn_conference_info);
1160 if (session->im_mcu_uri) {
1161 struct sip_dialog *dialog = sipe_dialog_find(session, session->im_mcu_uri);
1162 if (!dialog) {
1163 dialog = sipe_dialog_add(session);
1165 dialog->callid = g_strdup(session->callid);
1166 dialog->with = g_strdup(session->im_mcu_uri);
1168 /* send INVITE to IM MCU */
1169 sipe_im_invite(sipe_private, session, dialog->with, NULL, NULL, NULL, FALSE);
1173 sipe_process_pending_invite_queue(sipe_private, session);
1176 void
1177 sipe_conf_immcu_closed(struct sipe_core_private *sipe_private,
1178 struct sip_session *session)
1180 sipe_user_present_info(sipe_private, session,
1181 _("You have been disconnected from this conference."));
1182 sipe_backend_chat_close(session->chat_session->backend);
1185 void
1186 conf_session_close(struct sipe_core_private *sipe_private,
1187 struct sip_session *session)
1189 if (session) {
1190 /* unsubscribe from focus */
1191 sipe_subscribe_conference(sipe_private,
1192 session->chat_session->id, TRUE);
1194 if (session->focus_dialog) {
1195 /* send BYE to focus */
1196 sip_transport_bye(sipe_private, session->focus_dialog);
1201 void
1202 sipe_process_imdn(struct sipe_core_private *sipe_private,
1203 struct sipmsg *msg)
1205 gchar *with = parse_from(sipmsg_find_header(msg, "From"));
1206 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
1207 static struct sip_session *session;
1208 sipe_xml *xn_imdn;
1209 const sipe_xml *node;
1210 gchar *message_id;
1211 gchar *message;
1213 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
1214 if (!session) {
1215 SIPE_DEBUG_INFO("sipe_process_imdn: unable to find conf session with callid=%s", callid);
1216 g_free(with);
1217 return;
1220 xn_imdn = sipe_xml_parse(msg->body, msg->bodylen);
1221 message_id = sipe_xml_data(sipe_xml_child(xn_imdn, "message-id"));
1223 message = g_hash_table_lookup(session->conf_unconfirmed_messages, message_id);
1225 /* recipient */
1226 for (node = sipe_xml_child(xn_imdn, "recipient"); node; node = sipe_xml_twin(node)) {
1227 gchar *tmp = parse_from(sipe_xml_attribute(node, "uri"));
1228 gchar *uri = parse_from(tmp);
1229 gchar *status = sipe_xml_data(sipe_xml_child(node, "status"));
1230 guint error = status ? g_ascii_strtoull(status, NULL, 10) : 0;
1231 /* default to error if missing or conversion failed */
1232 if ((error == 0) || (error >= 300))
1233 sipe_user_present_message_undelivered(sipe_private,
1234 session,
1235 error,
1237 uri,
1238 message);
1239 g_free(status);
1240 g_free(tmp);
1241 g_free(uri);
1244 sipe_xml_free(xn_imdn);
1246 g_hash_table_remove(session->conf_unconfirmed_messages, message_id);
1247 SIPE_DEBUG_INFO("sipe_process_imdn: removed message %s from conf_unconfirmed_messages(count=%d)",
1248 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
1249 g_free(message_id);
1250 g_free(with);
1253 void sipe_core_conf_make_leader(struct sipe_core_public *sipe_public,
1254 gpointer parameter,
1255 const gchar *buddy_name)
1257 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1258 struct sipe_chat_session *chat_session = parameter;
1259 struct sip_session *session;
1261 SIPE_DEBUG_INFO("sipe_core_conf_make_leader: chat_title=%s",
1262 chat_session->title);
1264 session = sipe_session_find_chat(sipe_private, chat_session);
1265 sipe_conf_modify_user_role(sipe_private, session, buddy_name);
1268 void sipe_core_conf_remove_from(struct sipe_core_public *sipe_public,
1269 gpointer parameter,
1270 const gchar *buddy_name)
1272 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1273 struct sipe_chat_session *chat_session = parameter;
1274 struct sip_session *session;
1276 SIPE_DEBUG_INFO("sipe_core_conf_remove_from: chat_title=%s",
1277 chat_session->title);
1279 session = sipe_session_find_chat(sipe_private, chat_session);
1280 sipe_conf_delete_user(sipe_private, session, buddy_name);
1284 Local Variables:
1285 mode: c
1286 c-file-style: "bsd"
1287 indent-tabs-mode: t
1288 tab-width: 8
1289 End: