2 * @file sipe-groupchat.c
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
24 * This module implements the OCS2007R2 Group Chat functionality
26 * Documentation references:
28 * Microsoft TechNet: Key Protocols and Windows Services Used by Group Chat
29 * <http://technet.microsoft.com/en-us/library/ee323484%28office.13%29.aspx>
30 * Microsoft TechNet: Group Chat Call Flows
31 * <http://technet.microsoft.com/en-us/library/ee323524%28office.13%29.aspx>
32 * Microsoft Office Communications Server 2007 R2 Technical Reference Guide
33 * <http://go.microsoft.com/fwlink/?LinkID=159649>
34 * Microsoft DevNet: [MS-XCCOSIP] Extensible Chat Control Over SIP
35 * <http://msdn.microsoft.com/en-us/library/hh624112.aspx>
36 * RFC 4028: Session Timers in the Session Initiation Protocol (SIP)
37 * <http://www.rfc-editor.org/rfc/rfc4028.txt>
43 * <sib domain="<DOMAIN>" infoType="123" />
46 * serverTime="2010-09-14T14:26:17.6206356Z"
48 * messageSizeLimit="512"
49 * storySizeLimit="4096"
50 * rootUri="ma-cat://<DOMAIN>/<GUID>"
51 * dbVersion="3ea3a5a8-ef36-46cf-898f-7a5133931d63"
54 * is there any information in there we would need/use?
56 * - cmd:getpref/rpl:getpref/cmd:setpref/rpl:setpref
57 * probably useless, as libpurple stores configuration locally
59 * can store base64 encoded "free text" in key/value fashion
60 * <cmd id="cmd:getpref" seqid="x">
62 * <pref label="kedzie.GroupChannels"
64 * createdefault="true" />
67 * <cmd id="cmd:setpref" seqid="x">
69 * <pref label="kedzie.GroupChannels"
71 * createdefault="false"
72 * content="<BASE64 text>" />
76 * use this to sync chats in buddy list on multiple clients?
79 * <inv inviteId="1" domain="<DOMAIN>" />
83 * according to documentation should provide list of outstanding invites.
84 * [no log file examples]
85 * should we automatically join those channels or ask user to join/add?
87 * - chatserver_command_message()
88 * needs to support multiple <grpchat> nodes?
89 * [no log file examples]
91 * - create/delete chat rooms
92 * [no log file examples]
93 * are these related to this functionality?
95 * <cmd id="cmd:nodespermcreatechild" seqid="1">
98 * <rpl id="rpl:nodespermcreatechild" seqid="1">
99 * <commandid seqid="1" envid="xxx" />
100 * <resp code="200">SUCCESS_OK</resp>
104 * - file transfer (uses HTTPS PUT/GET via a filestore server)
105 * [no log file examples]
118 #include "sipe-common.h"
120 #include "sip-transport.h"
121 #include "sipe-backend.h"
122 #include "sipe-chat.h"
123 #include "sipe-core.h"
124 #include "sipe-core-private.h"
125 #include "sipe-dialog.h"
126 #include "sipe-groupchat.h"
128 #include "sipe-nls.h"
129 #include "sipe-schedule.h"
130 #include "sipe-session.h"
131 #include "sipe-utils.h"
132 #include "sipe-xml.h"
134 #define GROUPCHAT_RETRY_TIMEOUT 5*60 /* seconds */
137 * aib node - magic numbers?
140 * <aib key="3984" value="0,1,2,3,4,5,7,9,10,12,13,14,15,16,17" />
141 * <aib key="12276" value="6,8,11" />
143 * "value" corresponds to the "id" attribute in uib nodes.
145 * @TODO: Confirm "guessed" meaning of the magic numbers:
146 * 3984 = normal users
147 * 12276 = channel operators
149 #define GROUPCHAT_AIB_KEY_USER "3984"
150 #define GROUPCHAT_AIB_KEY_CHANOP "12276"
152 struct sipe_groupchat
{
153 struct sip_session
*session
;
156 GHashTable
*uri_to_chat_session
;
163 struct sipe_groupchat_msg
{
164 GHashTable
*container
;
165 struct sipe_chat_session
*session
;
172 static void sipe_groupchat_msg_free(gpointer data
) {
173 struct sipe_groupchat_msg
*msg
= data
;
174 g_free(msg
->content
);
180 static void sipe_groupchat_msg_remove(gpointer data
) {
181 struct sipe_groupchat_msg
*msg
= data
;
182 g_hash_table_remove(msg
->container
, &msg
->envid
);
185 static void sipe_groupchat_allocate(struct sipe_core_private
*sipe_private
)
187 struct sipe_groupchat
*groupchat
= g_new0(struct sipe_groupchat
, 1);
189 groupchat
->uri_to_chat_session
= g_hash_table_new(g_str_hash
, g_str_equal
);
190 groupchat
->msgs
= g_hash_table_new_full(g_int_hash
, g_int_equal
,
192 sipe_groupchat_msg_free
);
193 groupchat
->envid
= rand();
194 groupchat
->connected
= FALSE
;
195 sipe_private
->groupchat
= groupchat
;
198 static void sipe_groupchat_free_join_queue(struct sipe_groupchat
*groupchat
)
200 sipe_utils_slist_free_full(groupchat
->join_queue
, g_free
);
201 groupchat
->join_queue
= NULL
;
204 void sipe_groupchat_free(struct sipe_core_private
*sipe_private
)
206 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
208 sipe_groupchat_free_join_queue(groupchat
);
209 g_hash_table_destroy(groupchat
->msgs
);
210 g_hash_table_destroy(groupchat
->uri_to_chat_session
);
211 g_free(groupchat
->domain
);
213 sipe_private
->groupchat
= NULL
;
217 static struct sipe_groupchat_msg
*generate_xccos_message(struct sipe_groupchat
*groupchat
,
218 const gchar
*content
)
220 struct sipe_groupchat_msg
*msg
= g_new0(struct sipe_groupchat_msg
, 1);
222 msg
->container
= groupchat
->msgs
;
223 msg
->envid
= groupchat
->envid
++;
224 msg
->xccos
= g_strdup_printf("<xccos ver=\"1\" envid=\"%u\" xmlns=\"urn:parlano:xml:ns:xccos\">"
230 g_hash_table_insert(groupchat
->msgs
, &msg
->envid
, msg
);
236 * Create short-lived dialog with ocschat@<domain> (or user specified value)
237 * This initiates the Group Chat feature
239 void sipe_groupchat_init(struct sipe_core_private
*sipe_private
)
241 const gchar
*setting
= sipe_backend_setting(SIPE_CORE_PUBLIC
,
242 SIPE_SETTING_GROUPCHAT_USER
);
243 const gchar
*persistent
= sipe_private
->persistentChatPool_uri
;
244 gboolean user_set
= !is_empty(setting
);
245 gboolean provisioned
= !is_empty(persistent
);
246 gchar
**parts
= g_strsplit(user_set
? setting
:
247 provisioned
? persistent
:
248 sipe_private
->username
, "@", 2);
249 gboolean domain_found
= !is_empty(parts
[1]);
250 const gchar
*user
= "ocschat";
251 const gchar
*domain
= parts
[domain_found
? 1 : 0];
253 struct sip_session
*session
;
254 struct sipe_groupchat
*groupchat
;
256 /* User specified or provisioned URI is valid 'user@company.com' */
257 if ((user_set
|| provisioned
) && domain_found
&& !is_empty(parts
[0]))
260 SIPE_DEBUG_INFO("sipe_groupchat_init: username '%s' setting '%s' persistent '%s' split '%s'/'%s' GC user %s@%s",
261 sipe_private
->username
, setting
? setting
: "(null)",
262 persistent
? persistent
: "(null)",
263 parts
[0], parts
[1] ? parts
[1] : "(null)", user
, domain
);
265 if (!sipe_private
->groupchat
)
266 sipe_groupchat_allocate(sipe_private
);
267 groupchat
= sipe_private
->groupchat
;
269 chat_uri
= g_strdup_printf("sip:%s@%s", user
, domain
);
270 session
= sipe_session_find_or_add_im(sipe_private
,
272 session
->is_groupchat
= TRUE
;
273 sipe_im_invite(sipe_private
, session
, chat_uri
,
274 NULL
, NULL
, NULL
, FALSE
);
276 g_free(groupchat
->domain
);
277 groupchat
->domain
= g_strdup(domain
);
283 /* sipe_schedule_action */
284 static void groupchat_init_retry_cb(struct sipe_core_private
*sipe_private
,
285 SIPE_UNUSED_PARAMETER gpointer data
)
287 sipe_groupchat_init(sipe_private
);
290 static void groupchat_init_retry(struct sipe_core_private
*sipe_private
)
292 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
294 SIPE_DEBUG_INFO_NOFORMAT("groupchat_init_retry: trying again later...");
296 groupchat
->session
= NULL
;
297 groupchat
->connected
= FALSE
;
299 sipe_schedule_seconds(sipe_private
,
300 "<+groupchat-retry>",
302 GROUPCHAT_RETRY_TIMEOUT
,
303 groupchat_init_retry_cb
,
307 void sipe_groupchat_invite_failed(struct sipe_core_private
*sipe_private
,
308 struct sip_session
*session
)
310 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
311 const gchar
*setting
= sipe_backend_setting(SIPE_CORE_PUBLIC
,
312 SIPE_SETTING_GROUPCHAT_USER
);
313 gboolean retry
= FALSE
;
315 if (groupchat
->session
) {
316 /* response to group chat server invite */
317 SIPE_DEBUG_ERROR_NOFORMAT("can't connect to group chat server!");
319 /* group chat server exists, but communication failed */
322 /* response to initial invite */
323 SIPE_DEBUG_INFO_NOFORMAT("no group chat server found.");
326 sipe_session_close(sipe_private
, session
);
328 if (!is_empty(setting
)) {
329 gchar
*msg
= g_strdup_printf(_("Group Chat Proxy setting is incorrect:\n\n\t%s\n\nPlease update your Account."),
331 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
332 _("Couldn't find Group Chat server!"),
336 /* user specified group chat settings: we should retry */
341 groupchat_init_retry(sipe_private
);
343 SIPE_DEBUG_INFO_NOFORMAT("disabling group chat feature.");
347 static gchar
*generate_chanid_node(const gchar
*uri
, guint key
)
349 /* ma-chan://<domain>/<value> */
350 gchar
**parts
= g_strsplit(uri
, "/", 4);
351 gchar
*chanid
= NULL
;
353 if (parts
[2] && parts
[3]) {
354 chanid
= g_strdup_printf("<chanid key=\"%d\" domain=\"%s\" value=\"%s\"/>",
355 key
, parts
[2], parts
[3]);
357 SIPE_DEBUG_ERROR("generate_chanid_node: mal-formed URI '%s'",
366 static void groupchat_update_cb(struct sipe_core_private
*sipe_private
,
368 static gboolean
groupchat_expired_session_response(struct sipe_core_private
*sipe_private
,
370 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
372 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
374 /* 481 Call Leg Does Not Exist -> server dropped session */
375 if (msg
->response
== 481) {
376 struct sip_session
*session
= groupchat
->session
;
377 struct sip_dialog
*dialog
= sipe_dialog_find(session
,
381 /* close dialog from our side */
382 sip_transport_bye(sipe_private
, dialog
);
383 sipe_dialog_remove(session
, session
->with
);
384 /* dialog is no longer valid */
387 /* re-initialize groupchat session */
388 groupchat
->session
= NULL
;
389 groupchat
->connected
= FALSE
;
390 sipe_groupchat_init(sipe_private
);
392 sipe_schedule_seconds(sipe_private
,
393 "<+groupchat-expires>",
403 /* sipe_schedule_action */
404 static void groupchat_update_cb(struct sipe_core_private
*sipe_private
,
405 SIPE_UNUSED_PARAMETER gpointer data
)
407 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
409 if (groupchat
->session
) {
410 struct sip_dialog
*dialog
= sipe_dialog_find(groupchat
->session
,
411 groupchat
->session
->with
);
414 sip_transport_update(sipe_private
,
416 groupchat_expired_session_response
);
420 static struct sipe_groupchat_msg
*chatserver_command(struct sipe_core_private
*sipe_private
,
423 void sipe_groupchat_invite_response(struct sipe_core_private
*sipe_private
,
424 struct sip_dialog
*dialog
,
425 struct sipmsg
*response
)
427 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
429 SIPE_DEBUG_INFO_NOFORMAT("sipe_groupchat_invite_response");
431 if (!groupchat
->session
) {
432 /* response to initial invite */
433 struct sipe_groupchat_msg
*msg
= generate_xccos_message(groupchat
,
434 "<cmd id=\"cmd:requri\" seqid=\"1\"><data/></cmd>");
435 const gchar
*session_expires
= sipmsg_find_header(response
,
438 sip_transport_info(sipe_private
,
439 "Content-Type: text/plain\r\n",
443 sipe_groupchat_msg_remove(msg
);
445 if (session_expires
) {
446 groupchat
->expires
= strtoul(session_expires
, NULL
, 10);
448 if (groupchat
->expires
) {
449 SIPE_DEBUG_INFO("sipe_groupchat_invite_response: session expires in %d seconds",
452 if (groupchat
->expires
> 10)
453 groupchat
->expires
-= 10;
454 sipe_schedule_seconds(sipe_private
,
455 "<+groupchat-expires>",
464 /* response to group chat server invite */
467 SIPE_DEBUG_INFO_NOFORMAT("connection to group chat server established.");
469 groupchat
->connected
= TRUE
;
471 /* Any queued joins? */
472 if (groupchat
->join_queue
) {
473 GString
*cmd
= g_string_new("<cmd id=\"cmd:bjoin\" seqid=\"1\">"
478 /* We used g_slist_prepend() to create the list */
479 groupchat
->join_queue
= entry
= g_slist_reverse(groupchat
->join_queue
);
481 gchar
*chanid
= generate_chanid_node(entry
->data
, i
++);
482 g_string_append(cmd
, chanid
);
486 sipe_groupchat_free_join_queue(groupchat
);
488 g_string_append(cmd
, "</data></cmd>");
489 chatserver_command(sipe_private
, cmd
->str
);
490 g_string_free(cmd
, TRUE
);
493 /* Request outstanding invites from server */
494 invcmd
= g_strdup_printf("<cmd id=\"cmd:getinv\" seqid=\"1\">"
496 "<inv inviteId=\"1\" domain=\"%s\"/>"
498 "</cmd>", groupchat
->domain
);
499 chatserver_command(sipe_private
, invcmd
);
504 static void chatserver_command_error_notify(struct sipe_core_private
*sipe_private
,
505 struct sipe_chat_session
*chat_session
,
506 const gchar
*content
)
508 gchar
*label
= g_strdup_printf(_("This message was not delivered to chat room '%s'"),
509 chat_session
->title
);
510 gchar
*errmsg
= g_strdup_printf("%s:\n<font color=\"#888888\"></b>%s<b></font>",
513 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC
,
514 chat_session
->backend
,
521 static gboolean
chatserver_command_response(struct sipe_core_private
*sipe_private
,
523 struct transaction
*trans
)
525 if (msg
->response
!= 200) {
526 struct sipe_groupchat_msg
*gmsg
= trans
->payload
->data
;
527 struct sipe_chat_session
*chat_session
= gmsg
->session
;
529 SIPE_DEBUG_INFO("chatserver_command_response: failure %d", msg
->response
);
532 chatserver_command_error_notify(sipe_private
,
536 groupchat_expired_session_response(sipe_private
, msg
, trans
);
541 static struct sipe_groupchat_msg
*chatserver_command(struct sipe_core_private
*sipe_private
,
544 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
545 struct sipe_groupchat_msg
*msg
= NULL
;
547 if (groupchat
->session
) {
548 struct sip_dialog
*dialog
= sipe_dialog_find(groupchat
->session
,
549 groupchat
->session
->with
);
552 struct transaction
*trans
;
554 msg
= generate_xccos_message(groupchat
, cmd
);
555 trans
= sip_transport_info(sipe_private
,
556 "Content-Type: text/plain\r\n",
559 chatserver_command_response
);
562 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
564 payload
->destroy
= sipe_groupchat_msg_remove
;
566 trans
->payload
= payload
;
568 /* SIP transport is no longer valid - give up */
569 sipe_groupchat_msg_remove(msg
);
578 static void chatserver_response_uri(struct sipe_core_private
*sipe_private
,
579 struct sip_session
*session
,
580 SIPE_UNUSED_PARAMETER guint result
,
581 SIPE_UNUSED_PARAMETER
const gchar
*message
,
584 const sipe_xml
*uib
= sipe_xml_child(xml
, "uib");
585 const gchar
*uri
= sipe_xml_attribute(uib
, "uri");
587 /* drop connection to ocschat@<domain> again */
588 sipe_session_close(sipe_private
, session
);
591 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
593 SIPE_DEBUG_INFO("chatserver_response_uri: '%s'", uri
);
595 groupchat
->session
= session
= sipe_session_find_or_add_im(sipe_private
,
598 session
->is_groupchat
= TRUE
;
599 sipe_im_invite(sipe_private
, session
, uri
, NULL
, NULL
, NULL
, FALSE
);
601 SIPE_DEBUG_WARNING_NOFORMAT("chatserver_response_uri: no server URI found!");
602 groupchat_init_retry(sipe_private
);
606 static void chatserver_response_channel_search(struct sipe_core_private
*sipe_private
,
607 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
609 const gchar
*message
,
612 struct sipe_core_public
*sipe_public
= SIPE_CORE_PUBLIC
;
615 sipe_backend_notify_error(sipe_public
,
616 _("Error retrieving room list"),
619 const sipe_xml
*chanib
;
621 for (chanib
= sipe_xml_child(xml
, "chanib");
623 chanib
= sipe_xml_twin(chanib
)) {
624 const gchar
*name
= sipe_xml_attribute(chanib
, "name");
625 const gchar
*desc
= sipe_xml_attribute(chanib
, "description");
626 const gchar
*uri
= sipe_xml_attribute(chanib
, "uri");
627 const sipe_xml
*node
;
628 guint user_count
= 0;
632 for (node
= sipe_xml_child(chanib
, "info");
634 node
= sipe_xml_twin(node
)) {
635 const gchar
*id
= sipe_xml_attribute(node
, "id");
640 data
= sipe_xml_data(node
);
642 if (sipe_strcase_equal(id
, "urn:parlano:ma:info:ucnt")) {
643 user_count
= g_ascii_strtoll(data
, NULL
, 10);
644 } else if (sipe_strcase_equal(id
, "urn:parlano:ma:info:visibilty")) {
645 if (sipe_strcase_equal(data
, "private")) {
646 flags
|= SIPE_GROUPCHAT_ROOM_PRIVATE
;
654 for (node
= sipe_xml_child(chanib
, "prop");
656 node
= sipe_xml_twin(node
)) {
657 const gchar
*id
= sipe_xml_attribute(node
, "id");
662 data
= sipe_xml_data(node
);
664 gboolean value
= sipe_strcase_equal(data
, "true");
669 if (sipe_strcase_equal(id
, "urn:parlano:ma:prop:filepost")) {
670 add
= SIPE_GROUPCHAT_ROOM_FILEPOST
;
671 } else if (sipe_strcase_equal(id
, "urn:parlano:ma:prop:invite")) {
672 add
= SIPE_GROUPCHAT_ROOM_INVITE
;
673 } else if (sipe_strcase_equal(id
, "urn:parlano:ma:prop:logged")) {
674 add
= SIPE_GROUPCHAT_ROOM_LOGGED
;
681 SIPE_DEBUG_INFO("group chat channel '%s': '%s' (%s) with %u users, flags 0x%x",
682 name
, desc
, uri
, user_count
, flags
);
683 sipe_backend_groupchat_room_add(sipe_public
,
689 sipe_backend_groupchat_room_terminate(sipe_public
);
692 static gboolean
is_chanop(const sipe_xml
*aib
)
694 return sipe_strequal(sipe_xml_attribute(aib
, "key"),
695 GROUPCHAT_AIB_KEY_CHANOP
);
698 static void add_user(struct sipe_chat_session
*chat_session
,
700 gboolean
new, gboolean chanop
)
702 SIPE_DEBUG_INFO("add_user: %s%s%s to room %s (%s)",
704 chanop
? "chanop " : "",
706 chat_session
->title
, chat_session
->id
);
707 sipe_backend_chat_add(chat_session
->backend
, uri
, new);
709 sipe_backend_chat_operator(chat_session
->backend
, uri
);
712 static void chatserver_response_join(struct sipe_core_private
*sipe_private
,
713 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
715 const gchar
*message
,
719 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
720 _("Error joining chat room"),
723 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
724 const sipe_xml
*node
;
725 GHashTable
*user_ids
= g_hash_table_new(g_str_hash
, g_str_equal
);
727 /* Extract user IDs & URIs and generate ID -> URI map */
728 for (node
= sipe_xml_child(xml
, "uib");
730 node
= sipe_xml_twin(node
)) {
731 const gchar
*id
= sipe_xml_attribute(node
, "id");
732 const gchar
*uri
= sipe_xml_attribute(node
, "uri");
734 g_hash_table_insert(user_ids
,
739 /* Process channel data */
740 for (node
= sipe_xml_child(xml
, "chanib");
742 node
= sipe_xml_twin(node
)) {
743 const gchar
*uri
= sipe_xml_attribute(node
, "uri");
746 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
748 gboolean
new = (chat_session
== NULL
);
749 const gchar
*attr
= sipe_xml_attribute(node
, "name");
750 gchar
*self
= sip_uri_self(sipe_private
);
754 chat_session
= sipe_chat_create_session(SIPE_CHAT_TYPE_GROUPCHAT
,
755 sipe_xml_attribute(node
,
758 g_hash_table_insert(groupchat
->uri_to_chat_session
,
762 SIPE_DEBUG_INFO("joined room '%s' (%s)",
765 chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
770 SIPE_DEBUG_INFO("rejoining room '%s' (%s)",
773 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC
,
774 chat_session
->backend
,
776 chat_session
->title
);
780 attr
= sipe_xml_attribute(node
, "topic");
782 sipe_backend_chat_topic(chat_session
->backend
,
786 /* Process user map for channel */
787 for (aib
= sipe_xml_child(node
, "aib");
789 aib
= sipe_xml_twin(aib
)) {
790 const gchar
*value
= sipe_xml_attribute(aib
, "value");
791 gboolean chanop
= is_chanop(aib
);
792 gchar
**ids
= g_strsplit(value
, ",", 0);
798 const gchar
*uri
= g_hash_table_lookup(user_ids
,
801 add_user(chat_session
,
812 /* Request last 25 entries from channel history */
813 self
= g_strdup_printf("<cmd id=\"cmd:bccontext\" seqid=\"1\">"
815 "<chanib uri=\"%s\"/>"
816 "<bcq><last cnt=\"25\"/></bcq>"
818 "</cmd>", chat_session
->id
);
819 chatserver_command(sipe_private
, self
);
824 g_hash_table_destroy(user_ids
);
828 static void chatserver_grpchat_message(struct sipe_core_private
*sipe_private
,
829 const sipe_xml
*grpchat
);
831 static void chatserver_response_history(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
832 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
833 SIPE_UNUSED_PARAMETER guint result
,
834 SIPE_UNUSED_PARAMETER
const gchar
*message
,
837 const sipe_xml
*grpchat
;
839 for (grpchat
= sipe_xml_child(xml
, "chanib/msg");
841 grpchat
= sipe_xml_twin(grpchat
))
842 if (sipe_strequal(sipe_xml_attribute(grpchat
, "id"),
844 chatserver_grpchat_message(sipe_private
, grpchat
);
847 static void chatserver_response_part(struct sipe_core_private
*sipe_private
,
848 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
850 const gchar
*message
,
854 SIPE_DEBUG_WARNING("chatserver_response_part: failed with %d: %s. Dropping room",
857 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
858 const gchar
*uri
= sipe_xml_attribute(sipe_xml_child(xml
, "chanib"),
860 struct sipe_chat_session
*chat_session
;
863 (chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
866 SIPE_DEBUG_INFO("leaving room '%s' (%s)",
867 chat_session
->title
, chat_session
->id
);
869 g_hash_table_remove(groupchat
->uri_to_chat_session
,
871 sipe_chat_remove_session(chat_session
);
874 SIPE_DEBUG_WARNING("chatserver_response_part: unknown chat room uri '%s'",
880 static void chatserver_notice_join(struct sipe_core_private
*sipe_private
,
881 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
882 SIPE_UNUSED_PARAMETER guint result
,
883 SIPE_UNUSED_PARAMETER
const gchar
*message
,
886 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
889 for (uib
= sipe_xml_child(xml
, "uib");
891 uib
= sipe_xml_twin(uib
)) {
892 const gchar
*uri
= sipe_xml_attribute(uib
, "uri");
897 for (aib
= sipe_xml_child(uib
, "aib");
899 aib
= sipe_xml_twin(aib
)) {
900 const gchar
*domain
= sipe_xml_attribute(aib
, "domain");
901 const gchar
*path
= sipe_xml_attribute(aib
, "value");
903 if (domain
&& path
) {
904 gchar
*room_uri
= g_strdup_printf("ma-chan://%s/%s",
906 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
909 add_user(chat_session
,
921 static void chatserver_notice_part(struct sipe_core_private
*sipe_private
,
922 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
923 SIPE_UNUSED_PARAMETER guint result
,
924 SIPE_UNUSED_PARAMETER
const gchar
*message
,
927 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
928 const sipe_xml
*chanib
;
930 for (chanib
= sipe_xml_child(xml
, "chanib");
932 chanib
= sipe_xml_twin(chanib
)) {
933 const gchar
*room_uri
= sipe_xml_attribute(chanib
, "uri");
936 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
942 for (uib
= sipe_xml_child(chanib
, "uib");
944 uib
= sipe_xml_twin(uib
)) {
945 const gchar
*uri
= sipe_xml_attribute(uib
, "uri");
948 SIPE_DEBUG_INFO("remove_user: %s from room %s (%s)",
952 sipe_backend_chat_remove(chat_session
->backend
,
961 static const struct response
{
963 void (* const handler
)(struct sipe_core_private
*,
964 struct sip_session
*,
965 guint result
, const gchar
*,
966 const sipe_xml
*xml
);
967 } response_table
[] = {
968 { "rpl:requri", chatserver_response_uri
},
969 { "rpl:chansrch", chatserver_response_channel_search
},
970 { "rpl:join", chatserver_response_join
},
971 { "rpl:bjoin", chatserver_response_join
},
972 { "rpl:bccontext", chatserver_response_history
},
973 { "rpl:part", chatserver_response_part
},
974 { "ntc:join", chatserver_notice_join
},
975 { "ntc:bjoin", chatserver_notice_join
},
976 { "ntc:part", chatserver_notice_part
},
980 /* Handles rpl:XXX & ntc:YYY */
981 static void chatserver_response(struct sipe_core_private
*sipe_private
,
982 const sipe_xml
*reply
,
983 struct sip_session
*session
)
986 const sipe_xml
*resp
, *data
;
990 const struct response
*r
;
992 id
= sipe_xml_attribute(reply
, "id");
994 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: no reply ID found!");
998 resp
= sipe_xml_child(reply
, "resp");
1000 result
= sipe_xml_int_attribute(resp
, "code", 500);
1001 message
= sipe_xml_data(resp
);
1003 message
= g_strdup("");
1006 data
= sipe_xml_child(reply
, "data");
1008 SIPE_DEBUG_INFO("chatserver_response: '%s' result (%d) %s",
1009 id
, result
, message
? message
: "");
1011 for (r
= response_table
; r
->key
; r
++) {
1012 if (sipe_strcase_equal(id
, r
->key
)) {
1013 (*r
->handler
)(sipe_private
, session
, result
, message
, data
);
1014 /* session can be invalid now */
1020 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: ignoring unknown response");
1024 } while ((reply
= sipe_xml_twin(reply
)) != NULL
);
1027 static void chatserver_grpchat_message(struct sipe_core_private
*sipe_private
,
1028 const sipe_xml
*grpchat
)
1030 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1031 const gchar
*uri
= sipe_xml_attribute(grpchat
, "chanUri");
1032 const gchar
*from
= sipe_xml_attribute(grpchat
, "author");
1033 time_t when
= sipe_utils_str_to_time(sipe_xml_attribute(grpchat
, "ts"));
1034 gchar
*text
= sipe_xml_data(sipe_xml_child(grpchat
, "chat"));
1035 struct sipe_chat_session
*chat_session
;
1038 if (!uri
|| !from
) {
1039 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' received without chat room URI or author!",
1045 chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
1047 if (!chat_session
) {
1048 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' from '%s' received from unknown chat room '%s'!",
1049 text
? text
: "", from
, uri
);
1054 /* libxml2 decodes all entities, but the backend expects HTML */
1055 escaped
= g_markup_escape_text(text
, -1);
1057 sipe_backend_chat_message(SIPE_CORE_PUBLIC
, chat_session
->backend
,
1058 from
, when
, escaped
);
1062 void process_incoming_info_groupchat(struct sipe_core_private
*sipe_private
,
1064 struct sip_session
*session
)
1066 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1067 const sipe_xml
*node
;
1068 const gchar
*callid
;
1069 struct sip_dialog
*dialog
;
1071 callid
= sipmsg_find_call_id_header(msg
);
1072 dialog
= sipe_dialog_find(session
, session
->with
);
1073 if (sipe_strequal(callid
, dialog
->callid
)) {
1075 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
1077 if (((node
= sipe_xml_child(xml
, "rpl")) != NULL
) ||
1078 ((node
= sipe_xml_child(xml
, "ntc")) != NULL
)) {
1079 chatserver_response(sipe_private
, node
, session
);
1080 } else if ((node
= sipe_xml_child(xml
, "grpchat")) != NULL
) {
1081 chatserver_grpchat_message(sipe_private
, node
);
1083 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info_groupchat: ignoring unknown response");
1088 * Our last session got disconnected without proper shutdown,
1089 * e.g. by Pidgin crashing or network connection loss. When
1090 * we reconnect to the group chat the server will send INFO
1091 * messages to the current *AND* the obsolete Call-ID, until
1092 * the obsolete session expires.
1094 * Ignore these INFO messages to avoid, e.g. duplicate texts,
1095 * and respond with an error so that the server knows that we
1096 * consider this dialog to be terminated.
1098 SIPE_DEBUG_INFO("process_incoming_info_groupchat: ignoring unsolicited INFO message to obsolete Call-ID: %s",
1101 sip_transport_response(sipe_private
, msg
, 487, "Request Terminated", NULL
);
1107 void sipe_groupchat_send(struct sipe_core_private
*sipe_private
,
1108 struct sipe_chat_session
*chat_session
,
1111 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1112 gchar
*cmd
, *self
, *timestamp
, *tmp
;
1113 gchar
**lines
, **strvp
;
1114 struct sipe_groupchat_msg
*msg
;
1116 if (!groupchat
|| !chat_session
)
1119 SIPE_DEBUG_INFO("sipe_groupchat_send: '%s' to %s",
1120 what
, chat_session
->id
);
1122 self
= sip_uri_self(sipe_private
);
1123 timestamp
= sipe_utils_time_to_str(time(NULL
));
1126 * 'what' is already XML-escaped, e.g.
1133 * Group Chat only accepts plain text, not full HTML. So we have to
1134 * strip all HTML tags and XML escape the text.
1136 * Line breaks are encoded as <br> and therefore need to be replaced
1137 * before stripping. In order to prevent HTML stripping to strip line
1138 * endings, we need to split the text into lines on <br>.
1140 lines
= g_strsplit(what
, "<br>", 0);
1141 for (strvp
= lines
; *strvp
; strvp
++) {
1142 /* replace array entry with HTML stripped & XML escaped version */
1143 gchar
*stripped
= sipe_backend_markup_strip_html(*strvp
);
1144 gchar
*escaped
= g_markup_escape_text(stripped
, -1);
1149 tmp
= g_strjoinv("\r\n", lines
);
1151 cmd
= g_strdup_printf("<grpchat id=\"grpchat\" seqid=\"1\" chanUri=\"%s\" author=\"%s\" ts=\"%s\">"
1154 chat_session
->id
, self
, timestamp
, tmp
);
1158 msg
= chatserver_command(sipe_private
, cmd
);
1162 msg
->session
= chat_session
;
1163 msg
->content
= g_strdup(what
);
1165 chatserver_command_error_notify(sipe_private
,
1171 void sipe_groupchat_leave(struct sipe_core_private
*sipe_private
,
1172 struct sipe_chat_session
*chat_session
)
1174 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1177 if (!groupchat
|| !chat_session
)
1180 SIPE_DEBUG_INFO("sipe_groupchat_leave: %s", chat_session
->id
);
1182 cmd
= g_strdup_printf("<cmd id=\"cmd:part\" seqid=\"1\">"
1184 "<chanib uri=\"%s\"/>"
1186 "</cmd>", chat_session
->id
);
1187 chatserver_command(sipe_private
, cmd
);
1191 gboolean
sipe_core_groupchat_query_rooms(struct sipe_core_public
*sipe_public
)
1193 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1194 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1196 if (!groupchat
|| !groupchat
->connected
)
1199 chatserver_command(sipe_private
,
1200 "<cmd id=\"cmd:chansrch\" seqid=\"1\">"
1202 "<qib qtype=\"BYNAME\" criteria=\"\" extended=\"false\"/>"
1209 void sipe_core_groupchat_join(struct sipe_core_public
*sipe_public
,
1212 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1213 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1215 if (!g_str_has_prefix(uri
, "ma-chan://"))
1219 /* This happens when a user has set auto-join on a channel */
1220 sipe_groupchat_allocate(sipe_private
);
1221 groupchat
= sipe_private
->groupchat
;
1224 if (groupchat
->connected
) {
1225 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
1228 /* Already joined? */
1231 /* Yes, update backend session */
1232 SIPE_DEBUG_INFO("sipe_core_groupchat_join: show '%s' (%s)",
1233 chat_session
->title
,
1235 sipe_backend_chat_show(chat_session
->backend
);
1238 /* No, send command out directly */
1239 gchar
*chanid
= generate_chanid_node(uri
, 0);
1241 gchar
*cmd
= g_strdup_printf("<cmd id=\"cmd:join\" seqid=\"1\">"
1245 SIPE_DEBUG_INFO("sipe_core_groupchat_join: join %s",
1247 chatserver_command(sipe_private
, cmd
);
1253 /* Add it to the queue but avoid duplicates */
1254 if (!g_slist_find_custom(groupchat
->join_queue
, uri
,
1255 (GCompareFunc
)g_strcmp0
)) {
1256 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_groupchat_join: URI queued");
1257 groupchat
->join_queue
= g_slist_prepend(groupchat
->join_queue
,
1263 void sipe_groupchat_rejoin(struct sipe_core_private
*sipe_private
,
1264 struct sipe_chat_session
*chat_session
)
1266 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1269 /* First rejoined channel after reconnect will trigger this */
1270 sipe_groupchat_allocate(sipe_private
);
1271 groupchat
= sipe_private
->groupchat
;
1274 /* Remember "old" session, so that we don't recreate it at join */
1275 g_hash_table_insert(groupchat
->uri_to_chat_session
,
1278 sipe_core_groupchat_join(SIPE_CORE_PUBLIC
, chat_session
->id
);