6 * Copyright (C) 2010-2016 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
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>
46 #include "sipe-appshare.h"
49 #include "sipe-common.h"
51 #include "sip-transport.h"
52 #include "sipe-backend.h"
53 #include "sipe-buddy.h"
54 #include "sipe-chat.h"
55 #include "sipe-conf.h"
56 #include "sipe-core.h"
57 #include "sipe-core-private.h"
58 #include "sipe-dialog.h"
59 #include "sipe-http.h"
62 #include "sipe-session.h"
63 #include "sipe-subscriptions.h"
64 #include "sipe-user.h"
65 #include "sipe-utils.h"
69 * Invite counterparty to join conference.
70 * @param focus_uri (%s)
71 * @param subject (%s) of conference
73 #define SIPE_SEND_CONF_INVITE \
74 "<Conferencing version=\"2.0\">"\
75 "<focus-uri>%s</focus-uri>"\
76 "<subject>%s</subject>"\
77 "<im available=\"true\">"\
83 sipe_conf_check_for_lync_url(struct sipe_core_private
*sipe_private
,
86 static struct transaction
*
87 cccp_request(struct sipe_core_private
*sipe_private
, const gchar
*method
,
88 const gchar
*with
, struct sip_dialog
*dialog
,
89 TransCallback callback
, const gchar
*body
, ...)
95 gchar
*self
= sip_uri_self(sipe_private
);
99 struct transaction
*trans
;
101 headers
= g_strdup_printf(
102 "Supported: ms-sender\r\n"
104 "Content-Type: application/cccp+xml\r\n",
105 sipe_private
->contact
);
107 /* TODO: put request_id to queue to further compare with incoming one */
108 request
= g_strdup_printf(
109 "<?xml version=\"1.0\"?>"
110 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" "
111 "xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "
120 sipe_private
->cccp_request_id
++,
124 va_start(args
, body
);
125 request_body
= g_strdup_vprintf(request
, args
);
130 trans
= sip_transport_request(sipe_private
,
140 g_free(request_body
);
146 process_conf_get_capabilities(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
148 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
150 if (msg
->response
>= 400) {
151 SIPE_DEBUG_INFO_NOFORMAT("process_conf_get_capabilities: "
152 "getConferencingCapabilities failed.");
155 if (msg
->response
== 200) {
156 sipe_xml
*xn_response
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
157 const sipe_xml
*node
;
158 gchar
*default_region
;
160 if (!sipe_strequal("success", sipe_xml_attribute(xn_response
, "code"))) {
164 node
= sipe_xml_child(xn_response
, "getConferencingCapabilities/mcu-types/mcuType");
165 for (;node
; node
= sipe_xml_twin(node
)) {
166 sipe_private
->conf_mcu_types
=
167 g_slist_append(sipe_private
->conf_mcu_types
,
168 sipe_xml_data(node
));
171 g_hash_table_remove_all(sipe_private
->access_numbers
);
172 node
= sipe_xml_child(xn_response
, "getConferencingCapabilities/pstn-bridging/access-numbers/region");
173 for (;node
; node
= sipe_xml_twin(node
)) {
174 gchar
*name
= g_strdup(sipe_xml_attribute(node
, "name"));
175 gchar
*number
= sipe_xml_data(sipe_xml_child(node
, "access-number/number"));
176 if (name
&& number
) {
177 g_hash_table_insert(sipe_private
->access_numbers
, name
, number
);
181 node
= sipe_xml_child(xn_response
, "getConferencingCapabilities/pstn-bridging/access-numbers/default-region");
182 default_region
= sipe_xml_data(node
);
183 if (default_region
) {
184 sipe_private
->default_access_number
=
185 g_hash_table_lookup(sipe_private
->access_numbers
, default_region
);
187 g_free(default_region
);
189 sipe_xml_free(xn_response
);
196 sipe_conf_get_capabilities(struct sipe_core_private
*sipe_private
)
198 cccp_request(sipe_private
, "SERVICE",
199 sipe_private
->focus_factory_uri
,
201 process_conf_get_capabilities
,
202 "<getConferencingCapabilities />");
206 sipe_conf_supports_mcu_type(struct sipe_core_private
*sipe_private
,
209 return g_slist_find_custom(sipe_private
->conf_mcu_types
, type
,
210 sipe_strcompare
) != NULL
;
214 * Generates random GUID.
215 * This method is borrowed from pidgin's msnutils.c
220 return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X",
221 rand() % 0xAAFF + 0x1111,
222 rand() % 0xAAFF + 0x1111,
223 rand() % 0xAAFF + 0x1111,
224 rand() % 0xAAFF + 0x1111,
225 rand() % 0xAAFF + 0x1111,
226 rand() % 0xAAFF + 0x1111,
227 rand() % 0xAAFF + 0x1111,
228 rand() % 0xAAFF + 0x1111);
231 /** Invite us to the focus callback */
233 process_invite_conf_focus_response(struct sipe_core_private
*sipe_private
,
235 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
237 struct sip_session
*session
= NULL
;
238 char *focus_uri
= parse_from(sipmsg_find_header(msg
, "To"));
240 session
= sipe_session_find_conference(sipe_private
, focus_uri
);
243 SIPE_DEBUG_INFO("process_invite_conf_focus_response: unable to find conf session with focus=%s", focus_uri
);
248 if (!session
->focus_dialog
) {
249 SIPE_DEBUG_INFO_NOFORMAT("process_invite_conf_focus_response: session's focus_dialog is NULL");
254 sipe_dialog_parse(session
->focus_dialog
, msg
, TRUE
);
256 if (msg
->response
>= 200) {
257 /* send ACK to focus */
258 session
->focus_dialog
->cseq
= 0;
259 sip_transport_ack(sipe_private
, session
->focus_dialog
);
260 session
->focus_dialog
->outgoing_invite
= NULL
;
261 session
->focus_dialog
->is_established
= TRUE
;
264 if (msg
->response
>= 400) {
265 gchar
*reason
= sipmsg_get_ms_diagnostics_reason(msg
);
267 SIPE_DEBUG_INFO_NOFORMAT("process_invite_conf_focus_response: INVITE response is not 200. Failed to join focus.");
268 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
269 _("Failed to join the conference"),
270 reason
? reason
: _("no reason given"));
273 sipe_session_remove(sipe_private
, session
);
276 } else if (msg
->response
== 200) {
277 sipe_xml
*xn_response
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
278 const gchar
*code
= sipe_xml_attribute(xn_response
, "code");
279 if (sipe_strequal(code
, "success")) {
280 /* subscribe to focus */
281 sipe_subscribe_conference(sipe_private
,
282 session
->chat_session
->id
,
285 if (session
->is_call
)
286 sipe_core_media_connect_conference(SIPE_CORE_PUBLIC
,
287 session
->chat_session
);
290 sipe_xml_free(xn_response
);
298 parse_ocs_focus_uri(const gchar
*uri
)
300 const gchar
*confkey
;
306 // URI can have this prefix if it was typed in by the user
307 if (g_str_has_prefix(uri
, "meet:") || g_str_has_prefix(uri
, "conf:")) {
311 uri_len
= strlen(uri
);
313 if (!uri
|| !g_str_has_prefix(uri
, "sip:") ||
314 uri_len
== 4 || g_strstr_len(uri
, -1, "%")) {
318 confkey
= g_strstr_len(uri
, -1, "?");
320 /* TODO: Investigate how conf-key field should be used,
321 * ignoring for now */
322 uri_len
= confkey
- uri
;
325 return g_strndup(uri
, uri_len
);
329 extract_uri_from_html(const gchar
*body
,
331 guint prefix_skip_chars
)
334 const gchar
*start
= g_strstr_len(body
, -1, prefix
);
339 start
+= prefix_skip_chars
;
340 end
= strchr(start
, '"');
343 gchar
*html
= g_strndup(start
, end
- start
);
345 /* decode HTML entities */
346 gchar
*html_unescaped
= sipe_backend_markup_strip_html(html
);
349 if (!is_empty(html_unescaped
)) {
350 uri
= sipe_utils_uri_unescape(html_unescaped
);
353 g_free(html_unescaped
);
360 static void sipe_conf_lync_url_cb(struct sipe_core_private
*sipe_private
,
362 SIPE_UNUSED_PARAMETER GSList
*headers
,
364 gpointer callback_data
)
366 gchar
*uri
= callback_data
;
368 if (status
!= (guint
) SIPE_HTTP_STATUS_ABORTED
) {
369 gchar
*focus_uri
= NULL
;
373 * Extract focus URI from HTML, e.g.
375 * <a ... href="conf:sip:...ABCDEF%3Frequired..." ... >
377 gchar
*uri
= extract_uri_from_html(body
, "href=\"conf", 6);
378 focus_uri
= parse_ocs_focus_uri(uri
);
383 SIPE_DEBUG_INFO("sipe_conf_lync_url_cb: found focus URI"
386 sipe_conf_create(sipe_private
, NULL
, focus_uri
);
390 * If present, domainOwnerJoinLauncherUrl redirects to
391 * a page from where we still may extract the focus URI.
394 static const gchar launcher_url_prefix
[] =
395 "var domainOwnerJoinLauncherUrl = \"";
397 SIPE_DEBUG_INFO("sipe_conf_lync_url_cb: no focus URI "
398 "found from URL '%s'", uri
);
400 launcher_url
= extract_uri_from_html(body
,
402 sizeof (launcher_url_prefix
) - 1);
405 sipe_conf_check_for_lync_url(sipe_private
, launcher_url
)) {
406 SIPE_DEBUG_INFO("sipe_conf_lync_url_cb: retrying with URL '%s'",
408 /* Ownership taken by sipe_conf_check_for_lync_url() */
413 error
= g_strdup_printf(_("Can't find a conference URI on this page:\n\n%s"),
416 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
417 _("Failed to join the conference"),
422 g_free(launcher_url
);
429 static gboolean
sipe_conf_check_for_lync_url(struct sipe_core_private
*sipe_private
,
432 if (!(g_str_has_prefix(uri
, "https://") ||
433 g_str_has_prefix(uri
, "http://")))
436 /* URL points to a HTML page with the conference focus URI */
437 return(sipe_http_request_get(sipe_private
,
440 sipe_conf_lync_url_cb
,
445 static void sipe_conf_uri_error(struct sipe_core_private
*sipe_private
,
448 gchar
*error
= g_strdup_printf(_("\"%s\" is not a valid conference URI"),
450 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
451 _("Failed to join the conference"),
456 void sipe_core_conf_create(struct sipe_core_public
*sipe_public
,
458 const gchar
*organizer
,
459 const gchar
*meeting_id
)
461 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
463 /* SIP URI or HTTP URL */
465 gchar
*uri_ue
= sipe_utils_uri_unescape(uri
);
467 SIPE_DEBUG_INFO("sipe_core_conf_create: URI '%s' unescaped '%s'",
469 uri_ue
? uri_ue
: "<UNDEFINED>");
471 /* takes ownership of "uri_ue" if successful */
472 if (!sipe_conf_check_for_lync_url(sipe_private
, uri_ue
)) {
473 gchar
*focus_uri
= parse_ocs_focus_uri(uri_ue
);
476 sipe_conf_create(sipe_private
, NULL
, focus_uri
);
479 sipe_conf_uri_error(sipe_private
, uri
);
484 /* Organizer email and meeting ID */
485 } else if (organizer
&& meeting_id
) {
486 gchar
*tmp
= g_strdup_printf("sip:%s;gruu;opaque=app:conf:focus:id:%s",
487 organizer
, meeting_id
);
488 gchar
*focus_uri
= parse_ocs_focus_uri(tmp
);
490 SIPE_DEBUG_INFO("sipe_core_conf_create: organizer '%s' meeting ID '%s'",
495 sipe_conf_create(sipe_private
, NULL
, focus_uri
);
498 sipe_conf_uri_error(sipe_private
, tmp
);
502 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
503 _("Failed to join the conference"),
504 _("Incomplete conference information provided"));
508 /** Create new session with Focus URI */
510 sipe_conf_create(struct sipe_core_private
*sipe_private
,
511 struct sipe_chat_session
*chat_session
,
512 const gchar
*focus_uri
)
514 /* addUser request to the focus.
516 * focus_URI, from, endpoint_GUID
518 static const gchar CCCP_ADD_USER
[] =
520 "<conferenceKeys confEntity=\"%s\"/>"
521 "<ci:user xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" entity=\"%s\">"
523 "<ci:entry>attendee</ci:entry>"
525 "<ci:endpoint entity=\"{%s}\" "
526 "xmlns:msci=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\"/>"
531 struct sip_session
*session
= sipe_session_add_chat(sipe_private
,
536 session
->focus_dialog
= g_new0(struct sip_dialog
, 1);
537 session
->focus_dialog
->callid
= gencallid();
538 session
->focus_dialog
->with
= g_strdup(session
->chat_session
->id
);
539 session
->focus_dialog
->endpoint_GUID
= rand_guid();
540 session
->focus_dialog
->ourtag
= gentag();
542 self
= sip_uri_self(sipe_private
);
543 session
->focus_dialog
->outgoing_invite
=
544 cccp_request(sipe_private
, "INVITE",
545 session
->focus_dialog
->with
, session
->focus_dialog
,
546 process_invite_conf_focus_response
,
548 session
->focus_dialog
->with
, self
,
549 session
->focus_dialog
->endpoint_GUID
);
551 /* Rejoin existing session? */
553 SIPE_DEBUG_INFO("sipe_conf_create: rejoin '%s' (%s)",
556 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC
,
557 chat_session
->backend
,
559 chat_session
->title
);
566 /** Modify User Role */
568 sipe_conf_modify_user_role(struct sipe_core_private
*sipe_private
,
569 struct sip_session
*session
,
572 /* modifyUserRoles request to the focus. Makes user a leader.
577 static const gchar CCCP_MODIFY_USER_ROLES
[] =
579 "<userKeys confEntity=\"%s\" userEntity=\"%s\"/>"
580 "<user-roles xmlns=\"urn:ietf:params:xml:ns:conference-info\">"
581 "<entry>presenter</entry>"
583 "</modifyUserRoles>";
585 if (!session
->focus_dialog
|| !session
->focus_dialog
->is_established
) {
586 SIPE_DEBUG_INFO_NOFORMAT("sipe_conf_modify_user_role: no dialog with focus, exiting.");
590 cccp_request(sipe_private
, "INFO", session
->focus_dialog
->with
,
591 session
->focus_dialog
, NULL
,
592 CCCP_MODIFY_USER_ROLES
,
593 session
->focus_dialog
->with
, who
);
597 * Check conference lock status
599 sipe_chat_lock_status
sipe_core_chat_lock_status(struct sipe_core_public
*sipe_public
,
600 struct sipe_chat_session
*chat_session
)
602 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
603 sipe_chat_lock_status status
= SIPE_CHAT_LOCK_STATUS_NOT_ALLOWED
;
606 (chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
)) {
607 struct sip_session
*session
= sipe_session_find_chat(sipe_private
,
610 gchar
*self
= sip_uri_self(sipe_private
);
612 /* Only operators are allowed to change the lock status */
613 if (sipe_backend_chat_is_operator(chat_session
->backend
, self
)) {
614 status
= session
->locked
?
615 SIPE_CHAT_LOCK_STATUS_LOCKED
:
616 SIPE_CHAT_LOCK_STATUS_UNLOCKED
;
627 * Modify Conference Lock
628 * Sends request to Focus.
629 * INFO method is a carrier of application/cccp+xml
632 sipe_core_chat_modify_lock(struct sipe_core_public
*sipe_public
,
633 struct sipe_chat_session
*chat_session
,
634 const gboolean locked
)
636 /* modifyConferenceLock request to the focus. Locks/unlocks conference.
639 * locked (%s) "true" or "false" values applicable
641 static const gchar CCCP_MODIFY_CONFERENCE_LOCK
[] =
642 "<modifyConferenceLock>"
643 "<conferenceKeys confEntity=\"%s\"/>"
644 "<locked>%s</locked>"
645 "</modifyConferenceLock>";
647 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
649 struct sip_session
*session
= sipe_session_find_chat(sipe_private
,
652 if (!session
) return;
653 if (!session
->focus_dialog
|| !session
->focus_dialog
->is_established
) {
654 SIPE_DEBUG_INFO_NOFORMAT("sipe_conf_modify_conference_lock: no dialog with focus, exiting.");
658 cccp_request(sipe_private
, "INFO", session
->focus_dialog
->with
,
659 session
->focus_dialog
, NULL
,
660 CCCP_MODIFY_CONFERENCE_LOCK
,
661 session
->focus_dialog
->with
,
662 locked
? "true" : "false");
665 /** Modify Delete User */
667 sipe_conf_delete_user(struct sipe_core_private
*sipe_private
,
668 struct sip_session
*session
,
671 /* deleteUser request to the focus. Removes a user from the conference.
676 static const gchar CCCP_DELETE_USER
[] =
678 "<userKeys confEntity=\"%s\" userEntity=\"%s\"/>"
681 if (!session
->focus_dialog
|| !session
->focus_dialog
->is_established
) {
682 SIPE_DEBUG_INFO_NOFORMAT("sipe_conf_delete_user: no dialog with focus, exiting.");
686 cccp_request(sipe_private
, "INFO", session
->focus_dialog
->with
,
687 session
->focus_dialog
, NULL
,
689 session
->focus_dialog
->with
, who
);
693 sipe_conf_announce_audio_mute_state(struct sipe_core_private
*sipe_private
,
694 struct sip_session
*session
,
697 // See [MS-CONFAV] 3.2.5.4 and 4.3
698 static const gchar CCCP_MODIFY_ENDPOINT_MEDIA
[] =
699 "<modifyEndpointMedia mscp:mcuUri=\"%s\""
700 " xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\">"
701 "<mediaKeys confEntity=\"%s\" userEntity=\"%s\""
702 " endpointEntity=\"%s\" mediaId=\"%d\"/>"
704 " xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" id=\"%d\">"
705 "<ci:type>audio</ci:type>"
706 "<ci:status>%s</ci:status>"
707 "<media-ingress-filter"
708 " xmlns=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\">"
710 "</media-ingress-filter>"
712 "</modifyEndpointMedia>";
714 gchar
*mcu_uri
= sipe_conf_build_uri(session
->focus_dialog
->with
,
716 gchar
*self
= sip_uri_self(sipe_private
);
718 cccp_request(sipe_private
, "INFO", session
->focus_dialog
->with
,
719 session
->focus_dialog
, NULL
,
720 CCCP_MODIFY_ENDPOINT_MEDIA
,
721 mcu_uri
, session
->focus_dialog
->with
, self
,
722 session
->audio_video_entity
,
723 session
->audio_media_id
, session
->audio_media_id
,
724 is_muted
? "recvonly" : "sendrecv",
725 is_muted
? "block" : "unblock");
731 /** Invite counterparty to join conference callback */
733 process_invite_conf_response(struct sipe_core_private
*sipe_private
,
735 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
737 struct sip_dialog
*dialog
= g_new0(struct sip_dialog
, 1);
739 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
740 dialog
->cseq
= sipmsg_parse_cseq(msg
);
741 dialog
->with
= parse_from(sipmsg_find_header(msg
, "To"));
742 sipe_dialog_parse(dialog
, msg
, TRUE
);
744 if (msg
->response
>= 200) {
745 /* send ACK to counterparty */
747 sip_transport_ack(sipe_private
, dialog
);
748 dialog
->outgoing_invite
= NULL
;
749 dialog
->is_established
= TRUE
;
752 if (msg
->response
>= 400) {
753 SIPE_DEBUG_INFO("process_invite_conf_response: INVITE response is not 200. Failed to invite %s.", dialog
->with
);
754 /* @TODO notify user of failure to invite counterparty */
755 sipe_dialog_free(dialog
);
759 if (msg
->response
>= 200) {
760 struct sip_session
*session
= sipe_session_find_im(sipe_private
, dialog
->with
);
761 struct sip_dialog
*im_dialog
= sipe_dialog_find(session
, dialog
->with
);
763 /* close IM session to counterparty */
765 sip_transport_bye(sipe_private
, im_dialog
);
766 sipe_dialog_remove(session
, dialog
->with
);
770 sipe_dialog_free(dialog
);
775 * Invites counterparty to join conference.
778 sipe_invite_conf(struct sipe_core_private
*sipe_private
,
779 struct sip_session
*session
,
785 struct sip_dialog
*dialog
= NULL
;
787 /* It will be short lived special dialog.
788 * Will not be stored in session.
790 dialog
= g_new0(struct sip_dialog
, 1);
791 dialog
->callid
= gencallid();
792 dialog
->with
= g_strdup(who
);
793 dialog
->ourtag
= gentag();
795 contact
= get_contact(sipe_private
);
796 hdr
= g_strdup_printf(
797 "Supported: ms-sender\r\n"
799 "Content-Type: application/ms-conf-invite+xml\r\n",
803 body
= g_strdup_printf(
804 SIPE_SEND_CONF_INVITE
,
805 session
->chat_session
->id
,
806 session
->subject
? session
->subject
: ""
809 sip_transport_invite(sipe_private
,
813 process_invite_conf_response
);
815 sipe_dialog_free(dialog
);
820 /** Create conference callback */
822 process_conf_add_response(struct sipe_core_private
*sipe_private
,
824 struct transaction
*trans
)
826 if (msg
->response
>= 400) {
827 SIPE_DEBUG_INFO_NOFORMAT("process_conf_add_response: SERVICE response is not 200. Failed to create conference.");
828 /* @TODO notify user of failure to create conference */
831 if (msg
->response
== 200) {
832 sipe_xml
*xn_response
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
833 if (sipe_strequal("success", sipe_xml_attribute(xn_response
, "code")))
835 gchar
*who
= trans
->payload
->data
;
836 const sipe_xml
*xn_conference_info
= sipe_xml_child(xn_response
, "addConference/conference-info");
837 struct sip_session
*session
= sipe_conf_create(sipe_private
,
839 sipe_xml_attribute(xn_conference_info
,
842 SIPE_DEBUG_INFO("process_conf_add_response: session->focus_uri=%s",
843 session
->chat_session
->id
);
845 session
->pending_invite_queue
= sipe_utils_slist_insert_unique_sorted(session
->pending_invite_queue
,
847 (GCompareFunc
)strcmp
,
850 sipe_xml_free(xn_response
);
857 * Creates conference.
860 sipe_conf_add(struct sipe_core_private
*sipe_private
,
863 gchar
*conference_id
;
864 struct transaction
*trans
;
865 time_t expiry
= time(NULL
) + 7*60*60; /* 7 hours */
868 /* addConference request to the focus factory.
870 * conference_id (%s) Ex.: 8386E6AEAAA41E4AA6627BA76D43B6D1
871 * expiry_time (%s) Ex.: 2009-07-13T17:57:09Z
872 * conference_view (%s) Ex.: <msci:entity-view entity="chat"/>
874 static const gchar CCCP_ADD_CONFERENCE
[] =
876 "<ci:conference-info xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" "
878 "xmlns:msci=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\">"
879 "<ci:conference-description>"
881 "<msci:conference-id>%s</msci:conference-id>"
882 "<msci:expiry-time>%s</msci:expiry-time>"
883 "<msci:admission-policy>openAuthenticated</msci:admission-policy>"
884 "</ci:conference-description>"
885 "<msci:conference-view>%s</msci:conference-view>"
886 "</ci:conference-info>"
889 static const gchar
*DESIRED_MCU_TYPES
[] = {
897 GString
*conference_view
= g_string_new("");
900 for (type
= DESIRED_MCU_TYPES
; *type
; ++type
) {
901 if (sipe_conf_supports_mcu_type(sipe_private
, *type
)) {
902 g_string_append(conference_view
, "<msci:entity-view entity=\"");
903 g_string_append(conference_view
, *type
);
904 g_string_append(conference_view
, "\"/>");
908 expiry_time
= sipe_utils_time_to_str(expiry
);
909 conference_id
= genconfid();
910 trans
= cccp_request(sipe_private
, "SERVICE", sipe_private
->focus_factory_uri
,
911 NULL
, process_conf_add_response
,
913 conference_id
, expiry_time
, conference_view
->str
);
914 g_free(conference_id
);
916 g_string_free(conference_view
, TRUE
);
919 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
921 payload
->destroy
= g_free
;
922 payload
->data
= g_strdup(who
);
923 trans
->payload
= payload
;
928 accept_incoming_invite_conf(struct sipe_core_private
*sipe_private
,
933 struct sip_session
*session
;
934 gchar
*newTag
= gentag();
935 const gchar
*oldHeader
= sipmsg_find_header(msg
, "To");
938 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
940 sipmsg_remove_header_now(msg
, "To");
941 sipmsg_add_header_now(msg
, "To", newHeader
);
944 /* acknowledge invite */
945 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
947 /* add self to conf */
948 session
= sipe_conf_create(sipe_private
, NULL
, focus_uri
);
949 session
->is_call
= audio
;
952 struct conf_accept_ctx
{
955 struct sipe_user_ask_ctx
*ask_ctx
;
957 SipeUserAskCb accept_cb
;
958 SipeUserAskCb decline_cb
;
964 conf_accept_ctx_free(struct conf_accept_ctx
*ctx
)
966 g_return_if_fail(ctx
!= NULL
);
968 sipmsg_free(ctx
->msg
);
969 g_free(ctx
->focus_uri
);
974 conf_accept_cb(struct sipe_core_private
*sipe_private
, struct conf_accept_ctx
*ctx
)
976 accept_incoming_invite_conf(sipe_private
, ctx
->focus_uri
, TRUE
, ctx
->msg
);
980 conf_decline_cb(struct sipe_core_private
*sipe_private
, struct conf_accept_ctx
*ctx
)
982 sip_transport_response(sipe_private
,
984 603, "Decline", NULL
);
988 sipe_conf_cancel_unaccepted(struct sipe_core_private
*sipe_private
,
991 const gchar
*callid1
= msg
? sipmsg_find_header(msg
, "Call-ID") : NULL
;
992 GSList
*it
= sipe_private
->sessions_to_accept
;
994 struct conf_accept_ctx
*ctx
= it
->data
;
995 const gchar
*callid2
= NULL
;
998 callid2
= sipmsg_find_header(ctx
->msg
, "Call-ID");
1000 if (sipe_strequal(callid1
, callid2
)) {
1004 sip_transport_response(sipe_private
, ctx
->msg
,
1005 487, "Request Terminated", NULL
);
1008 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
1010 sipe_user_close_ask(ctx
->ask_ctx
);
1011 conf_accept_ctx_free(ctx
);
1016 sipe_private
->sessions_to_accept
=
1017 g_slist_delete_link(sipe_private
->sessions_to_accept
, tmp
);
1027 accept_invitation_cb(struct sipe_core_private
*sipe_private
, gpointer data
)
1029 struct conf_accept_ctx
*ctx
= data
;
1031 sipe_private
->sessions_to_accept
=
1032 g_slist_remove(sipe_private
->sessions_to_accept
, ctx
);
1034 if (ctx
->accept_cb
) {
1035 ctx
->accept_cb(sipe_private
, ctx
);
1038 conf_accept_ctx_free(ctx
);
1042 decline_invitation_cb(struct sipe_core_private
*sipe_private
, gpointer data
)
1044 struct conf_accept_ctx
*ctx
= data
;
1046 sipe_private
->sessions_to_accept
=
1047 g_slist_remove(sipe_private
->sessions_to_accept
, ctx
);
1049 if (ctx
->decline_cb
) {
1050 ctx
->decline_cb(sipe_private
, ctx
);
1053 conf_accept_ctx_free(ctx
);
1057 ask_accept_invitation(struct sipe_core_private
*sipe_private
,
1058 const gchar
*focus_uri
,
1059 const gchar
*question
,
1061 SipeUserAskCb accept_cb
,
1062 SipeUserAskCb decline_cb
,
1067 gchar
*question_str
;
1068 struct conf_accept_ctx
*ctx
;
1070 parts
= g_strsplit(focus_uri
, ";", 2);
1071 alias
= sipe_buddy_get_alias(sipe_private
, parts
[0]);
1073 question_str
= g_strdup_printf("%s %s", alias
? alias
: parts
[0], question
);
1078 ctx
= g_new0(struct conf_accept_ctx
, 1);
1079 sipe_private
->sessions_to_accept
=
1080 g_slist_append(sipe_private
->sessions_to_accept
, ctx
);
1082 ctx
->focus_uri
= g_strdup(focus_uri
);
1083 ctx
->msg
= msg
? sipmsg_copy(msg
) : NULL
;
1084 ctx
->accept_cb
= accept_cb
;
1085 ctx
->decline_cb
= decline_cb
;
1086 ctx
->user_data
= user_data
;
1087 ctx
->ask_ctx
= sipe_user_ask(sipe_private
, question_str
,
1088 _("Accept"), accept_invitation_cb
,
1089 _("Decline"), decline_invitation_cb
,
1092 g_free(question_str
);
1096 ask_accept_voice_conference(struct sipe_core_private
*sipe_private
,
1097 const gchar
*focus_uri
,
1099 SipeUserAskCb accept_cb
,
1100 SipeUserAskCb decline_cb
)
1103 const gchar
*novv_note
;
1108 novv_note
= _("\n\nAs this client was not compiled with voice call "
1109 "support, if you accept, you will be able to contact "
1110 "the other participants only via IM session.");
1113 question
= g_strdup_printf(_("wants to invite you "
1114 "to a conference call%s"), novv_note
);
1116 ask_accept_invitation(sipe_private
, focus_uri
, question
, msg
,
1117 accept_cb
, decline_cb
, NULL
);
1123 process_incoming_invite_conf(struct sipe_core_private
*sipe_private
,
1126 sipe_xml
*xn_conferencing
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1127 const sipe_xml
*xn_focus_uri
= sipe_xml_child(xn_conferencing
, "focus-uri");
1128 const sipe_xml
*xn_audio
= sipe_xml_child(xn_conferencing
, "audio");
1129 gchar
*focus_uri
= sipe_xml_data(xn_focus_uri
);
1130 gboolean audio
= sipe_strequal(sipe_xml_attribute(xn_audio
, "available"), "true");
1132 sipe_xml_free(xn_conferencing
);
1134 SIPE_DEBUG_INFO("We have received invitation to Conference. Focus URI=%s", focus_uri
);
1137 sip_transport_response(sipe_private
, msg
, 180, "Ringing", NULL
);
1138 ask_accept_voice_conference(sipe_private
, focus_uri
, msg
,
1139 (SipeUserAskCb
) conf_accept_cb
,
1140 (SipeUserAskCb
) conf_decline_cb
);
1143 accept_incoming_invite_conf(sipe_private
, focus_uri
, FALSE
, msg
);
1152 process_conference_av_endpoint(const sipe_xml
*endpoint
,
1153 const gchar
*user_uri
,
1154 const gchar
*self_uri
,
1155 struct sip_session
*session
)
1157 const sipe_xml
*media
;
1158 const gchar
*new_entity
;
1160 if (!sipe_strequal(user_uri
, self_uri
)) {
1161 /* We are interested only in our own endpoint data. */
1165 new_entity
= sipe_xml_attribute(endpoint
, "entity");
1166 if (!sipe_strequal(session
->audio_video_entity
, new_entity
)) {
1167 g_free(session
->audio_video_entity
);
1168 session
->audio_video_entity
= g_strdup(new_entity
);
1171 session
->audio_media_id
= 0;
1173 media
= sipe_xml_child(endpoint
, "media");
1174 for (; media
; media
= sipe_xml_twin(media
)) {
1175 gchar
*type
= sipe_xml_data(sipe_xml_child(media
, "type"));
1177 if (sipe_strequal(type
, "audio")) {
1178 session
->audio_media_id
=
1179 sipe_xml_int_attribute(media
, "id", 0);
1184 if (session
->audio_media_id
!= 0) {
1191 call_accept_cb(struct sipe_core_private
*sipe_private
, struct conf_accept_ctx
*ctx
)
1193 struct sip_session
*session
;
1194 session
= sipe_session_find_conference(sipe_private
, ctx
->focus_uri
);
1197 sipe_core_media_connect_conference(SIPE_CORE_PUBLIC
,
1198 session
->chat_session
);
1202 #if defined(HAVE_XDATA) && defined(HAVE_GIO)
1204 sipe_core_conf_is_viewing_appshare(struct sipe_core_public
*sipe_public
,
1205 struct sipe_chat_session
*chat_session
)
1210 mcu_uri
= sipe_conf_build_uri(chat_session
->id
, "applicationsharing");
1211 calls
= g_hash_table_get_values(SIPE_CORE_PRIVATE
->media_calls
);
1213 for (; calls
; calls
= g_list_delete_link(calls
, calls
)) {
1214 struct sipe_media_call
*call
= calls
->data
;
1215 if (sipe_strequal(call
->with
, mcu_uri
)) {
1222 if (calls
!= NULL
) {
1229 #endif // defined(HAVE_XDATA) && defined(HAVE_GIO)
1233 sipe_process_conference(struct sipe_core_private
*sipe_private
,
1236 sipe_xml
*xn_conference_info
;
1237 const sipe_xml
*node
;
1238 const sipe_xml
*xn_subject
;
1239 const gchar
*focus_uri
;
1240 struct sip_session
*session
;
1241 gboolean just_joined
= FALSE
;
1243 gboolean audio_was_added
= FALSE
;
1244 #if defined(HAVE_XDATA) && defined(HAVE_GIO)
1245 gboolean presentation_was_added
= FALSE
;
1246 #endif // defined(HAVE_XDATA) && defined(HAVE_GIO)
1249 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1251 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !sipe_strequal(sipmsg_find_header(msg
, "Event"), "conference")) return;
1253 xn_conference_info
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1254 if (!xn_conference_info
) return;
1256 focus_uri
= sipe_xml_attribute(xn_conference_info
, "entity");
1257 session
= sipe_session_find_conference(sipe_private
, focus_uri
);
1260 SIPE_DEBUG_INFO("sipe_process_conference: unable to find conf session with focus=%s", focus_uri
);
1264 if (!session
->chat_session
->backend
) {
1265 gchar
*self
= sip_uri_self(sipe_private
);
1268 session
->chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
1269 session
->chat_session
,
1270 session
->chat_session
->title
,
1273 /* @TODO ask for full state (re-subscribe) if it was a partial one -
1274 * this is to obtain full list of conference participants.
1280 if ((xn_subject
= sipe_xml_child(xn_conference_info
, "conference-description/subject"))) {
1281 g_free(session
->subject
);
1282 session
->subject
= sipe_xml_data(xn_subject
);
1283 sipe_backend_chat_topic(session
->chat_session
->backend
, session
->subject
);
1284 SIPE_DEBUG_INFO("sipe_process_conference: subject=%s", session
->subject
? session
->subject
: "");
1288 if (!session
->im_mcu_uri
) {
1289 for (node
= sipe_xml_child(xn_conference_info
, "conference-description/conf-uris/entry");
1291 node
= sipe_xml_twin(node
))
1293 gchar
*purpose
= sipe_xml_data(sipe_xml_child(node
, "purpose"));
1295 if (sipe_strequal("chat", purpose
)) {
1297 session
->im_mcu_uri
= sipe_xml_data(sipe_xml_child(node
, "uri"));
1298 SIPE_DEBUG_INFO("sipe_process_conference: im_mcu_uri=%s", session
->im_mcu_uri
);
1306 if (!session
->chat_session
->organizer
) {
1307 node
= sipe_xml_child(xn_conference_info
, "conference-description/organizer/display-name");
1309 session
->chat_session
->organizer
= sipe_xml_data(node
);
1314 if (!session
->chat_session
->join_url
) {
1315 node
= sipe_xml_child(xn_conference_info
, "conference-description/join-url");
1317 session
->chat_session
->join_url
= sipe_xml_data(node
);
1321 /* dial-in conference id */
1322 if (!session
->chat_session
->dial_in_conf_id
) {
1323 node
= sipe_xml_child(xn_conference_info
, "conference-description/pstn-access/id");
1325 session
->chat_session
->dial_in_conf_id
= sipe_xml_data(node
);
1330 for (node
= sipe_xml_child(xn_conference_info
, "users/user"); node
; node
= sipe_xml_twin(node
)) {
1331 const gchar
*user_uri
= sipe_xml_attribute(node
, "entity");
1332 const gchar
*state
= sipe_xml_attribute(node
, "state");
1333 gchar
*role
= sipe_xml_data(sipe_xml_child(node
, "roles/entry"));
1334 gboolean is_operator
= sipe_strequal(role
, "presenter");
1335 gboolean is_in_im_mcu
= FALSE
;
1336 gchar
*self
= sip_uri_self(sipe_private
);
1338 if (sipe_strequal("deleted", state
)) {
1339 if (sipe_backend_chat_find(session
->chat_session
->backend
, user_uri
)) {
1340 sipe_backend_chat_remove(session
->chat_session
->backend
,
1345 const sipe_xml
*endpoint
;
1346 for (endpoint
= sipe_xml_child(node
, "endpoint"); endpoint
; endpoint
= sipe_xml_twin(endpoint
)) {
1347 const gchar
*session_type
;
1348 gchar
*status
= sipe_xml_data(sipe_xml_child(endpoint
, "status"));
1349 gboolean connected
= sipe_strequal("connected", status
);
1355 session_type
= sipe_xml_attribute(endpoint
, "session-type");
1357 if (sipe_strequal("chat", session_type
)) {
1358 is_in_im_mcu
= TRUE
;
1359 if (!sipe_backend_chat_find(session
->chat_session
->backend
, user_uri
)) {
1360 sipe_backend_chat_add(session
->chat_session
->backend
,
1362 !just_joined
&& g_ascii_strcasecmp(user_uri
, self
));
1365 sipe_backend_chat_operator(session
->chat_session
->backend
,
1368 } else if (sipe_strequal("audio-video", session_type
)) {
1370 if (!session
->is_call
)
1371 audio_was_added
= TRUE
;
1372 process_conference_av_endpoint(endpoint
,
1377 } else if (sipe_strequal("applicationsharing", session_type
)) {
1378 #if defined(HAVE_XDATA) && defined(HAVE_GIO)
1379 if (!sipe_core_conf_is_viewing_appshare(SIPE_CORE_PUBLIC
,
1380 session
->chat_session
)) {
1384 media_state
= sipe_xml_data(sipe_xml_child(endpoint
, "media/media-state"));
1385 status
= sipe_xml_data(sipe_xml_child(endpoint
, "media/status"));
1387 if (sipe_strequal(media_state
, "connected") &&
1388 sipe_strequal(status
, "sendonly")) {
1389 presentation_was_added
= TRUE
;
1391 g_free(media_state
);
1394 #endif // defined(HAVE_XDATA) && defined(HAVE_GIO)
1397 if (!is_in_im_mcu
) {
1398 if (sipe_backend_chat_find(session
->chat_session
->backend
, user_uri
)) {
1399 sipe_backend_chat_remove(session
->chat_session
->backend
,
1409 if (audio_was_added
) {
1410 session
->is_call
= TRUE
;
1411 ask_accept_voice_conference(sipe_private
, focus_uri
, NULL
,
1412 (SipeUserAskCb
) call_accept_cb
,
1415 #if defined(HAVE_XDATA) && defined(HAVE_GIO)
1416 if (presentation_was_added
) {
1417 sipe_appshare_connect_conference(sipe_private
, session
->chat_session
,
1420 #endif // defined(HAVE_XDATA) && defined(HAVE_GIO)
1423 /* entity-view, locked */
1424 for (node
= sipe_xml_child(xn_conference_info
, "conference-view/entity-view");
1426 node
= sipe_xml_twin(node
)) {
1428 const sipe_xml
*xn_type
= sipe_xml_child(node
, "entity-state/media/entry/type");
1430 if (xn_type
&& sipe_strequal("chat", (tmp
= sipe_xml_data(xn_type
)))) {
1431 const sipe_xml
*xn_locked
= sipe_xml_child(node
, "entity-state/locked");
1433 gchar
*locked
= sipe_xml_data(xn_locked
);
1434 gboolean prev_locked
= session
->locked
;
1435 session
->locked
= sipe_strequal(locked
, "true");
1436 if (prev_locked
&& !session
->locked
) {
1437 sipe_user_present_info(sipe_private
, session
,
1438 _("This conference is no longer locked. Additional participants can now join."));
1440 if (!prev_locked
&& session
->locked
) {
1441 sipe_user_present_info(sipe_private
, session
,
1442 _("This conference is locked. Nobody else can join the conference while it is locked."));
1445 SIPE_DEBUG_INFO("sipe_process_conference: session->locked=%s",
1446 session
->locked
? "TRUE" : "FALSE");
1452 sipe_xml_free(xn_conference_info
);
1454 if (session
->im_mcu_uri
) {
1455 struct sip_dialog
*dialog
= sipe_dialog_find(session
, session
->im_mcu_uri
);
1457 dialog
= sipe_dialog_add(session
);
1459 dialog
->callid
= g_strdup(session
->callid
);
1460 dialog
->with
= g_strdup(session
->im_mcu_uri
);
1462 /* send INVITE to IM MCU */
1463 sipe_im_invite(sipe_private
, session
, dialog
->with
, NULL
, NULL
, NULL
, FALSE
);
1467 sipe_process_pending_invite_queue(sipe_private
, session
);
1471 sipe_conf_immcu_closed(struct sipe_core_private
*sipe_private
,
1472 struct sip_session
*session
)
1474 sipe_user_present_info(sipe_private
, session
,
1475 _("You have been disconnected from this conference."));
1476 sipe_backend_chat_close(session
->chat_session
->backend
);
1480 conf_session_close(struct sipe_core_private
*sipe_private
,
1481 struct sip_session
*session
)
1484 /* unsubscribe from focus */
1485 sipe_subscribe_conference(sipe_private
,
1486 session
->chat_session
->id
, TRUE
);
1488 if (session
->focus_dialog
) {
1489 /* send BYE to focus */
1490 sip_transport_bye(sipe_private
, session
->focus_dialog
);
1496 sipe_process_imdn(struct sipe_core_private
*sipe_private
,
1499 gchar
*with
= parse_from(sipmsg_find_header(msg
, "From"));
1500 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
1501 static struct sip_session
*session
;
1503 const sipe_xml
*node
;
1507 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, with
);
1509 SIPE_DEBUG_INFO("sipe_process_imdn: unable to find conf session with callid=%s", callid
);
1514 xn_imdn
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1515 message_id
= sipe_xml_data(sipe_xml_child(xn_imdn
, "message-id"));
1517 message
= g_hash_table_lookup(session
->conf_unconfirmed_messages
, message_id
);
1520 for (node
= sipe_xml_child(xn_imdn
, "recipient"); node
; node
= sipe_xml_twin(node
)) {
1521 gchar
*tmp
= parse_from(sipe_xml_attribute(node
, "uri"));
1522 gchar
*uri
= parse_from(tmp
);
1523 gchar
*status
= sipe_xml_data(sipe_xml_child(node
, "status"));
1524 guint error
= status
? g_ascii_strtoull(status
, NULL
, 10) : 0;
1525 /* default to error if missing or conversion failed */
1526 if ((error
== 0) || (error
>= 300))
1527 sipe_user_present_message_undelivered(sipe_private
,
1538 sipe_xml_free(xn_imdn
);
1540 g_hash_table_remove(session
->conf_unconfirmed_messages
, message_id
);
1541 SIPE_DEBUG_INFO("sipe_process_imdn: removed message %s from conf_unconfirmed_messages(count=%d)",
1542 message_id
, g_hash_table_size(session
->conf_unconfirmed_messages
));
1547 void sipe_core_conf_make_leader(struct sipe_core_public
*sipe_public
,
1549 const gchar
*buddy_name
)
1551 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1552 struct sipe_chat_session
*chat_session
= parameter
;
1553 struct sip_session
*session
;
1555 SIPE_DEBUG_INFO("sipe_core_conf_make_leader: chat_title=%s",
1556 chat_session
->title
);
1558 session
= sipe_session_find_chat(sipe_private
, chat_session
);
1559 sipe_conf_modify_user_role(sipe_private
, session
, buddy_name
);
1562 void sipe_core_conf_remove_from(struct sipe_core_public
*sipe_public
,
1564 const gchar
*buddy_name
)
1566 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1567 struct sipe_chat_session
*chat_session
= parameter
;
1568 struct sip_session
*session
;
1570 SIPE_DEBUG_INFO("sipe_core_conf_remove_from: chat_title=%s",
1571 chat_session
->title
);
1573 session
= sipe_session_find_chat(sipe_private
, chat_session
);
1574 sipe_conf_delete_user(sipe_private
, session
, buddy_name
);
1578 sipe_conf_build_uri(const gchar
*focus_uri
, const gchar
*session_type
)
1580 gchar
**parts
= g_strsplit(focus_uri
, ":focus:", 2);
1581 gchar
*result
= NULL
;
1583 if (g_strv_length(parts
) == 2) {
1584 result
= g_strconcat(parts
[0], ":", session_type
, ":", parts
[1],
1593 access_numbers_info(struct sipe_core_public
*sipe_public
)
1595 GString
*result
= g_string_new("");
1597 #if GLIB_CHECK_VERSION(2,16,0)
1598 GList
*keys
= g_hash_table_get_keys(SIPE_CORE_PRIVATE
->access_numbers
);
1599 keys
= g_list_sort(keys
, (GCompareFunc
)g_strcmp0
);
1601 for (; keys
; keys
= g_list_delete_link(keys
, keys
)) {
1603 value
= g_hash_table_lookup(SIPE_CORE_PRIVATE
->access_numbers
,
1606 g_string_append(result
, keys
->data
);
1607 g_string_append(result
, " ");
1608 g_string_append(result
, value
);
1609 g_string_append(result
, "<br/>");
1612 (void)sipe_public
; /* keep compiler happy */
1615 return g_string_free(result
, FALSE
);
1619 sipe_core_conf_entry_info(struct sipe_core_public
*sipe_public
,
1620 struct sipe_chat_session
*chat_session
)
1622 gchar
*access_info
= access_numbers_info(sipe_public
);
1623 gchar
*result
= g_strdup_printf(
1624 "<b><font size=\"+1\">%s</font></b><br/>"
1625 "<b>%s:</b> %s<br/>"
1626 "<b>%s:</b> %s<br/>"
1631 "<b>%s:</b> %s<br/>"
1633 "<b><font size=\"+1\">%s</font></b><br/>"
1637 SIPE_CORE_PRIVATE
->default_access_number
? SIPE_CORE_PRIVATE
->default_access_number
: "",
1639 chat_session
->dial_in_conf_id
? chat_session
->dial_in_conf_id
: "",
1641 chat_session
->join_url
? chat_session
->join_url
: "",
1643 chat_session
->organizer
? chat_session
->organizer
: "",
1644 _("Alternative dial-in numbers"),
1647 g_free(access_info
);