security: fix target name memory leak in Kerberos
[siplcs.git] / src / core / sipe-groupchat.c
blob3bd65e8fdff5a84a763750c4326337c013f5158e
1 /**
2 * @file sipe-groupchat.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 /**
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 * XML XCCOS message specification
35 * <???> (searches on the internet currently reveal nothing)
38 * @TODO:
40 * -.cmd:getserverinfo
41 * <sib domain="<DOMAIN>" infoType="123" />
42 * rpl:getservinfo
43 * <sib infoType="123"
44 * serverTime="2010-09-14T14:26:17.6206356Z"
45 * searchLimit="999"
46 * messageSizeLimit="512"
47 * storySizeLimit="4096"
48 * rootUri="ma-cat://<DOMAIN>/<GUID>"
49 * dbVersion="3ea3a5a8-ef36-46cf-898f-7a5133931d63"
50 * />
52 * is there any information in there we would need/use?
54 * - cmd:getpref/rpl:getpref/cmd:setpref/rpl:setpref
55 * probably useless, as libpurple stores configuration locally
57 * can store base64 encoded "free text" in key/value fashion
58 * <cmd id="cmd:getpref" seqid="x">
59 * <data>
60 * <pref label="kedzie.GroupChannels"
61 * seqid="71"
62 * createdefault="true" />
63 * </data>
64 * </cmd>
65 * <cmd id="cmd:setpref" seqid="x">
66 * <data>
67 * <pref label="kedzie.GroupChannels"
68 * seqid="71"
69 * createdefault="false"
70 * content="<BASE64 text>" />
71 * </data>
72 * </cmd>
74 * use this to sync chats in buddy list on multiple clients?
76 * - cmd:bccontext
77 * send after cmd:join to trigger rpl:bccontext
79 * - rpl:bccontext
80 * according to available documentation delivers channel history, etc.
81 * [no log file examples]
82 * can we add the history to the window when we open the window, or would
83 * that confuse users that use history based on client log?
85 * - cmd:getinv
86 * <inv inviteId="1" domain="<DOMAIN>" />
87 * rpl:getinv
88 * ???
90 * according to documentation should provide list of outstanding invites.
91 * [no log file examples]
92 * should we automatically join those channels or ask user to join/add?
94 * - chatserver_command_message()
95 * needs to support multiple <chatgrp> nodes?
96 * [no log file examples]
98 * - create/delete chat rooms
99 * [no log file examples]
100 * are these related to this functionality?
102 * <cmd id="cmd:nodespermcreatechild" seqid="1">
103 * <data />
104 * </cmd>
105 * <rpl id="rpl:nodespermcreatechild" seqid="1">
106 * <commandid seqid="1" envid="xxx" />
107 * <resp code="200">SUCCESS_OK</resp>
108 * <data />
109 * </rpl>
111 * - file transfer (uses HTTPS PUT/GET via a filestore server)
112 * [no log file examples]
116 #ifdef HAVE_CONFIG_H
117 #include "config.h"
118 #endif
120 #include <stdlib.h>
121 #include <string.h>
123 #include <glib.h>
125 #include "sipe-common.h"
126 #include "sipmsg.h"
127 #include "sip-transport.h"
128 #include "sipe-backend.h"
129 #include "sipe-chat.h"
130 #include "sipe-core.h"
131 #include "sipe-core-private.h"
132 #include "sipe-dialog.h"
133 #include "sipe-groupchat.h"
134 #include "sipe-im.h"
135 #include "sipe-nls.h"
136 #include "sipe-schedule.h"
137 #include "sipe-session.h"
138 #include "sipe-utils.h"
139 #include "sipe-xml.h"
141 #define GROUPCHAT_RETRY_TIMEOUT 5*60 /* seconds */
144 * aib node - magic numbers?
146 * Example:
147 * <aib key="3984" value="0,1,2,3,4,5,7,9,10,12,13,14,15,16,17" />
148 * <aib key="12276" value="6,8,11" />
150 * "value" corresponds to the "id" attribute in uib nodes.
152 * @TODO: Confirm "guessed" meaning of the magic numbers:
153 * 3984 = normal users
154 * 12276 = channel operators
156 #define GROUPCHAT_AIB_KEY_USER "3984"
157 #define GROUPCHAT_AIB_KEY_CHANOP "12276"
159 struct sipe_groupchat {
160 struct sip_session *session;
161 gchar *domain;
162 GSList *join_queue;
163 GHashTable *uri_to_chat_session;
164 GHashTable *msgs;
165 guint envid;
166 gboolean connected;
169 struct sipe_groupchat_msg {
170 GHashTable *container;
171 struct sipe_chat_session *session;
172 gchar *content;
173 gchar *xccos;
174 guint envid;
177 /* GDestroyNotify */
178 static void sipe_groupchat_msg_free(gpointer data) {
179 struct sipe_groupchat_msg *msg = data;
180 g_free(msg->content);
181 g_free(msg->xccos);
182 g_free(msg);
185 /* GDestroyNotify */
186 static void sipe_groupchat_msg_remove(gpointer data) {
187 struct sipe_groupchat_msg *msg = data;
188 g_hash_table_remove(msg->container, &msg->envid);
191 static void sipe_groupchat_allocate(struct sipe_core_private *sipe_private)
193 struct sipe_groupchat *groupchat = g_new0(struct sipe_groupchat, 1);
195 groupchat->uri_to_chat_session = g_hash_table_new(g_str_hash, g_str_equal);
196 groupchat->msgs = g_hash_table_new_full(g_int_hash, g_int_equal,
197 NULL,
198 sipe_groupchat_msg_free);
199 groupchat->envid = rand();
200 groupchat->connected = FALSE;
201 sipe_private->groupchat = groupchat;
204 static void sipe_groupchat_free_join_queue(struct sipe_groupchat *groupchat)
206 GSList *entry = groupchat->join_queue;
207 while (entry) {
208 g_free(entry->data);
209 entry = entry->next;
211 g_slist_free(groupchat->join_queue);
212 groupchat->join_queue = NULL;
215 void sipe_groupchat_free(struct sipe_core_private *sipe_private)
217 struct sipe_groupchat *groupchat = sipe_private->groupchat;
218 if (groupchat) {
219 sipe_groupchat_free_join_queue(groupchat);
220 g_hash_table_destroy(groupchat->msgs);
221 g_hash_table_destroy(groupchat->uri_to_chat_session);
222 g_free(groupchat->domain);
223 g_free(groupchat);
224 sipe_private->groupchat = NULL;
228 static struct sipe_groupchat_msg *generate_xccos_message(struct sipe_groupchat *groupchat,
229 const gchar *content)
231 struct sipe_groupchat_msg *msg = g_new0(struct sipe_groupchat_msg, 1);
233 msg->container = groupchat->msgs;
234 msg->envid = groupchat->envid++;
235 msg->xccos = g_strdup_printf("<xccos ver=\"1\" envid=\"%u\" xmlns=\"urn:parlano:xml:ns:xccos\">"
236 "%s"
237 "</xccos>",
238 msg->envid,
239 content);
241 g_hash_table_insert(groupchat->msgs, &msg->envid, msg);
243 return(msg);
247 * Create short-lived dialog with ocschat@<domain> (or user specified value)
248 * This initiates the Group Chat feature
250 void sipe_groupchat_init(struct sipe_core_private *sipe_private)
252 const gchar *setting = sipe_backend_setting(SIPE_CORE_PUBLIC,
253 SIPE_SETTING_GROUPCHAT_USER);
254 gboolean user_set = !is_empty(setting);
255 gchar **parts = g_strsplit(user_set ? setting : sipe_private->username,
256 "@", 2);
257 gboolean domain_found = !is_empty(parts[1]);
258 const gchar *user = "ocschat";
259 const gchar *domain = parts[domain_found ? 1 : 0];
260 gchar *chat_uri;
261 struct sip_session *session;
262 struct sipe_groupchat *groupchat;
264 /* User specified valid 'user@company.com' */
265 if (user_set && domain_found && !is_empty(parts[0]))
266 user = parts[0];
268 SIPE_DEBUG_INFO("sipe_groupchat_init: username '%s' setting '%s' split '%s'/'%s' GC user %s@%s",
269 sipe_private->username, setting ? setting : "(null)", parts[0],
270 parts[1] ? parts[1] : "(null)", user, domain);
272 if (!sipe_private->groupchat)
273 sipe_groupchat_allocate(sipe_private);
274 groupchat = sipe_private->groupchat;
276 chat_uri = g_strdup_printf("sip:%s@%s", user, domain);
277 session = sipe_session_find_or_add_im(sipe_private,
278 chat_uri);
279 session->is_groupchat = TRUE;
280 sipe_im_invite(sipe_private, session, chat_uri,
281 NULL, NULL, NULL, FALSE);
283 g_free(groupchat->domain);
284 groupchat->domain = g_strdup(domain);
286 g_free(chat_uri);
287 g_strfreev(parts);
290 /* sipe_schedule_action */
291 static void groupchat_init_retry_cb(struct sipe_core_private *sipe_private,
292 SIPE_UNUSED_PARAMETER gpointer data)
294 sipe_groupchat_init(sipe_private);
297 static void groupchat_init_retry(struct sipe_core_private *sipe_private)
299 struct sipe_groupchat *groupchat = sipe_private->groupchat;
301 SIPE_DEBUG_INFO_NOFORMAT("groupchat_init_retry: trying again later...");
303 groupchat->session = NULL;
304 groupchat->connected = FALSE;
306 sipe_schedule_seconds(sipe_private,
307 "<+grouchat-retry>",
308 NULL,
309 GROUPCHAT_RETRY_TIMEOUT,
310 groupchat_init_retry_cb,
311 NULL);
314 void sipe_groupchat_invite_failed(struct sipe_core_private *sipe_private,
315 struct sip_session *session)
317 struct sipe_groupchat *groupchat = sipe_private->groupchat;
318 const gchar *setting = sipe_backend_setting(SIPE_CORE_PUBLIC,
319 SIPE_SETTING_GROUPCHAT_USER);
320 gboolean retry = FALSE;
322 if (groupchat->session) {
323 /* response to group chat server invite */
324 SIPE_DEBUG_ERROR_NOFORMAT("can't connect to group chat server!");
326 /* group chat server exists, but communication failed */
327 retry = TRUE;
328 } else {
329 /* response to initial invite */
330 SIPE_DEBUG_INFO_NOFORMAT("no group chat server found.");
333 sipe_session_close(sipe_private, session);
335 if (!is_empty(setting)) {
336 gchar *msg = g_strdup_printf(_("Group Chat Proxy setting is incorrect:\n\n\t%s\n\nPlease update your Account."),
337 setting);
338 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
339 _("Couldn't find Group Chat server!"),
340 msg);
341 g_free(msg);
343 /* user specified group chat settings: we should retry */
344 retry = TRUE;
347 if (retry) {
348 groupchat_init_retry(sipe_private);
349 } else {
350 SIPE_DEBUG_INFO_NOFORMAT("disabling group chat feature.");
354 static gchar *generate_chanid_node(const gchar *uri, guint key)
356 /* ma-chan://<domain>/<value> */
357 gchar **parts = g_strsplit(uri, "/", 4);
358 gchar *chanid = NULL;
360 if (parts[2] && parts[3]) {
361 chanid = g_strdup_printf("<chanid key=\"%d\" domain=\"%s\" value=\"%s\"/>",
362 key, parts[2], parts[3]);
363 } else {
364 SIPE_DEBUG_ERROR("generate_chanid_node: mal-formed URI '%s'",
365 uri);
367 g_strfreev(parts);
369 return chanid;
372 static struct sipe_groupchat_msg *chatserver_command(struct sipe_core_private *sipe_private,
373 const gchar *cmd);
375 void sipe_groupchat_invite_response(struct sipe_core_private *sipe_private,
376 struct sip_dialog *dialog)
378 struct sipe_groupchat *groupchat = sipe_private->groupchat;
380 SIPE_DEBUG_INFO_NOFORMAT("sipe_groupchat_invite_response");
382 if (!groupchat->session) {
383 /* response to initial invite */
384 struct sipe_groupchat_msg *msg = generate_xccos_message(groupchat,
385 "<cmd id=\"cmd:requri\" seqid=\"1\"><data/></cmd>");
386 sip_transport_info(sipe_private,
387 "Content-Type: text/plain\r\n",
388 msg->xccos,
389 dialog,
390 NULL);
391 sipe_groupchat_msg_remove(msg);
393 } else {
394 /* response to group chat server invite */
395 gchar *invcmd;
397 SIPE_DEBUG_INFO_NOFORMAT("connection to group chat server established.");
399 groupchat->connected = TRUE;
401 /* Any queued joins? */
402 if (groupchat->join_queue) {
403 GString *cmd = g_string_new("<cmd id=\"cmd:bjoin\" seqid=\"1\">"
404 "<data>");
405 GSList *entry;
406 guint i = 0;
408 /* We used g_slist_prepend() to create the list */
409 groupchat->join_queue = entry = g_slist_reverse(groupchat->join_queue);
410 while (entry) {
411 gchar *chanid = generate_chanid_node(entry->data, i++);
412 g_string_append(cmd, chanid);
413 g_free(chanid);
414 entry = entry->next;
416 sipe_groupchat_free_join_queue(groupchat);
418 g_string_append(cmd, "</data></cmd>");
419 chatserver_command(sipe_private, cmd->str);
420 g_string_free(cmd, TRUE);
423 /* Request outstanding invites from server */
424 invcmd = g_strdup_printf("<cmd id=\"cmd:getinv\" seqid=\"1\">"
425 "<data>"
426 "<inv inviteId=\"1\" domain=\"%s\"/>"
427 "</data>"
428 "</cmd>", groupchat->domain);
429 chatserver_command(sipe_private, invcmd);
430 g_free(invcmd);
434 /* TransCallback */
435 static gboolean chatserver_command_response(struct sipe_core_private *sipe_private,
436 struct sipmsg *msg,
437 struct transaction *trans)
439 if (msg->response != 200) {
440 struct sipe_groupchat_msg *gmsg = trans->payload->data;
441 struct sipe_chat_session *chat_session = gmsg->session;
443 SIPE_DEBUG_INFO("chatserver_command_response: failure %d", msg->response);
445 if (chat_session) {
446 gchar *label = g_strdup_printf(_("This message was not delivered to chat room '%s'"),
447 chat_session->title);
448 gchar *errmsg = g_strdup_printf("%s:\n<font color=\"#888888\"></b>%s<b></font>",
449 label, gmsg->content);
450 g_free(label);
451 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC,
452 chat_session->backend,
453 NULL,
454 errmsg);
455 g_free(errmsg);
458 return TRUE;
461 static struct sipe_groupchat_msg *chatserver_command(struct sipe_core_private *sipe_private,
462 const gchar *cmd)
464 struct sipe_groupchat *groupchat = sipe_private->groupchat;
465 struct sipe_groupchat_msg *msg = generate_xccos_message(groupchat, cmd);
467 struct sip_dialog *dialog = sipe_dialog_find(groupchat->session,
468 groupchat->session->with);
470 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
471 struct transaction *trans = sip_transport_info(sipe_private,
472 "Content-Type: text/plain\r\n",
473 msg->xccos,
474 dialog,
475 chatserver_command_response);
477 payload->destroy = sipe_groupchat_msg_remove;
478 payload->data = msg;
479 trans->payload = payload;
481 return(msg);
484 static void chatserver_response_uri(struct sipe_core_private *sipe_private,
485 struct sip_session *session,
486 SIPE_UNUSED_PARAMETER guint result,
487 SIPE_UNUSED_PARAMETER const gchar *message,
488 const sipe_xml *xml)
490 const sipe_xml *uib = sipe_xml_child(xml, "uib");
491 const gchar *uri = sipe_xml_attribute(uib, "uri");
493 /* drop connection to ocschat@<domain> again */
494 sipe_session_close(sipe_private, session);
496 if (uri) {
497 struct sipe_groupchat *groupchat = sipe_private->groupchat;
499 SIPE_DEBUG_INFO("chatserver_response_uri: '%s'", uri);
501 groupchat->session = session = sipe_session_find_or_add_im(sipe_private,
502 uri);
504 session->is_groupchat = TRUE;
505 sipe_im_invite(sipe_private, session, uri, NULL, NULL, NULL, FALSE);
506 } else {
507 SIPE_DEBUG_WARNING_NOFORMAT("process_incoming_info_groupchat: no server URI found!");
508 groupchat_init_retry(sipe_private);
512 static void chatserver_response_channel_search(struct sipe_core_private *sipe_private,
513 SIPE_UNUSED_PARAMETER struct sip_session *session,
514 guint result,
515 const gchar *message,
516 const sipe_xml *xml)
518 struct sipe_core_public *sipe_public = SIPE_CORE_PUBLIC;
520 if (result != 200) {
521 sipe_backend_notify_error(sipe_public,
522 _("Error retrieving room list"),
523 message);
524 } else {
525 const sipe_xml *chanib;
527 for (chanib = sipe_xml_child(xml, "chanib");
528 chanib;
529 chanib = sipe_xml_twin(chanib)) {
530 const gchar *name = sipe_xml_attribute(chanib, "name");
531 const gchar *desc = sipe_xml_attribute(chanib, "description");
532 const gchar *uri = sipe_xml_attribute(chanib, "uri");
533 const sipe_xml *node;
534 guint user_count = 0;
535 guint32 flags = 0;
537 /* information */
538 for (node = sipe_xml_child(chanib, "info");
539 node;
540 node = sipe_xml_twin(node)) {
541 const gchar *id = sipe_xml_attribute(node, "id");
542 gchar *data;
544 if (!id) continue;
546 data = sipe_xml_data(node);
547 if (data) {
548 if (sipe_strcase_equal(id, "urn:parlano:ma:info:ucnt")) {
549 user_count = g_ascii_strtoll(data, NULL, 10);
550 } else if (sipe_strcase_equal(id, "urn:parlano:ma:info:visibilty")) {
551 if (sipe_strcase_equal(data, "private")) {
552 flags |= SIPE_GROUPCHAT_ROOM_PRIVATE;
555 g_free(data);
559 /* properties */
560 for (node = sipe_xml_child(chanib, "prop");
561 node;
562 node = sipe_xml_twin(node)) {
563 const gchar *id = sipe_xml_attribute(node, "id");
564 gchar *data;
566 if (!id) continue;
568 data = sipe_xml_data(node);
569 if (data) {
570 gboolean value = sipe_strcase_equal(data, "true");
571 g_free(data);
573 if (value) {
574 guint32 add = 0;
575 if (sipe_strcase_equal(id, "urn:parlano:ma:prop:filepost")) {
576 add = SIPE_GROUPCHAT_ROOM_FILEPOST;
577 } else if (sipe_strcase_equal(id, "urn:parlano:ma:prop:invite")) {
578 add = SIPE_GROUPCHAT_ROOM_INVITE;
579 } else if (sipe_strcase_equal(id, "urn:parlano:ma:prop:logged")) {
580 add = SIPE_GROUPCHAT_ROOM_LOGGED;
582 flags |= add;
587 SIPE_DEBUG_INFO("group chat channel '%s': '%s' (%s) with %u users, flags 0x%x",
588 name, desc, uri, user_count, flags);
589 sipe_backend_groupchat_room_add(sipe_public,
590 uri, name, desc,
591 user_count, flags);
595 sipe_backend_groupchat_room_terminate(sipe_public);
598 static gboolean is_chanop(const sipe_xml *aib)
600 return sipe_strequal(sipe_xml_attribute(aib, "key"),
601 GROUPCHAT_AIB_KEY_CHANOP);
604 static void add_user(struct sipe_chat_session *chat_session,
605 const gchar *uri,
606 gboolean new, gboolean chanop)
608 SIPE_DEBUG_INFO("add_user: %s%s%s to room %s (%s)",
609 new ? "new " : "",
610 chanop ? "chanop " : "",
611 uri,
612 chat_session->title, chat_session->id);
613 sipe_backend_chat_add(chat_session->backend, uri, new);
614 if (chanop)
615 sipe_backend_chat_operator(chat_session->backend, uri);
618 static void chatserver_response_join(struct sipe_core_private *sipe_private,
619 SIPE_UNUSED_PARAMETER struct sip_session *session,
620 guint result,
621 const gchar *message,
622 const sipe_xml *xml)
624 if (result != 200) {
625 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
626 _("Error joining chat room"),
627 message);
628 } else {
629 struct sipe_groupchat *groupchat = sipe_private->groupchat;
630 const sipe_xml *node;
631 GHashTable *user_ids = g_hash_table_new(g_str_hash, g_str_equal);
633 /* Extract user IDs & URIs and generate ID -> URI map */
634 for (node = sipe_xml_child(xml, "uib");
635 node;
636 node = sipe_xml_twin(node)) {
637 const gchar *id = sipe_xml_attribute(node, "id");
638 const gchar *uri = sipe_xml_attribute(node, "uri");
639 if (id && uri)
640 g_hash_table_insert(user_ids,
641 (gpointer) id,
642 (gpointer) uri);
645 /* Process channel data */
646 for (node = sipe_xml_child(xml, "chanib");
647 node;
648 node = sipe_xml_twin(node)) {
649 const gchar *uri = sipe_xml_attribute(node, "uri");
651 if (uri) {
652 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
653 uri);
654 gboolean new = (chat_session == NULL);
655 const gchar *attr = sipe_xml_attribute(node, "name");
656 char *self = sip_uri_self(sipe_private);
657 const sipe_xml *aib;
659 if (new) {
660 chat_session = sipe_chat_create_session(SIPE_CHAT_TYPE_GROUPCHAT,
661 sipe_xml_attribute(node,
662 "uri"),
663 attr ? attr : "");
664 g_hash_table_insert(groupchat->uri_to_chat_session,
665 chat_session->id,
666 chat_session);
668 SIPE_DEBUG_INFO("joined room '%s' (%s)",
669 chat_session->title,
670 chat_session->id);
671 chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
672 chat_session,
673 chat_session->title,
674 self);
675 } else {
676 SIPE_DEBUG_INFO("rejoining room '%s' (%s)",
677 chat_session->title,
678 chat_session->id);
679 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
680 chat_session->backend,
681 self,
682 chat_session->title);
684 g_free(self);
686 attr = sipe_xml_attribute(node, "topic");
687 if (attr) {
688 sipe_backend_chat_topic(chat_session->backend,
689 attr);
692 /* Process user map for channel */
693 for (aib = sipe_xml_child(node, "aib");
694 aib;
695 aib = sipe_xml_twin(aib)) {
696 const gchar *value = sipe_xml_attribute(aib, "value");
697 gboolean chanop = is_chanop(aib);
698 gchar **ids = g_strsplit(value, ",", 0);
700 if (ids) {
701 gchar **uid = ids;
703 while (*uid) {
704 const gchar *uri = g_hash_table_lookup(user_ids,
705 *uid);
706 if (uri)
707 add_user(chat_session,
708 uri,
709 FALSE,
710 chanop);
711 uid++;
714 g_strfreev(ids);
720 g_hash_table_destroy(user_ids);
724 static void chatserver_response_part(struct sipe_core_private *sipe_private,
725 SIPE_UNUSED_PARAMETER struct sip_session *session,
726 guint result,
727 const gchar *message,
728 const sipe_xml *xml)
730 if (result != 200) {
731 SIPE_DEBUG_WARNING("chatserver_response_part: failed with %d: %s. Dropping room",
732 result, message);
733 } else {
734 struct sipe_groupchat *groupchat = sipe_private->groupchat;
735 const gchar *uri = sipe_xml_attribute(sipe_xml_child(xml, "chanib"),
736 "uri");
737 struct sipe_chat_session *chat_session;
739 if (uri &&
740 (chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
741 uri))) {
743 SIPE_DEBUG_INFO("leaving room '%s' (%s)",
744 chat_session->title, chat_session->id);
746 g_hash_table_remove(groupchat->uri_to_chat_session,
747 uri);
748 sipe_chat_remove_session(chat_session);
750 } else {
751 SIPE_DEBUG_WARNING("chatserver_response_part: unknown chat room uri '%s'",
752 uri ? uri : "");
757 static void chatserver_notice_join(struct sipe_core_private *sipe_private,
758 SIPE_UNUSED_PARAMETER struct sip_session *session,
759 SIPE_UNUSED_PARAMETER guint result,
760 SIPE_UNUSED_PARAMETER const gchar *message,
761 const sipe_xml *xml)
763 struct sipe_groupchat *groupchat = sipe_private->groupchat;
764 const sipe_xml *uib;
766 for (uib = sipe_xml_child(xml, "uib");
767 uib;
768 uib = sipe_xml_twin(uib)) {
769 const gchar *uri = sipe_xml_attribute(uib, "uri");
771 if (uri) {
772 const sipe_xml *aib;
774 for (aib = sipe_xml_child(uib, "aib");
775 aib;
776 aib = sipe_xml_twin(aib)) {
777 const gchar *domain = sipe_xml_attribute(aib, "domain");
778 const gchar *path = sipe_xml_attribute(aib, "value");
780 if (domain && path) {
781 gchar *room_uri = g_strdup_printf("ma-chan://%s/%s",
782 domain, path);
783 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
784 room_uri);
785 if (chat_session)
786 add_user(chat_session,
787 uri,
788 TRUE,
789 is_chanop(aib));
791 g_free(room_uri);
798 static void chatserver_notice_part(struct sipe_core_private *sipe_private,
799 SIPE_UNUSED_PARAMETER struct sip_session *session,
800 SIPE_UNUSED_PARAMETER guint result,
801 SIPE_UNUSED_PARAMETER const gchar *message,
802 const sipe_xml *xml)
804 struct sipe_groupchat *groupchat = sipe_private->groupchat;
805 const sipe_xml *chanib;
807 for (chanib = sipe_xml_child(xml, "chanib");
808 chanib;
809 chanib = sipe_xml_twin(chanib)) {
810 const gchar *room_uri = sipe_xml_attribute(chanib, "uri");
812 if (room_uri) {
813 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
814 room_uri);
816 if (chat_session) {
817 const sipe_xml *uib;
819 for (uib = sipe_xml_child(chanib, "uib");
820 uib;
821 uib = sipe_xml_twin(uib)) {
822 const gchar *uri = sipe_xml_attribute(uib, "uri");
824 if (uri) {
825 SIPE_DEBUG_INFO("remove_user: %s from room %s (%s)",
826 uri,
827 chat_session->title,
828 chat_session->id);
829 sipe_backend_chat_remove(chat_session->backend,
830 uri);
838 static const struct response {
839 const gchar *key;
840 void (* const handler)(struct sipe_core_private *,
841 struct sip_session *,
842 guint result, const gchar *,
843 const sipe_xml *xml);
844 } response_table[] = {
845 { "rpl:requri", chatserver_response_uri },
846 { "rpl:chansrch", chatserver_response_channel_search },
847 { "rpl:join", chatserver_response_join },
848 { "rpl:bjoin", chatserver_response_join },
849 { "rpl:part", chatserver_response_part },
850 { "ntc:join", chatserver_notice_join },
851 { "ntc:bjoin", chatserver_notice_join },
852 { "ntc:part", chatserver_notice_part },
853 { NULL, NULL }
856 /* Handles rpl:XXX & ntc:YYY */
857 static void chatserver_response(struct sipe_core_private *sipe_private,
858 const sipe_xml *reply,
859 struct sip_session *session)
861 do {
862 const sipe_xml *resp, *data;
863 const gchar *id;
864 gchar *message;
865 guint result = 500;
866 const struct response *r;
868 id = sipe_xml_attribute(reply, "id");
869 if (!id) {
870 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: no reply ID found!");
871 continue;
874 resp = sipe_xml_child(reply, "resp");
875 if (resp) {
876 result = sipe_xml_int_attribute(resp, "code", 500);
877 message = sipe_xml_data(resp);
878 } else {
879 message = g_strdup("");
882 data = sipe_xml_child(reply, "data");
884 SIPE_DEBUG_INFO("chatserver_response: '%s' result (%d) %s",
885 id, result, message ? message : "");
887 for (r = response_table; r->key; r++) {
888 if (sipe_strcase_equal(id, r->key)) {
889 (*r->handler)(sipe_private, session, result, message, data);
890 break;
893 if (!r->key) {
894 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: ignoring unknown response");
897 g_free(message);
898 } while ((reply = sipe_xml_twin(reply)) != NULL);
901 static void chatserver_grpchat_message(struct sipe_core_private *sipe_private,
902 const sipe_xml *chatgrp)
904 struct sipe_groupchat *groupchat = sipe_private->groupchat;
905 const gchar *uri = sipe_xml_attribute(chatgrp, "chanUri");
906 const gchar *from = sipe_xml_attribute(chatgrp, "author");
907 gchar *text = sipe_xml_data(sipe_xml_child(chatgrp, "chat"));
908 struct sipe_chat_session *chat_session;
909 gchar *escaped;
911 if (!uri || !from) {
912 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' received without chat room URI or author!",
913 text ? text : "");
914 g_free(text);
915 return;
918 chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
919 uri);
920 if (!chat_session) {
921 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' from '%s' received from unknown chat room '%s'!",
922 text ? text : "", from, uri);
923 g_free(text);
924 return;
927 /* libxml2 decodes all entities, but the backend expects HTML */
928 escaped = g_markup_escape_text(text, -1);
929 g_free(text);
930 sipe_backend_chat_message(SIPE_CORE_PUBLIC, chat_session->backend,
931 from, escaped);
932 g_free(escaped);
935 void process_incoming_info_groupchat(struct sipe_core_private *sipe_private,
936 struct sipmsg *msg,
937 struct sip_session *session)
939 sipe_xml *xml = sipe_xml_parse(msg->body, msg->bodylen);
940 const sipe_xml *node;
942 /* @TODO: is this always correct?*/
943 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
945 if (!xml) return;
947 if (((node = sipe_xml_child(xml, "rpl")) != NULL) ||
948 ((node = sipe_xml_child(xml, "ntc")) != NULL)) {
949 chatserver_response(sipe_private, node, session);
950 } else if ((node = sipe_xml_child(xml, "grpchat")) != NULL) {
951 chatserver_grpchat_message(sipe_private, node);
952 } else {
953 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info_groupchat: ignoring unknown response");
956 sipe_xml_free(xml);
959 void sipe_groupchat_send(struct sipe_core_private *sipe_private,
960 struct sipe_chat_session *chat_session,
961 const gchar *what)
963 struct sipe_groupchat *groupchat = sipe_private->groupchat;
964 gchar *cmd, *self, *timestamp, *tmp;
965 struct sipe_groupchat_msg *msg;
967 if (!groupchat || !chat_session)
968 return;
970 SIPE_DEBUG_INFO("sipe_groupchat_send: '%s' to %s",
971 what, chat_session->id);
973 self = sip_uri_self(sipe_private);
974 timestamp = sipe_utils_time_to_str(time(NULL));
977 * 'what' is already XML-escaped, e.g.
979 * " -> &quot;
980 * > -> &gt;
981 * < -> &lt;
982 * & -> &amp;
984 * No need to escape them here.
986 * Only exception are line breaks which are encoded as <br>.
987 * Replace them with the correct XML tag <br/>.
989 tmp = replace(what, "<br>", "<br/>");
990 cmd = g_strdup_printf("<grpchat id=\"grpchat\" seqid=\"1\" chanUri=\"%s\" author=\"%s\" ts=\"%s\">"
991 "<chat>%s</chat>"
992 "</grpchat>",
993 chat_session->id, self, timestamp, tmp);
994 g_free(tmp);
995 g_free(timestamp);
996 g_free(self);
997 msg = chatserver_command(sipe_private, cmd);
998 g_free(cmd);
1000 msg->session = chat_session;
1001 msg->content = g_strdup(what);
1004 void sipe_groupchat_leave(struct sipe_core_private *sipe_private,
1005 struct sipe_chat_session *chat_session)
1007 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1008 gchar *cmd;
1010 if (!groupchat || !chat_session)
1011 return;
1013 SIPE_DEBUG_INFO("sipe_groupchat_leave: %s", chat_session->id);
1015 cmd = g_strdup_printf("<cmd id=\"cmd:part\" seqid=\"1\">"
1016 "<data>"
1017 "<chanib uri=\"%s\"/>"
1018 "</data>"
1019 "</cmd>", chat_session->id);
1020 chatserver_command(sipe_private, cmd);
1021 g_free(cmd);
1024 gboolean sipe_core_groupchat_query_rooms(struct sipe_core_public *sipe_public)
1026 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1027 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1029 if (!groupchat || !groupchat->connected)
1030 return FALSE;
1032 chatserver_command(sipe_private,
1033 "<cmd id=\"cmd:chansrch\" seqid=\"1\">"
1034 "<data>"
1035 "<qib qtype=\"BYNAME\" criteria=\"\" extended=\"false\"/>"
1036 "</data>"
1037 "</cmd>");
1039 return TRUE;
1042 void sipe_core_groupchat_join(struct sipe_core_public *sipe_public,
1043 const gchar *uri)
1045 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1046 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1048 if (!g_str_has_prefix(uri, "ma-chan://"))
1049 return;
1051 if (!groupchat) {
1052 /* This happens when a user has set auto-join on a channel */
1053 sipe_groupchat_allocate(sipe_private);
1054 groupchat = sipe_private->groupchat;
1057 if (groupchat->connected) {
1058 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
1059 uri);
1061 /* Already joined? */
1062 if (chat_session) {
1064 /* Yes, update backend session */
1065 SIPE_DEBUG_INFO("sipe_core_groupchat_join: show '%s' (%s)",
1066 chat_session->title,
1067 chat_session->id);
1068 sipe_backend_chat_show(chat_session->backend);
1070 } else {
1071 /* No, send command out directly */
1072 gchar *chanid = generate_chanid_node(uri, 0);
1073 if (chanid) {
1074 gchar *cmd = g_strdup_printf("<cmd id=\"cmd:join\" seqid=\"1\">"
1075 "<data>%s</data>"
1076 "</cmd>",
1077 chanid);
1078 SIPE_DEBUG_INFO("sipe_core_groupchat_join: join %s",
1079 uri);
1080 chatserver_command(sipe_private, cmd);
1081 g_free(cmd);
1082 g_free(chanid);
1085 } else {
1086 /* Add it to the queue but avoid duplicates */
1087 if (!g_slist_find_custom(groupchat->join_queue, uri,
1088 sipe_strcompare)) {
1089 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_groupchat_join: URI queued");
1090 groupchat->join_queue = g_slist_prepend(groupchat->join_queue,
1091 g_strdup(uri));
1096 void sipe_groupchat_rejoin(struct sipe_core_private *sipe_private,
1097 struct sipe_chat_session *chat_session)
1099 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1101 if (!groupchat) {
1102 /* First rejoined channel after reconnect will trigger this */
1103 sipe_groupchat_allocate(sipe_private);
1104 groupchat = sipe_private->groupchat;
1107 /* Remember "old" session, so that we don't recreate it at join */
1108 g_hash_table_insert(groupchat->uri_to_chat_session,
1109 chat_session->id,
1110 chat_session);
1111 sipe_core_groupchat_join(SIPE_CORE_PUBLIC, chat_session->id);
1115 Local Variables:
1116 mode: c
1117 c-file-style: "bsd"
1118 indent-tabs-mode: t
1119 tab-width: 8
1120 End: